/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include "baside2.hxx" #include "brkdlg.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "textwindowpeer.hxx" #include "uiobject.hxx" #include namespace basctl { using namespace ::com::sun::star; using namespace ::com::sun::star::uno; namespace { sal_uInt16 const NoMarker = 0xFFFF; tools::Long const nBasePad = 2; tools::Long const nCursorPad = 5; tools::Long nVirtToolBoxHeight; // inited in WatchWindow, used in Stackwindow // Returns pBase converted to SbxVariable if valid and is not an SbxMethod. SbxVariable* IsSbxVariable (SbxBase* pBase) { if (SbxVariable* pVar = dynamic_cast(pBase)) if (!dynamic_cast(pVar)) return pVar; return nullptr; } Image GetImage(const OUString& rId) { return Image(StockImage::Yes, rId); } int const nScrollLine = 12; int const nScrollPage = 60; int const DWBORDER = 3; std::u16string_view const cSuffixes = u"%&!#@$"; } // namespace /** * Helper functions to get/set text in TextEngine using * the stream interface. * * get/setText() only supports tools Strings limited to 64K). */ OUString getTextEngineText (ExtTextEngine& rEngine) { SvMemoryStream aMemStream; aMemStream.SetStreamCharSet( RTL_TEXTENCODING_UTF8 ); aMemStream.SetLineDelimiter( LINEEND_LF ); rEngine.Write( aMemStream ); std::size_t nSize = aMemStream.Tell(); OUString aText( static_cast(aMemStream.GetData()), nSize, RTL_TEXTENCODING_UTF8 ); return aText; } void setTextEngineText (ExtTextEngine& rEngine, std::u16string_view aStr) { rEngine.SetText(OUString()); OString aUTF8Str = OUStringToOString( aStr, RTL_TEXTENCODING_UTF8 ); SvMemoryStream aMemStream( const_cast(aUTF8Str.getStr()), aUTF8Str.getLength(), StreamMode::READ ); aMemStream.SetStreamCharSet( RTL_TEXTENCODING_UTF8 ); aMemStream.SetLineDelimiter( LINEEND_LF ); rEngine.Read(aMemStream); } namespace { void lcl_DrawIDEWindowFrame(DockingWindow const * pWin, vcl::RenderContext& rRenderContext) { if (pWin->IsFloatingMode()) return; Size aSz(pWin->GetOutputSizePixel()); const Color aOldLineColor(rRenderContext.GetLineColor()); rRenderContext.SetLineColor(COL_WHITE); // White line on top rRenderContext.DrawLine(Point(0, 0), Point(aSz.Width(), 0)); // Black line at bottom rRenderContext.SetLineColor(COL_BLACK); rRenderContext.DrawLine(Point(0, aSz.Height() - 1), Point(aSz.Width(), aSz.Height() - 1)); rRenderContext.SetLineColor(aOldLineColor); } void lcl_SeparateNameAndIndex( const OUString& rVName, OUString& rVar, OUString& rIndex ) { rVar = rVName; rIndex.clear(); sal_Int32 nIndexStart = rVar.indexOf( '(' ); if ( nIndexStart != -1 ) { sal_Int32 nIndexEnd = rVar.indexOf( ')', nIndexStart ); if (nIndexEnd != -1) { rIndex = rVar.copy(nIndexStart + 1, nIndexEnd - nIndexStart - 1); rVar = rVar.copy(0, nIndexStart); rVar = comphelper::string::stripEnd(rVar, ' '); rIndex = comphelper::string::strip(rIndex, ' '); } } if ( !rVar.isEmpty() ) { sal_uInt16 nLastChar = rVar.getLength()-1; if ( cSuffixes.find(rVar[ nLastChar ] ) != std::u16string_view::npos ) rVar = rVar.replaceAt( nLastChar, 1, u"" ); } if ( !rIndex.isEmpty() ) { sal_uInt16 nLastChar = rIndex.getLength()-1; if ( cSuffixes.find(rIndex[ nLastChar ] ) != std::u16string_view::npos ) rIndex = rIndex.replaceAt( nLastChar, 1, u"" ); } } } // namespace // EditorWindow class EditorWindow::ChangesListener: public cppu::WeakImplHelper< beans::XPropertiesChangeListener > { public: explicit ChangesListener(EditorWindow & editor): editor_(editor) {} private: virtual ~ChangesListener() override {} virtual void SAL_CALL disposing(lang::EventObject const &) override { std::unique_lock g(editor_.mutex_); editor_.notifier_.clear(); } virtual void SAL_CALL propertiesChange( Sequence< beans::PropertyChangeEvent > const &) override { SolarMutexGuard g; editor_.ImplSetFont(); } EditorWindow & editor_; }; class EditorWindow::ProgressInfo : public SfxProgress { public: ProgressInfo (SfxObjectShell* pObjSh, OUString const& rText, sal_uInt32 nRange) : SfxProgress(pObjSh, rText, nRange), nCurState(0) { } void StepProgress () { SetState(++nCurState); } private: sal_uInt32 nCurState; }; EditorWindow::EditorWindow (vcl::Window* pParent, ModulWindow* pModulWindow) : Window(pParent, WB_BORDER), rModulWindow(*pModulWindow), nCurTextWidth(0), m_nSetSourceInBasicId(nullptr), aHighlighter(HighlighterLanguage::Basic), aSyntaxIdle( "basctl EditorWindow aSyntaxIdle" ), bHighlighting(false), bDoSyntaxHighlight(true), bDelayHighlight(true), pCodeCompleteWnd(VclPtr::Create(this)) { set_id("EditorWindow"); const Wallpaper aBackground(rModulWindow.GetLayout().GetSyntaxBackgroundColor()); SetBackground(aBackground); GetWindow(GetWindowType::Border)->SetBackground(aBackground); SetPointer( PointerStyle::Text ); SetHelpId( HID_BASICIDE_EDITORWINDOW ); listener_ = new ChangesListener(*this); Reference< beans::XMultiPropertySet > n( officecfg::Office::Common::Font::SourceViewFont::get(), UNO_QUERY_THROW); { std::unique_lock g(mutex_); notifier_ = n; } // The zoom level applied to the editor window is the zoom slider value in the shell nCurrentZoomLevel = GetShell()->GetCurrentZoomSliderValue(); const Sequence aPropertyNames{"FontHeight", "FontName"}; n->addPropertiesChangeListener(aPropertyNames, listener_); } EditorWindow::~EditorWindow() { disposeOnce(); } void EditorWindow::dispose() { if (m_nSetSourceInBasicId) { Application::RemoveUserEvent(m_nSetSourceInBasicId); m_nSetSourceInBasicId = nullptr; } Reference< beans::XMultiPropertySet > n; { std::unique_lock g(mutex_); n = notifier_; } if (n.is()) { n->removePropertiesChangeListener(listener_); } aSyntaxIdle.Stop(); if ( pEditEngine ) { EndListening( *pEditEngine ); pEditEngine->RemoveView(pEditView.get()); } pCodeCompleteWnd.disposeAndClear(); vcl::Window::dispose(); } OUString EditorWindow::GetWordAtCursor() { OUString aWord; if ( pEditView ) { TextEngine* pTextEngine = pEditView->GetTextEngine(); if ( pTextEngine ) { // check first, if the cursor is at a help URL const TextSelection& rSelection = pEditView->GetSelection(); const TextPaM& rSelStart = rSelection.GetStart(); const TextPaM& rSelEnd = rSelection.GetEnd(); OUString aText = pTextEngine->GetText( rSelEnd.GetPara() ); CharClass aClass( ::comphelper::getProcessComponentContext() , Application::GetSettings().GetLanguageTag() ); sal_Int32 nSelStart = rSelStart.GetIndex(); sal_Int32 nSelEnd = rSelEnd.GetIndex(); sal_Int32 nLength = aText.getLength(); sal_Int32 nStart = 0; sal_Int32 nEnd = nLength; while ( nStart < nLength ) { OUString aURL( URIHelper::FindFirstURLInText( aText, nStart, nEnd, aClass ) ); INetURLObject aURLObj( aURL ); if ( aURLObj.GetProtocol() == INetProtocol::VndSunStarHelp && nSelStart >= nStart && nSelStart <= nEnd && nSelEnd >= nStart && nSelEnd <= nEnd ) { aWord = aURL; break; } nStart = nEnd; nEnd = nLength; } // Not the selected range, but at the CursorPosition, // if a word is partially selected. if ( aWord.isEmpty() ) aWord = pTextEngine->GetWord( rSelEnd ); // Can be empty when full word selected, as Cursor behind it if ( aWord.isEmpty() && pEditView->HasSelection() ) aWord = pTextEngine->GetWord( rSelStart ); } } return aWord; } void EditorWindow::RequestHelp( const HelpEvent& rHEvt ) { bool bDone = false; // Should have been activated at some point if ( pEditEngine ) { if ( rHEvt.GetMode() & HelpEventMode::CONTEXT ) { OUString aKeyword = GetWordAtCursor(); Application::GetHelp()->SearchKeyword( aKeyword ); bDone = true; } else if ( rHEvt.GetMode() & HelpEventMode::QUICK ) { OUString aHelpText; tools::Rectangle aHelpRect; if ( StarBASIC::IsRunning() ) { Point aWindowPos = rHEvt.GetMousePosPixel(); aWindowPos = ScreenToOutputPixel( aWindowPos ); Point aDocPos = GetEditView()->GetDocPos( aWindowPos ); TextPaM aCursor = GetEditView()->GetTextEngine()->GetPaM(aDocPos); TextPaM aStartOfWord; OUString aWord = GetEditView()->GetTextEngine()->GetWord( aCursor, &aStartOfWord ); if ( !aWord.isEmpty() && !comphelper::string::isdigitAsciiString(aWord) ) { sal_uInt16 nLastChar = aWord.getLength() - 1; if ( cSuffixes.find(aWord[ nLastChar ] ) != std::u16string_view::npos ) aWord = aWord.replaceAt( nLastChar, 1, u"" ); SbxBase* pSBX = StarBASIC::FindSBXInCurrentScope( aWord ); if (SbxVariable const* pVar = IsSbxVariable(pSBX)) { SbxDataType eType = pVar->GetType(); if ( static_cast(eType) == sal_uInt8(SbxOBJECT) ) // might cause a crash e. g. at the selections-object // Type == Object does not mean pVar == Object! ; // aHelpText = ((SbxObject*)pVar)->GetClassName(); else if ( eType & SbxARRAY ) ; // aHelpText = "{...}"; else if ( static_cast(eType) != sal_uInt8(SbxEMPTY) ) { aHelpText = pVar->GetName(); if ( aHelpText.isEmpty() ) // name is not copied with the passed parameters aHelpText = aWord; aHelpText += "=" + pVar->GetOUString(); } } if ( !aHelpText.isEmpty() ) { tools::Rectangle aStartWordRect(GetEditView()->GetTextEngine()->PaMtoEditCursor(aStartOfWord)); TextPaM aEndOfWord(aStartOfWord.GetPara(), aStartOfWord.GetIndex() + aWord.getLength()); tools::Rectangle aEndWordRect(GetEditView()->GetTextEngine()->PaMtoEditCursor(aEndOfWord)); aHelpRect = aStartWordRect.GetUnion(aEndWordRect); Point aTopLeft = GetEditView()->GetWindowPos(aHelpRect.TopLeft()); aTopLeft = GetEditView()->GetWindow()->OutputToScreenPixel(aTopLeft); aHelpRect.SetPos(aTopLeft); } } } Help::ShowQuickHelp( this, aHelpRect, aHelpText, QuickHelpFlags::NONE); bDone = true; } } if ( !bDone ) Window::RequestHelp( rHEvt ); } void EditorWindow::Resize() { // ScrollBars, etc. happens in Adjust... if ( !pEditView ) return; tools::Long nVisY = pEditView->GetStartDocPos().Y(); pEditView->ShowCursor(); Size aOutSz( GetOutputSizePixel() ); tools::Long nMaxVisAreaStart = pEditView->GetTextEngine()->GetTextHeight() - aOutSz.Height(); if ( nMaxVisAreaStart < 0 ) nMaxVisAreaStart = 0; if ( pEditView->GetStartDocPos().Y() > nMaxVisAreaStart ) { Point aStartDocPos( pEditView->GetStartDocPos() ); aStartDocPos.setY( nMaxVisAreaStart ); pEditView->SetStartDocPos( aStartDocPos ); pEditView->ShowCursor(); rModulWindow.GetBreakPointWindow().GetCurYOffset() = aStartDocPos.Y(); rModulWindow.GetLineNumberWindow().GetCurYOffset() = aStartDocPos.Y(); } InitScrollBars(); if ( nVisY != pEditView->GetStartDocPos().Y() ) Invalidate(); } void EditorWindow::MouseMove( const MouseEvent &rEvt ) { if ( pEditView ) pEditView->MouseMove( rEvt ); } void EditorWindow::MouseButtonUp( const MouseEvent &rEvt ) { if ( pEditView ) { pEditView->MouseButtonUp( rEvt ); if (SfxBindings* pBindings = GetBindingsPtr()) { pBindings->Invalidate( SID_BASICIDE_STAT_POS ); pBindings->Invalidate( SID_BASICIDE_STAT_TITLE ); } } } void EditorWindow::MouseButtonDown( const MouseEvent &rEvt ) { GrabFocus(); if (!pEditView) return; pEditView->MouseButtonDown(rEvt); if( pCodeCompleteWnd->IsVisible() ) { if (pEditView->GetSelection() != pCodeCompleteWnd->GetTextSelection()) { //selection changed, code complete window should be hidden pCodeCompleteWnd->HideAndRestoreFocus(); } } } void EditorWindow::Command( const CommandEvent& rCEvt ) { if ( !pEditView ) return; pEditView->Command( rCEvt ); if ( ( rCEvt.GetCommand() == CommandEventId::Wheel ) || ( rCEvt.GetCommand() == CommandEventId::StartAutoScroll ) || ( rCEvt.GetCommand() == CommandEventId::AutoScroll ) ) { const CommandWheelData* pData = rCEvt.GetWheelData(); // Check if it is a Ctrl+Wheel zoom command if (pData && pData->IsMod1()) { const sal_uInt16 nOldZoom = GetCurrentZoom(); sal_uInt16 nNewZoom; if( pData->GetDelta() < 0 ) nNewZoom = std::max(basctl::Shell::GetMinZoom(), basegfx::zoomtools::zoomOut(nOldZoom)); else nNewZoom = std::min(basctl::Shell::GetMaxZoom(), basegfx::zoomtools::zoomIn(nOldZoom)); GetShell()->SetGlobalEditorZoomLevel(nNewZoom); } else HandleScrollCommand(rCEvt, &rModulWindow.GetEditHScrollBar(), &rModulWindow.GetEditVScrollBar()); } else if ( rCEvt.GetCommand() == CommandEventId::ContextMenu ) { SfxDispatcher* pDispatcher = GetDispatcher(); if ( pDispatcher ) { SfxDispatcher::ExecutePopup(); } if( pCodeCompleteWnd->IsVisible() ) // hide the code complete window pCodeCompleteWnd->ClearAndHide(); } } bool EditorWindow::ImpCanModify() { bool bCanModify = true; if ( StarBASIC::IsRunning() && rModulWindow.GetBasicStatus().bIsRunning ) { // If in Trace-mode, abort the trace or refuse input // Remove markers in the modules in Notify at Basic::Stopped std::unique_ptr xQueryBox(Application::CreateMessageDialog(nullptr, VclMessageType::Question, VclButtonsType::OkCancel, IDEResId(RID_STR_WILLSTOPPRG))); if (xQueryBox->run() == RET_OK) { rModulWindow.GetBasicStatus().bIsRunning = false; StopBasic(); } else bCanModify = false; } return bCanModify; } void EditorWindow::KeyInput( const KeyEvent& rKEvt ) { if ( !pEditView ) // Happens in Win95 return; bool const bWasModified = pEditEngine->IsModified(); // see if there is an accelerator to be processed first SfxViewShell *pVS( SfxViewShell::Current()); bool bDone = pVS && pVS->KeyInput( rKEvt ); if (pCodeCompleteWnd->IsVisible() && CodeCompleteOptions::IsCodeCompleteOn()) { if (pCodeCompleteWnd->HandleKeyInput(rKEvt)) return; } if( (rKEvt.GetKeyCode().GetCode() == KEY_SPACE || rKEvt.GetKeyCode().GetCode() == KEY_TAB || rKEvt.GetKeyCode().GetCode() == KEY_RETURN ) && CodeCompleteOptions::IsAutoCorrectOn() ) { HandleAutoCorrect(); } if( rKEvt.GetCharCode() == '"' && CodeCompleteOptions::IsAutoCloseQuotesOn() ) {//autoclose double quotes HandleAutoCloseDoubleQuotes(); } if( rKEvt.GetCharCode() == '(' && CodeCompleteOptions::IsAutoCloseParenthesisOn() ) {//autoclose parenthesis HandleAutoCloseParen(); } if( rKEvt.GetKeyCode().GetCode() == KEY_RETURN && CodeCompleteOptions::IsProcedureAutoCompleteOn() ) {//autoclose implementation HandleProcedureCompletion(); } if( rKEvt.GetKeyCode().GetCode() == KEY_POINT && CodeCompleteOptions::IsCodeCompleteOn() ) { HandleCodeCompletion(); } if ( !bDone && ( !TextEngine::DoesKeyChangeText( rKEvt ) || ImpCanModify() ) ) { if ( ( rKEvt.GetKeyCode().GetCode() == KEY_TAB ) && !rKEvt.GetKeyCode().IsMod1() && !rKEvt.GetKeyCode().IsMod2() && !GetEditView()->IsReadOnly() ) { TextSelection aSel( pEditView->GetSelection() ); if ( aSel.GetStart().GetPara() != aSel.GetEnd().GetPara() ) { bDelayHighlight = false; if ( !rKEvt.GetKeyCode().IsShift() ) pEditView->IndentBlock(); else pEditView->UnindentBlock(); bDelayHighlight = true; bDone = true; } } if ( !bDone ) bDone = pEditView->KeyInput( rKEvt ); } if ( !bDone ) { Window::KeyInput( rKEvt ); } else { if (SfxBindings* pBindings = GetBindingsPtr()) { pBindings->Invalidate( SID_BASICIDE_STAT_POS ); pBindings->Invalidate( SID_BASICIDE_STAT_TITLE ); if ( rKEvt.GetKeyCode().GetGroup() == KEYGROUP_CURSOR ) { pBindings->Update( SID_BASICIDE_STAT_POS ); pBindings->Update( SID_BASICIDE_STAT_TITLE ); } if ( rKEvt.GetKeyCode().GetGroup() == KEYGROUP_ALPHA || rKEvt.GetKeyCode().GetGroup() == KEYGROUP_NUM ) { // If the module is read-only, warn that it can't be edited if ( rModulWindow.IsReadOnly() ) rModulWindow.ShowReadOnlyInfoBar(); } if ( !bWasModified && pEditEngine->IsModified() ) { pBindings->Invalidate( SID_SAVEDOC ); pBindings->Invalidate( SID_DOC_MODIFIED ); pBindings->Invalidate( SID_UNDO ); } if ( rKEvt.GetKeyCode().GetCode() == KEY_INSERT ) pBindings->Invalidate( SID_ATTR_INSERT ); } } } void EditorWindow::HandleAutoCorrect() { TextSelection aSel = GetEditView()->GetSelection(); const sal_uInt32 nLine = aSel.GetStart().GetPara(); const sal_Int32 nIndex = aSel.GetStart().GetIndex(); OUString aLine( pEditEngine->GetText( nLine ) ); // the line being modified const OUString& sActSubName = GetActualSubName( nLine ); // the actual procedure std::vector aPortions; aHighlighter.getHighlightPortions( aLine, aPortions ); if( aPortions.empty() ) return; HighlightPortion& r = aPortions.back(); if( static_cast(nIndex) != aPortions.size()-1 ) {//cursor is not standing at the end of the line for (auto const& portion : aPortions) { if( portion.nEnd == nIndex ) { r = portion; break; } } } OUString sStr = aLine.copy( r.nBegin, r.nEnd - r.nBegin ); //if WS or empty string: stop, nothing to do if( ( r.tokenType == TokenType::Whitespace ) || sStr.isEmpty() ) return; //create the appropriate TextSelection, and update the cache TextPaM aStart( nLine, r.nBegin ); TextPaM aEnd( nLine, r.nBegin + sStr.getLength() ); TextSelection sTextSelection( aStart, aEnd ); rModulWindow.UpdateModule(); rModulWindow.GetSbModule()->GetCodeCompleteDataFromParse( aCodeCompleteCache ); // correct the last entered keyword if( r.tokenType == TokenType::Keywords ) { sStr = sStr.toAsciiLowerCase(); if( !SbModule::GetKeywordCase(sStr).isEmpty() ) // if it is a keyword, get its correct case sStr = SbModule::GetKeywordCase(sStr); else // else capitalize first letter/select the correct one, and replace sStr = sStr.replaceAt( 0, 1, OUString(sStr[0]).toAsciiUpperCase() ); pEditEngine->ReplaceText( sTextSelection, sStr ); pEditView->SetSelection( aSel ); } if( r.tokenType != TokenType::Identifier ) return; // correct variables if( !aCodeCompleteCache.GetCorrectCaseVarName( sStr, sActSubName ).isEmpty() ) { sStr = aCodeCompleteCache.GetCorrectCaseVarName( sStr, sActSubName ); pEditEngine->ReplaceText( sTextSelection, sStr ); pEditView->SetSelection( aSel ); } else { //autocorrect procedures SbxArray* pArr = rModulWindow.GetSbModule()->GetMethods().get(); for (sal_uInt32 i = 0; i < pArr->Count(); ++i) { if (pArr->Get(i)->GetName().equalsIgnoreAsciiCase(sStr)) { sStr = pArr->Get(i)->GetName(); //if found, get the correct case pEditEngine->ReplaceText( sTextSelection, sStr ); pEditView->SetSelection( aSel ); return; } } } } TextSelection EditorWindow::GetLastHighlightPortionTextSelection() const {//creates a text selection from the highlight portion on the cursor const sal_uInt32 nLine = GetEditView()->GetSelection().GetStart().GetPara(); const sal_Int32 nIndex = GetEditView()->GetSelection().GetStart().GetIndex(); OUString aLine( pEditEngine->GetText( nLine ) ); // the line being modified std::vector aPortions; aHighlighter.getHighlightPortions( aLine, aPortions ); assert(!aPortions.empty()); HighlightPortion& r = aPortions.back(); if( static_cast(nIndex) != aPortions.size()-1 ) {//cursor is not standing at the end of the line for (auto const& portion : aPortions) { if( portion.nEnd == nIndex ) { r = portion; break; } } } if( aPortions.empty() ) return TextSelection(); std::u16string_view sStr = aLine.subView( r.nBegin, r.nEnd - r.nBegin ); TextPaM aStart( nLine, r.nBegin ); TextPaM aEnd( nLine, r.nBegin + sStr.size() ); return TextSelection( aStart, aEnd ); } void EditorWindow::HandleAutoCloseParen() { TextSelection aSel = GetEditView()->GetSelection(); const sal_uInt32 nLine = aSel.GetStart().GetPara(); OUString aLine( pEditEngine->GetText( nLine ) ); // the line being modified if( aLine.getLength() > 0 && aLine[aSel.GetEnd().GetIndex()-1] != '(' ) { GetEditView()->InsertText(")"); //leave the cursor on its place: inside the parenthesis TextPaM aEnd(nLine, aSel.GetEnd().GetIndex()); GetEditView()->SetSelection( TextSelection( aEnd, aEnd ) ); } } void EditorWindow::HandleAutoCloseDoubleQuotes() { TextSelection aSel = GetEditView()->GetSelection(); const sal_uInt32 nLine = aSel.GetStart().GetPara(); OUString aLine( pEditEngine->GetText( nLine ) ); // the line being modified std::vector aPortions; aHighlighter.getHighlightPortions( aLine, aPortions ); if( aPortions.empty() ) return; if( aLine.getLength() > 0 && !aLine.endsWith("\"") && (aPortions.back().tokenType != TokenType::String) ) { GetEditView()->InsertText("\""); //leave the cursor on its place: inside the two double quotes TextPaM aEnd(nLine, aSel.GetEnd().GetIndex()); GetEditView()->SetSelection( TextSelection( aEnd, aEnd ) ); } } void EditorWindow::HandleProcedureCompletion() { TextSelection aSel = GetEditView()->GetSelection(); const sal_uInt32 nLine = aSel.GetStart().GetPara(); OUString aLine( pEditEngine->GetText( nLine ) ); OUString sProcType; OUString sProcName; bool bFoundName = GetProcedureName(aLine, sProcType, sProcName); if (!bFoundName) return; OUString sText("\nEnd "); aSel = GetEditView()->GetSelection(); if( sProcType.equalsIgnoreAsciiCase("function") ) sText += "Function\n"; if( sProcType.equalsIgnoreAsciiCase("sub") ) sText += "Sub\n"; if( nLine+1 == pEditEngine->GetParagraphCount() ) { pEditView->InsertText( sText );//append to the end GetEditView()->SetSelection(aSel); } else { for( sal_uInt32 i = nLine+1; i < pEditEngine->GetParagraphCount(); ++i ) {//searching forward for end token, or another sub/function definition OUString aCurrLine = pEditEngine->GetText( i ); std::vector aCurrPortions; aHighlighter.getHighlightPortions( aCurrLine, aCurrPortions ); if( aCurrPortions.size() >= 3 ) {//at least 3 tokens: (sub|function) whitespace identifier... HighlightPortion& r = aCurrPortions.front(); std::u16string_view sStr = aCurrLine.subView(r.nBegin, r.nEnd - r.nBegin); if( r.tokenType == TokenType::Keywords ) { if( o3tl::equalsIgnoreAsciiCase(sStr, u"sub") || o3tl::equalsIgnoreAsciiCase(sStr, u"function") ) { pEditView->InsertText( sText );//append to the end GetEditView()->SetSelection(aSel); break; } if( o3tl::equalsIgnoreAsciiCase(sStr, u"end") ) break; } } } } } bool EditorWindow::GetProcedureName(std::u16string_view rLine, OUString& rProcType, OUString& rProcName) const { std::vector aPortions; aHighlighter.getHighlightPortions(rLine, aPortions); if( aPortions.empty() ) return false; bool bFoundType = false; bool bFoundName = false; for (auto const& portion : aPortions) { std::u16string_view sTokStr = rLine.substr(portion.nBegin, portion.nEnd - portion.nBegin); if( portion.tokenType == TokenType::Keywords && ( o3tl::equalsIgnoreAsciiCase(sTokStr, u"sub") || o3tl::equalsIgnoreAsciiCase(sTokStr, u"function")) ) { rProcType = sTokStr; bFoundType = true; } if( portion.tokenType == TokenType::Identifier && bFoundType ) { rProcName = sTokStr; bFoundName = true; break; } } if( !bFoundType || !bFoundName ) return false;// no sub/function keyword or there is no identifier return true; } void EditorWindow::HandleCodeCompletion() { rModulWindow.UpdateModule(); rModulWindow.GetSbModule()->GetCodeCompleteDataFromParse(aCodeCompleteCache); TextSelection aSel = GetEditView()->GetSelection(); const sal_uInt32 nLine = aSel.GetStart().GetPara(); OUString aLine( pEditEngine->GetText( nLine ) ); // the line being modified std::vector< OUString > aVect; //vector to hold the base variable+methods for the nested reflection std::vector aPortions; aLine = aLine.copy(0, aSel.GetEnd().GetIndex()); aHighlighter.getHighlightPortions( aLine, aPortions ); if( aPortions.empty() ) return; //use the syntax highlighter to grab out nested reflection calls, eg. aVar.aMethod("aa").aOtherMethod .. for( std::vector::reverse_iterator i( aPortions.rbegin()); i != aPortions.rend(); ++i) { if( i->tokenType == TokenType::Whitespace ) // a whitespace: stop; if there is no ws, it goes to the beginning of the line break; if( i->tokenType == TokenType::Identifier || i->tokenType == TokenType::Keywords ) // extract the identifiers(methods, base variable) /* an example: Dim aLocVar2 as com.sun.star.beans.PropertyValue * here, aLocVar2.Name, and PropertyValue's Name field is treated as a keyword(?!) * */ aVect.insert( aVect.begin(), aLine.copy(i->nBegin, i->nEnd - i->nBegin) ); } if( aVect.empty() )//nothing to do return; OUString sBaseName = aVect[aVect.size()-1];//variable name OUString sVarType = aCodeCompleteCache.GetVarType( sBaseName ); if( !sVarType.isEmpty() && CodeCompleteOptions::IsAutoCorrectOn() ) {//correct variable name, if autocorrection on const OUString& sStr = aCodeCompleteCache.GetCorrectCaseVarName( sBaseName, GetActualSubName(nLine) ); if( !sStr.isEmpty() ) { TextPaM aStart(nLine, aSel.GetStart().GetIndex() - sStr.getLength() ); TextSelection sTextSelection(aStart, TextPaM(nLine, aSel.GetStart().GetIndex())); pEditEngine->ReplaceText( sTextSelection, sStr ); pEditView->SetSelection( aSel ); } } UnoTypeCodeCompletetor aTypeCompletor( aVect, sVarType ); if( !aTypeCompletor.CanCodeComplete() ) return; std::vector< OUString > aEntryVect;//entries to be inserted into the list std::vector< OUString > aFieldVect = aTypeCompletor.GetXIdlClassFields();//fields aEntryVect.insert(aEntryVect.end(), aFieldVect.begin(), aFieldVect.end() ); if( CodeCompleteOptions::IsExtendedTypeDeclaration() ) {// if extended types on, reflect classes, else just the structs (XIdlClass without methods) std::vector< OUString > aMethVect = aTypeCompletor.GetXIdlClassMethods();//methods aEntryVect.insert(aEntryVect.end(), aMethVect.begin(), aMethVect.end() ); } if( !aEntryVect.empty() ) SetupAndShowCodeCompleteWnd( aEntryVect, aSel ); } void EditorWindow::SetupAndShowCodeCompleteWnd( const std::vector< OUString >& aEntryVect, TextSelection aSel ) { // clear the listbox pCodeCompleteWnd->ClearListBox(); // fill the listbox for(const auto & l : aEntryVect) { pCodeCompleteWnd->InsertEntry( l ); } // show it pCodeCompleteWnd->Show(); pCodeCompleteWnd->ResizeAndPositionListBox(); pCodeCompleteWnd->SelectFirstEntry(); // correct text selection, and set it ++aSel.GetStart().GetIndex(); ++aSel.GetEnd().GetIndex(); pCodeCompleteWnd->SetTextSelection( aSel ); //give the focus to the EditView pEditView->GetWindow()->GrabFocus(); } void EditorWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) { if (!pEditEngine) // We need it now at latest CreateEditEngine(); pEditView->Paint(rRenderContext, rRect); } void EditorWindow::LoseFocus() { // tdf#114258 wait until the next event loop cycle to do this so it doesn't // happen during a mouse down/up selection in the treeview whose contents // this may update if (!m_nSetSourceInBasicId) m_nSetSourceInBasicId = Application::PostUserEvent(LINK(this, EditorWindow, SetSourceInBasicHdl)); Window::LoseFocus(); } IMPL_LINK_NOARG(EditorWindow, SetSourceInBasicHdl, void*, void) { m_nSetSourceInBasicId = nullptr; SetSourceInBasic(); } void EditorWindow::SetSourceInBasic() { if ( pEditEngine && pEditEngine->IsModified() && !GetEditView()->IsReadOnly() ) // Added for #i60626, otherwise // any read only bug in the text engine could lead to a crash later { if ( !StarBASIC::IsRunning() ) // Not at runtime! { rModulWindow.UpdateModule(); } } } // Returns the position of the last character of any of the following // EOL char combinations: CR, CR/LF, LF, return -1 if no EOL is found sal_Int32 searchEOL( std::u16string_view rStr, sal_Int32 fromIndex ) { size_t iLF = rStr.find( LINE_SEP, fromIndex ); if( iLF != std::u16string_view::npos ) return iLF; size_t iCR = rStr.find( LINE_SEP_CR, fromIndex ); return iCR == std::u16string_view::npos ? -1 : iCR; } void EditorWindow::CreateEditEngine() { if (pEditEngine) return; pEditEngine.reset(new ExtTextEngine); pEditView.reset(new TextView(pEditEngine.get(), this)); pEditView->SetAutoIndentMode(true); pEditEngine->SetUpdateMode(false); pEditEngine->InsertView(pEditView.get()); ImplSetFont(); aSyntaxIdle.SetInvokeHandler( LINK( this, EditorWindow, SyntaxTimerHdl ) ); bool bWasDoSyntaxHighlight = bDoSyntaxHighlight; bDoSyntaxHighlight = false; // too slow for large texts... OUString aOUSource(rModulWindow.GetModule()); sal_Int32 nLines = 0; sal_Int32 nIndex = -1; do { nLines++; nIndex = searchEOL( aOUSource, nIndex+1 ); } while (nIndex >= 0); // nLines*4: SetText+Formatting+DoHighlight+Formatting // it could be cut down on one formatting but you would wait even longer // for the text then if the source code is long... pProgress.reset(new ProgressInfo(GetShell()->GetViewFrame().GetObjectShell(), IDEResId(RID_STR_GENERATESOURCE), nLines * 4)); setTextEngineText(*pEditEngine, aOUSource); pEditView->SetStartDocPos(Point(0, 0)); pEditView->SetSelection(TextSelection()); rModulWindow.GetBreakPointWindow().GetCurYOffset() = 0; rModulWindow.GetLineNumberWindow().GetCurYOffset() = 0; pEditEngine->SetUpdateMode(true); rModulWindow.PaintImmediately(); // has only been invalidated at UpdateMode = true pEditView->ShowCursor(); StartListening(*pEditEngine); aSyntaxIdle.Stop(); bDoSyntaxHighlight = bWasDoSyntaxHighlight; for (sal_Int32 nLine = 0; nLine < nLines; nLine++) aSyntaxLineTable.insert(nLine); ForceSyntaxTimeout(); pProgress.reset(); pEditEngine->SetModified( false ); pEditEngine->EnableUndo( true ); InitScrollBars(); if (SfxBindings* pBindings = GetBindingsPtr()) { pBindings->Invalidate(SID_BASICIDE_STAT_POS); pBindings->Invalidate(SID_BASICIDE_STAT_TITLE); } DBG_ASSERT(rModulWindow.GetBreakPointWindow().GetCurYOffset() == 0, "CreateEditEngine: breakpoints moved?"); // set readonly mode for readonly libraries ScriptDocument aDocument(rModulWindow.GetDocument()); OUString aOULibName(rModulWindow.GetLibName()); Reference< script::XLibraryContainer2 > xModLibContainer( aDocument.getLibraryContainer( E_SCRIPTS ), UNO_QUERY ); if (xModLibContainer.is() && xModLibContainer->hasByName(aOULibName) && xModLibContainer->isLibraryReadOnly(aOULibName)) { rModulWindow.SetReadOnly(true); } if (aDocument.isDocument() && aDocument.isReadOnly()) rModulWindow.SetReadOnly(true); } void EditorWindow::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) { TextHint const* pTextHint = dynamic_cast(&rHint); if (!pTextHint) return; TextHint const& rTextHint = *pTextHint; if( rTextHint.GetId() == SfxHintId::TextViewScrolled ) { rModulWindow.GetEditVScrollBar().SetThumbPos( pEditView->GetStartDocPos().Y() ); rModulWindow.GetEditHScrollBar().SetThumbPos( pEditView->GetStartDocPos().X() ); rModulWindow.GetBreakPointWindow().DoScroll ( rModulWindow.GetBreakPointWindow().GetCurYOffset() - pEditView->GetStartDocPos().Y() ); rModulWindow.GetLineNumberWindow().DoScroll ( rModulWindow.GetLineNumberWindow().GetCurYOffset() - pEditView->GetStartDocPos().Y() ); } else if( rTextHint.GetId() == SfxHintId::TextHeightChanged ) { if ( pEditView->GetStartDocPos().Y() ) { tools::Long nOutHeight = GetOutputSizePixel().Height(); tools::Long nTextHeight = pEditEngine->GetTextHeight(); if ( nTextHeight < nOutHeight ) pEditView->Scroll( 0, pEditView->GetStartDocPos().Y() ); rModulWindow.GetLineNumberWindow().Invalidate(); } SetScrollBarRanges(); } else if( rTextHint.GetId() == SfxHintId::TextFormatted ) { const tools::Long nWidth = pEditEngine->CalcTextWidth(); if ( nWidth != nCurTextWidth ) { nCurTextWidth = nWidth; rModulWindow.GetEditHScrollBar().SetRange( Range( 0, nCurTextWidth-1) ); rModulWindow.GetEditHScrollBar().SetThumbPos( pEditView->GetStartDocPos().X() ); } tools::Long nPrevTextWidth = nCurTextWidth; nCurTextWidth = pEditEngine->CalcTextWidth(); if ( nCurTextWidth != nPrevTextWidth ) SetScrollBarRanges(); } else if( rTextHint.GetId() == SfxHintId::TextParaInserted ) { ParagraphInsertedDeleted( rTextHint.GetValue(), true ); DoDelayedSyntaxHighlight( rTextHint.GetValue() ); } else if( rTextHint.GetId() == SfxHintId::TextParaRemoved ) { ParagraphInsertedDeleted( rTextHint.GetValue(), false ); } else if( rTextHint.GetId() == SfxHintId::TextParaContentChanged ) { DoDelayedSyntaxHighlight( rTextHint.GetValue() ); } else if( rTextHint.GetId() == SfxHintId::TextViewSelectionChanged ) { if (SfxBindings* pBindings = GetBindingsPtr()) { pBindings->Invalidate( SID_CUT ); pBindings->Invalidate( SID_COPY ); } } } OUString EditorWindow::GetActualSubName( sal_uInt32 nLine ) { SbxArrayRef pMethods = rModulWindow.GetSbModule()->GetMethods(); for (sal_uInt32 i = 0; i < pMethods->Count(); i++) { SbMethod* pMeth = dynamic_cast(pMethods->Get(i)); if( pMeth ) { sal_uInt16 l1,l2; pMeth->GetLineRange(l1,l2); if( (l1 <= nLine+1) && (nLine+1 <= l2) ) { return pMeth->GetName(); } } } return OUString(); } void EditorWindow::SetScrollBarRanges() { // extra method, not InitScrollBars, because for EditEngine events too if ( !pEditEngine ) return; rModulWindow.GetEditVScrollBar().SetRange( Range( 0, pEditEngine->GetTextHeight()-1 ) ); rModulWindow.GetEditHScrollBar().SetRange( Range( 0, nCurTextWidth-1 ) ); } void EditorWindow::InitScrollBars() { if (!pEditEngine) return; SetScrollBarRanges(); Size aOutSz(GetOutputSizePixel()); rModulWindow.GetEditVScrollBar().SetVisibleSize(aOutSz.Height()); rModulWindow.GetEditVScrollBar().SetPageSize(aOutSz.Height() * 8 / 10); rModulWindow.GetEditVScrollBar().SetLineSize(GetTextHeight()); rModulWindow.GetEditVScrollBar().SetThumbPos(pEditView->GetStartDocPos().Y()); rModulWindow.GetEditVScrollBar().Show(); rModulWindow.GetEditHScrollBar().SetVisibleSize(aOutSz.Width()); rModulWindow.GetEditHScrollBar().SetPageSize(aOutSz.Width() * 8 / 10); rModulWindow.GetEditHScrollBar().SetLineSize(GetTextWidth( "x" )); rModulWindow.GetEditHScrollBar().SetThumbPos(pEditView->GetStartDocPos().X()); rModulWindow.GetEditHScrollBar().Show(); } void EditorWindow::ImpDoHighlight( sal_uInt32 nLine ) { if ( !bDoSyntaxHighlight ) return; OUString aLine( pEditEngine->GetText( nLine ) ); bool const bWasModified = pEditEngine->IsModified(); pEditEngine->RemoveAttribs( nLine ); std::vector aPortions; aHighlighter.getHighlightPortions( aLine, aPortions ); for (auto const& portion : aPortions) { Color const aColor = rModulWindow.GetLayout().GetSyntaxColor(portion.tokenType); pEditEngine->SetAttrib(TextAttribFontColor(aColor), nLine, portion.nBegin, portion.nEnd); } pEditEngine->SetModified(bWasModified); } void EditorWindow::ChangeFontColor( Color aColor ) { if (pEditEngine) { vcl::Font aFont(pEditEngine->GetFont()); aFont.SetColor(aColor); pEditEngine->SetFont(aFont); } } void EditorWindow::UpdateSyntaxHighlighting () { const sal_uInt32 nCount = pEditEngine->GetParagraphCount(); for (sal_uInt32 i = 0; i < nCount; ++i) DoDelayedSyntaxHighlight(i); } void EditorWindow::ImplSetFont() { // Get default font name and height defined in the Options dialog OUString sFontName(officecfg::Office::Common::Font::SourceViewFont::FontName::get().value_or(OUString())); if (sFontName.isEmpty()) { vcl::Font aTmpFont(OutputDevice::GetDefaultFont(DefaultFontType::FIXED, Application::GetSettings().GetUILanguageTag().getLanguageType(), GetDefaultFontFlags::NONE, GetOutDev())); sFontName = aTmpFont.GetFamilyName(); } sal_uInt16 nDefaultFontHeight = officecfg::Office::Common::Font::SourceViewFont::FontHeight::get(); // Calculate font size considering zoom level sal_uInt16 nNewFontHeight = nDefaultFontHeight * (static_cast(nCurrentZoomLevel) / 100); Size aFontSize(0, nNewFontHeight); vcl::Font aFont(sFontName, aFontSize); aFont.SetColor(rModulWindow.GetLayout().GetFontColor()); SetPointFont(*GetOutDev(), aFont); // FIXME RenderContext aFont = GetFont(); rModulWindow.GetBreakPointWindow().SetFont(aFont); rModulWindow.GetLineNumberWindow().SetFont(aFont); rModulWindow.Invalidate(); if (pEditEngine) { bool const bModified = pEditEngine->IsModified(); pEditEngine->SetFont(aFont); pEditEngine->SetModified(bModified); } // Update controls if (SfxBindings* pBindings = GetBindingsPtr()) { pBindings->Invalidate( SID_BASICIDE_CURRENT_ZOOM ); pBindings->Invalidate( SID_ATTR_ZOOMSLIDER ); } } void EditorWindow::SetEditorZoomLevel(sal_uInt16 nNewZoomLevel) { if (nCurrentZoomLevel == nNewZoomLevel) return; if (nNewZoomLevel < MIN_ZOOM_LEVEL || nNewZoomLevel > MAX_ZOOM_LEVEL) return; nCurrentZoomLevel = nNewZoomLevel; ImplSetFont(); } void EditorWindow::DoSyntaxHighlight( sal_uInt32 nPara ) { // because of the DelayedSyntaxHighlight it's possible // that this line does not exist anymore! if ( nPara < pEditEngine->GetParagraphCount() ) { // unfortunately I'm not sure that exactly this line does Modified()... if ( pProgress ) pProgress->StepProgress(); ImpDoHighlight( nPara ); } } void EditorWindow::DoDelayedSyntaxHighlight( sal_uInt32 nPara ) { // line is only added to list, processed in TimerHdl // => don't manipulate breaks while EditEngine is formatting if ( pProgress ) pProgress->StepProgress(); if ( !bHighlighting && bDoSyntaxHighlight ) { if ( bDelayHighlight ) { aSyntaxLineTable.insert( nPara ); aSyntaxIdle.Start(); } else DoSyntaxHighlight( nPara ); } } IMPL_LINK_NOARG(EditorWindow, SyntaxTimerHdl, Timer *, void) { DBG_ASSERT( pEditView, "Not yet a View, but Syntax-Highlight?!" ); bool const bWasModified = pEditEngine->IsModified(); //pEditEngine->SetUpdateMode(false); bHighlighting = true; for (auto const& syntaxLine : aSyntaxLineTable) { DoSyntaxHighlight(syntaxLine); } // #i45572# if ( pEditView ) pEditView->ShowCursor( false ); pEditEngine->SetModified( bWasModified ); aSyntaxLineTable.clear(); bHighlighting = false; } void EditorWindow::ParagraphInsertedDeleted( sal_uInt32 nPara, bool bInserted ) { if ( pProgress ) pProgress->StepProgress(); if ( !bInserted && ( nPara == TEXT_PARA_ALL ) ) { rModulWindow.GetBreakPoints().reset(); rModulWindow.GetBreakPointWindow().Invalidate(); rModulWindow.GetLineNumberWindow().Invalidate(); } else { rModulWindow.GetBreakPoints().AdjustBreakPoints( static_cast(nPara)+1, bInserted ); tools::Long nLineHeight = GetTextHeight(); Size aSz = rModulWindow.GetBreakPointWindow().GetOutDev()->GetOutputSize(); tools::Rectangle aInvRect( Point( 0, 0 ), aSz ); tools::Long nY = nPara*nLineHeight - rModulWindow.GetBreakPointWindow().GetCurYOffset(); aInvRect.SetTop( nY ); rModulWindow.GetBreakPointWindow().Invalidate( aInvRect ); Size aLnSz(rModulWindow.GetLineNumberWindow().GetWidth(), GetOutputSizePixel().Height() - 2 * DWBORDER); rModulWindow.GetLineNumberWindow().SetPosSizePixel(Point(DWBORDER + 19, DWBORDER), aLnSz); rModulWindow.GetLineNumberWindow().Invalidate(); } } void EditorWindow::CreateProgress( const OUString& rText, sal_uInt32 nRange ) { DBG_ASSERT( !pProgress, "ProgressInfo exists already" ); pProgress.reset(new ProgressInfo( GetShell()->GetViewFrame().GetObjectShell(), rText, nRange )); } void EditorWindow::DestroyProgress() { pProgress.reset(); } void EditorWindow::ForceSyntaxTimeout() { aSyntaxIdle.Stop(); aSyntaxIdle.Invoke(); } FactoryFunction EditorWindow::GetUITestFactory() const { return EditorWindowUIObject::create; } // BreakPointWindow BreakPointWindow::BreakPointWindow (vcl::Window* pParent, ModulWindow* pModulWindow) : Window(pParent, WB_BORDER) , rModulWindow(*pModulWindow) , nCurYOffset(0) // memorize nCurYOffset and not take it from EditEngine , nMarkerPos(NoMarker) , bErrorMarker(false) { setBackgroundColor(GetSettings().GetStyleSettings().GetFieldColor()); SetHelpId(HID_BASICIDE_BREAKPOINTWINDOW); } void BreakPointWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) { if (SyncYOffset()) return; Size const aOutSz = rRenderContext.GetOutputSize(); tools::Long const nLineHeight = rRenderContext.GetTextHeight(); Image const aBrk[2] = { GetImage(RID_BMP_BRKDISABLED), GetImage(RID_BMP_BRKENABLED) }; Size const aBmpSz = rRenderContext.PixelToLogic(aBrk[1].GetSizePixel()); Point const aBmpOff((aOutSz.Width() - aBmpSz.Width()) / 2, (nLineHeight - aBmpSz.Height()) / 2); for (size_t i = 0, n = GetBreakPoints().size(); i < n; ++i) { BreakPoint& rBrk = GetBreakPoints().at(i); sal_uInt16 const nLine = rBrk.nLine - 1; size_t const nY = nLine*nLineHeight - nCurYOffset; rRenderContext.DrawImage(Point(0, nY) + aBmpOff, aBrk[rBrk.bEnabled]); } ShowMarker(rRenderContext); } void BreakPointWindow::ShowMarker(vcl::RenderContext& rRenderContext) { if (nMarkerPos == NoMarker) return; Size const aOutSz = GetOutDev()->GetOutputSize(); tools::Long const nLineHeight = GetTextHeight(); Image aMarker = GetImage(bErrorMarker ? OUString(RID_BMP_ERRORMARKER) : OUString(RID_BMP_STEPMARKER)); Size aMarkerSz(aMarker.GetSizePixel()); aMarkerSz = rRenderContext.PixelToLogic(aMarkerSz); Point aMarkerOff(0, 0); aMarkerOff.setX( (aOutSz.Width() - aMarkerSz.Width()) / 2 ); aMarkerOff.setY( (nLineHeight - aMarkerSz.Height()) / 2 ); tools::Long nY = nMarkerPos * nLineHeight - nCurYOffset; Point aPos(0, nY); aPos += aMarkerOff; rRenderContext.DrawImage(aPos, aMarker); } void BreakPointWindow::DoScroll( tools::Long nVertScroll ) { nCurYOffset -= nVertScroll; Window::Scroll( 0, nVertScroll ); } void BreakPointWindow::SetMarkerPos( sal_uInt16 nLine, bool bError ) { if ( SyncYOffset() ) PaintImmediately(); nMarkerPos = nLine; bErrorMarker = bError; Invalidate(); } void BreakPointWindow::SetNoMarker () { SetMarkerPos(NoMarker); } BreakPoint* BreakPointWindow::FindBreakPoint( const Point& rMousePos ) { size_t nLineHeight = GetTextHeight(); nLineHeight = nLineHeight > 0 ? nLineHeight : 1; size_t nYPos = rMousePos.Y() + nCurYOffset; for ( size_t i = 0, n = GetBreakPoints().size(); i < n ; ++i ) { BreakPoint& rBrk = GetBreakPoints().at( i ); sal_uInt16 nLine = rBrk.nLine-1; size_t nY = nLine*nLineHeight; if ( ( nYPos > nY ) && ( nYPos < ( nY + nLineHeight ) ) ) return &rBrk; } return nullptr; } void BreakPointWindow::MouseButtonDown( const MouseEvent& rMEvt ) { if ( rMEvt.GetClicks() == 2 ) { Point aMousePos( PixelToLogic( rMEvt.GetPosPixel() ) ); tools::Long nLineHeight = GetTextHeight(); if(nLineHeight) { tools::Long nYPos = aMousePos.Y() + nCurYOffset; tools::Long nLine = nYPos / nLineHeight + 1; rModulWindow.ToggleBreakPoint( static_cast(nLine) ); Invalidate(); } } } void BreakPointWindow::Command( const CommandEvent& rCEvt ) { if ( rCEvt.GetCommand() != CommandEventId::ContextMenu ) return; Point aPos( rCEvt.IsMouseEvent() ? rCEvt.GetMousePosPixel() : Point(1,1) ); tools::Rectangle aRect(aPos, Size(1, 1)); weld::Window* pPopupParent = weld::GetPopupParent(*this, aRect); std::unique_ptr xUIBuilder(Application::CreateBuilder(pPopupParent, "modules/BasicIDE/ui/breakpointmenus.ui")); Point aEventPos( PixelToLogic( aPos ) ); BreakPoint* pBrk = rCEvt.IsMouseEvent() ? FindBreakPoint( aEventPos ) : nullptr; if ( pBrk ) { // test if break point is enabled... std::unique_ptr xBrkPropMenu = xUIBuilder->weld_menu("breakmenu"); xBrkPropMenu->set_active("active", pBrk->bEnabled); OUString sCommand = xBrkPropMenu->popup_at_rect(pPopupParent, aRect); if (sCommand == "active") { pBrk->bEnabled = !pBrk->bEnabled; rModulWindow.UpdateBreakPoint( *pBrk ); Invalidate(); } else if (sCommand == "properties") { BreakPointDialog aBrkDlg(pPopupParent, GetBreakPoints()); aBrkDlg.SetCurrentBreakPoint( *pBrk ); aBrkDlg.run(); Invalidate(); } } else { std::unique_ptr xBrkListMenu = xUIBuilder->weld_menu("breaklistmenu"); OUString sCommand = xBrkListMenu->popup_at_rect(pPopupParent, aRect); if (sCommand == "manage") { BreakPointDialog aBrkDlg(pPopupParent, GetBreakPoints()); aBrkDlg.run(); Invalidate(); } } } bool BreakPointWindow::SyncYOffset() { TextView* pView = rModulWindow.GetEditView(); if ( pView ) { tools::Long nViewYOffset = pView->GetStartDocPos().Y(); if ( nCurYOffset != nViewYOffset ) { nCurYOffset = nViewYOffset; Invalidate(); return true; } } return false; } // virtual void BreakPointWindow::DataChanged(DataChangedEvent const & rDCEvt) { Window::DataChanged(rDCEvt); if (rDCEvt.GetType() == DataChangedEventType::SETTINGS && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) { Color aColor(GetSettings().GetStyleSettings().GetFieldColor()); const AllSettings* pOldSettings = rDCEvt.GetOldSettings(); if (!pOldSettings || aColor != pOldSettings->GetStyleSettings().GetFieldColor()) { setBackgroundColor(aColor); Invalidate(); } } } void BreakPointWindow::setBackgroundColor(Color aColor) { SetBackground(Wallpaper(aColor)); } namespace { struct WatchItem { OUString maName; OUString maDisplayName; SbxObjectRef mpObject; std::vector maMemberList; SbxDimArrayRef mpArray; int nDimLevel; // 0 = Root int nDimCount; std::vector vIndices; WatchItem* mpArrayParentItem; explicit WatchItem (OUString aName): maName(std::move(aName)), nDimLevel(0), nDimCount(0), mpArrayParentItem(nullptr) { } void clearWatchItem () { maMemberList.clear(); } WatchItem* GetRootItem(); SbxDimArray* GetRootArray(); }; } WatchWindow::WatchWindow(Layout* pParent) : DockingWindow(pParent, "modules/BasicIDE/ui/dockingwatch.ui", "DockingWatch") , m_nUpdateWatchesId(nullptr) { m_xTitleArea = m_xBuilder->weld_container("titlearea"); nVirtToolBoxHeight = m_xTitleArea->get_preferred_size().Height(); m_xTitle = m_xBuilder->weld_label("title"); m_xTitle->set_label(IDEResId(RID_STR_REMOVEWATCH)); m_xEdit = m_xBuilder->weld_entry("edit"); m_xRemoveWatchButton = m_xBuilder->weld_button("remove"); m_xTreeListBox = m_xBuilder->weld_tree_view("treeview"); m_xEdit->set_accessible_name(IDEResId(RID_STR_WATCHNAME)); m_xEdit->set_help_id(HID_BASICIDE_WATCHWINDOW_EDIT); m_xEdit->set_size_request(LogicToPixel(Size(80, 0), MapMode(MapUnit::MapAppFont)).Width(), -1); m_xEdit->connect_activate(LINK( this, WatchWindow, ActivateHdl)); m_xEdit->connect_key_press(LINK( this, WatchWindow, KeyInputHdl)); m_xTreeListBox->set_accessible_name(IDEResId(RID_STR_WATCHNAME)); m_xRemoveWatchButton->set_sensitive(false); m_xRemoveWatchButton->connect_clicked(LINK( this, WatchWindow, ButtonHdl)); m_xRemoveWatchButton->set_help_id(HID_BASICIDE_REMOVEWATCH); m_xRemoveWatchButton->set_tooltip_text(IDEResId(RID_STR_REMOVEWATCHTIP)); m_xTreeListBox->set_help_id(HID_BASICIDE_WATCHWINDOW_LIST); m_xTreeListBox->connect_editing(LINK(this, WatchWindow, EditingEntryHdl), LINK(this, WatchWindow, EditedEntryHdl)); m_xTreeListBox->connect_changed( LINK( this, WatchWindow, TreeListHdl ) ); m_xTreeListBox->connect_expanding(LINK(this, WatchWindow, RequestingChildrenHdl)); // VarTabWidth, ValueTabWidth, TypeTabWidth std::vector aWidths { 220, 100, 1250 }; std::vector aEditables { false, true, false }; m_xTreeListBox->set_column_fixed_widths(aWidths); m_xTreeListBox->set_column_editables(aEditables); SetText(IDEResId(RID_STR_WATCHNAME)); SetHelpId( HID_BASICIDE_WATCHWINDOW ); // make watch window keyboard accessible GetSystemWindow()->GetTaskPaneList()->AddWindow( this ); } WatchWindow::~WatchWindow() { disposeOnce(); } void WatchWindow::dispose() { if (m_nUpdateWatchesId) { Application::RemoveUserEvent(m_nUpdateWatchesId); m_nUpdateWatchesId = nullptr; } // Destroy user data m_xTreeListBox->all_foreach([this](weld::TreeIter& rEntry){ WatchItem* pItem = weld::fromId(m_xTreeListBox->get_id(rEntry)); delete pItem; return false; }); m_xTitle.reset(); m_xEdit.reset(); m_xRemoveWatchButton.reset(); m_xTitleArea.reset(); m_xTreeListBox.reset(); GetSystemWindow()->GetTaskPaneList()->RemoveWindow( this ); DockingWindow::dispose(); } void WatchWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) { lcl_DrawIDEWindowFrame(this, rRenderContext); } void WatchWindow::Resize() { Size aSz = GetOutputSizePixel(); Size aBoxSz(aSz.Width() - 2*DWBORDER, aSz.Height() - 2*DWBORDER); if ( aBoxSz.Width() < 4 ) aBoxSz.setWidth( 0 ); if ( aBoxSz.Height() < 4 ) aBoxSz.setHeight( 0 ); m_xBox->SetPosSizePixel(Point(DWBORDER, DWBORDER), aBoxSz); Invalidate(); } WatchItem* WatchItem::GetRootItem() { WatchItem* pItem = mpArrayParentItem; while( pItem ) { if( pItem->mpArray.is() ) break; pItem = pItem->mpArrayParentItem; } return pItem; } SbxDimArray* WatchItem::GetRootArray() { WatchItem* pRootItem = GetRootItem(); SbxDimArray* pRet = nullptr; if( pRootItem ) pRet = pRootItem->mpArray.get(); return pRet; } void WatchWindow::AddWatch( const OUString& rVName ) { OUString aVar, aIndex; lcl_SeparateNameAndIndex( rVName, aVar, aIndex ); WatchItem* pWatchItem = new WatchItem(aVar); OUString sId(weld::toId(pWatchItem)); std::unique_ptr xRet = m_xTreeListBox->make_iterator(); m_xTreeListBox->insert(nullptr, -1, &aVar, &sId, nullptr, nullptr, false, xRet.get()); m_xTreeListBox->set_text(*xRet, "", 1); m_xTreeListBox->set_text(*xRet, "", 2); m_xTreeListBox->set_cursor(*xRet); m_xTreeListBox->select(*xRet); m_xTreeListBox->scroll_to_row(*xRet); m_xRemoveWatchButton->set_sensitive(true); UpdateWatches(false); } void WatchWindow::RemoveSelectedWatch() { std::unique_ptr xEntry = m_xTreeListBox->make_iterator(); bool bEntry = m_xTreeListBox->get_cursor(xEntry.get()); if (bEntry) { m_xTreeListBox->remove(*xEntry); bEntry = m_xTreeListBox->get_cursor(xEntry.get()); if (bEntry) m_xEdit->set_text(weld::fromId(m_xTreeListBox->get_id(*xEntry))->maName); else m_xEdit->set_text(OUString()); if ( !m_xTreeListBox->n_children() ) m_xRemoveWatchButton->set_sensitive(false); } } IMPL_STATIC_LINK_NOARG(WatchWindow, ButtonHdl, weld::Button&, void) { if (SfxDispatcher* pDispatcher = GetDispatcher()) pDispatcher->Execute(SID_BASICIDE_REMOVEWATCH); } IMPL_LINK_NOARG(WatchWindow, TreeListHdl, weld::TreeView&, void) { std::unique_ptr xCurEntry = m_xTreeListBox->make_iterator(); bool bCurEntry = m_xTreeListBox->get_cursor(xCurEntry.get()); if (!bCurEntry) return; WatchItem* pItem = weld::fromId(m_xTreeListBox->get_id(*xCurEntry)); if (!pItem) return; m_xEdit->set_text(pItem->maName); } IMPL_LINK_NOARG(WatchWindow, ActivateHdl, weld::Entry&, bool) { OUString aCurText(m_xEdit->get_text()); if (!aCurText.isEmpty()) { AddWatch(aCurText); m_xEdit->select_region(0, -1); } return true; } IMPL_LINK(WatchWindow, KeyInputHdl, const KeyEvent&, rKEvt, bool) { bool bHandled = false; sal_uInt16 nKeyCode = rKEvt.GetKeyCode().GetCode(); if (nKeyCode == KEY_ESCAPE) { m_xEdit->set_text(OUString()); bHandled = true; } return bHandled; } // StackWindow StackWindow::StackWindow(Layout* pParent) : DockingWindow(pParent, "modules/BasicIDE/ui/dockingstack.ui", "DockingStack") { m_xTitle = m_xBuilder->weld_label("title"); m_xTitle->set_label(IDEResId(RID_STR_STACK)); m_xTitle->set_size_request(-1, nVirtToolBoxHeight); // so the two title areas are the same height m_xTreeListBox = m_xBuilder->weld_tree_view("stack"); m_xTreeListBox->set_help_id(HID_BASICIDE_STACKWINDOW_LIST); m_xTreeListBox->set_accessible_name(IDEResId(RID_STR_STACKNAME)); m_xTreeListBox->set_selection_mode(SelectionMode::NONE); m_xTreeListBox->append_text(OUString()); SetText(IDEResId(RID_STR_STACKNAME)); SetHelpId( HID_BASICIDE_STACKWINDOW ); // make stack window keyboard accessible GetSystemWindow()->GetTaskPaneList()->AddWindow( this ); } StackWindow::~StackWindow() { disposeOnce(); } void StackWindow::dispose() { GetSystemWindow()->GetTaskPaneList()->RemoveWindow( this ); m_xTitle.reset(); m_xTreeListBox.reset(); DockingWindow::dispose(); } void StackWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) { lcl_DrawIDEWindowFrame(this, rRenderContext); } void StackWindow::Resize() { Size aSz = GetOutputSizePixel(); Size aBoxSz(aSz.Width() - 2*DWBORDER, aSz.Height() - 2*DWBORDER); if ( aBoxSz.Width() < 4 ) aBoxSz.setWidth( 0 ); if ( aBoxSz.Height() < 4 ) aBoxSz.setHeight( 0 ); m_xBox->SetPosSizePixel(Point(DWBORDER, DWBORDER), aBoxSz); Invalidate(); } void StackWindow::UpdateCalls() { m_xTreeListBox->freeze(); m_xTreeListBox->clear(); if (StarBASIC::IsRunning()) { ErrCode eOld = SbxBase::GetError(); m_xTreeListBox->set_selection_mode(SelectionMode::Single); sal_Int32 nScope = 0; SbMethod* pMethod = StarBASIC::GetActiveMethod( nScope ); while ( pMethod ) { OUStringBuffer aEntry( OUString::number(nScope )); if ( aEntry.getLength() < 2 ) aEntry.insert(0, " "); aEntry.append(": " + pMethod->GetName()); SbxArray* pParams = pMethod->GetParameters(); SbxInfo* pInfo = pMethod->GetInfo(); if ( pParams ) { aEntry.append("("); // 0 is the sub's name... for (sal_uInt32 nParam = 1; nParam < pParams->Count(); nParam++) { SbxVariable* pVar = pParams->Get(nParam); assert(pVar && "Parameter?!"); if ( !pVar->GetName().isEmpty() ) { aEntry.append(pVar->GetName()); } else if ( pInfo ) { assert(nParam <= std::numeric_limits::max()); const SbxParamInfo* pParam = pInfo->GetParam( sal::static_int_cast(nParam) ); if ( pParam ) { aEntry.append(pParam->aName); } } aEntry.append("="); SbxDataType eType = pVar->GetType(); if( eType & SbxARRAY ) { aEntry.append("..."); } else if( eType != SbxOBJECT ) { aEntry.append(pVar->GetOUString()); } if (nParam < (pParams->Count() - 1)) { aEntry.append(", "); } } aEntry.append(")"); } m_xTreeListBox->append_text(aEntry.makeStringAndClear()); nScope++; pMethod = StarBASIC::GetActiveMethod( nScope ); } SbxBase::ResetError(); if( eOld != ERRCODE_NONE ) SbxBase::SetError( eOld ); } else { m_xTreeListBox->set_selection_mode(SelectionMode::NONE); m_xTreeListBox->append_text(OUString()); } m_xTreeListBox->thaw(); } ComplexEditorWindow::ComplexEditorWindow( ModulWindow* pParent ) : Window( pParent, WB_3DLOOK | WB_CLIPCHILDREN ), aBrkWindow(VclPtr::Create(this, pParent)), aLineNumberWindow(VclPtr::Create(this, pParent)), aEdtWindow(VclPtr::Create(this, pParent)), aEWVScrollBar(VclPtr::Create(this, false)), aEWHScrollBar(VclPtr::Create(this, true)) { aEdtWindow->Show(); aBrkWindow->Show(); aEWVScrollBar->SetLineSize(nScrollLine); aEWVScrollBar->SetPageSize(nScrollPage); aEWVScrollBar->SetScrollHdl( LINK( this, ComplexEditorWindow, ScrollHdl ) ); aEWVScrollBar->Show(); aEWHScrollBar->SetLineSize(nScrollLine); aEWHScrollBar->SetPageSize(nScrollPage); aEWHScrollBar->SetScrollHdl( LINK( this, ComplexEditorWindow, ScrollHdl ) ); aEWHScrollBar->Show(); } ComplexEditorWindow::~ComplexEditorWindow() { disposeOnce(); } void ComplexEditorWindow::dispose() { aBrkWindow.disposeAndClear(); aLineNumberWindow.disposeAndClear(); aEdtWindow.disposeAndClear(); aEWVScrollBar.disposeAndClear(); aEWHScrollBar.disposeAndClear(); vcl::Window::dispose(); } void ComplexEditorWindow::Resize() { Size aOutSz = GetOutputSizePixel(); Size aSz(aOutSz); aSz.AdjustWidth( -(2*DWBORDER) ); aSz.AdjustHeight( -(2*DWBORDER) ); tools::Long nBrkWidth = 20; tools::Long nSBWidth = aEWVScrollBar->GetSizePixel().Width(); tools::Long nSBHeight = aEWHScrollBar->GetSizePixel().Height(); Size aBrkSz(nBrkWidth, aSz.Height() - nSBHeight); if (aLineNumberWindow->IsVisible()) { Size aLnSz(aLineNumberWindow->GetWidth(), aSz.Height() - nSBHeight); Size aEWSz(aSz.Width() - nBrkWidth - aLineNumberWindow->GetWidth() - nSBWidth, aSz.Height() - nSBHeight); aBrkWindow->SetPosSizePixel(Point(DWBORDER, DWBORDER), aBrkSz); aLineNumberWindow->SetPosSizePixel(Point(DWBORDER + nBrkWidth, DWBORDER), aLnSz); aEdtWindow->SetPosSizePixel(Point(DWBORDER + nBrkWidth + aLnSz.Width(), DWBORDER), aEWSz); } else { Size aEWSz(aSz.Width() - nBrkWidth - nSBWidth, aSz.Height() - nSBHeight); aBrkWindow->SetPosSizePixel( Point( DWBORDER, DWBORDER ), aBrkSz ); aEdtWindow->SetPosSizePixel(Point(DWBORDER + nBrkWidth, DWBORDER), aEWSz); } aEWVScrollBar->SetPosSizePixel(Point(aOutSz.Width() - DWBORDER - nSBWidth, DWBORDER), Size(nSBWidth, aSz.Height() - nSBHeight)); aEWHScrollBar->SetPosSizePixel(Point(DWBORDER, aOutSz.Height() - DWBORDER - nSBHeight), Size(aSz.Width() - nSBWidth, nSBHeight)); } IMPL_LINK_NOARG(ComplexEditorWindow, ScrollHdl, weld::Scrollbar&, void) { if (aEdtWindow->GetEditView()) { tools::Long nXDiff = aEdtWindow->GetEditView()->GetStartDocPos().X() - aEWHScrollBar->GetThumbPos(); tools::Long nYDiff = aEdtWindow->GetEditView()->GetStartDocPos().Y() - aEWVScrollBar->GetThumbPos(); aEdtWindow->GetEditView()->Scroll(nXDiff, nYDiff); aBrkWindow->DoScroll( nYDiff ); aLineNumberWindow->DoScroll( nYDiff ); aEdtWindow->GetEditView()->ShowCursor(false); aEWVScrollBar->SetThumbPos( aEdtWindow->GetEditView()->GetStartDocPos().Y() ); } } void ComplexEditorWindow::DataChanged(DataChangedEvent const & rDCEvt) { Window::DataChanged(rDCEvt); if (rDCEvt.GetType() == DataChangedEventType::SETTINGS && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) { Color aColor(GetSettings().GetStyleSettings().GetFaceColor()); const AllSettings* pOldSettings = rDCEvt.GetOldSettings(); if (!pOldSettings || aColor != pOldSettings->GetStyleSettings().GetFaceColor()) { SetBackground(Wallpaper(aColor)); Invalidate(); } } } void ComplexEditorWindow::SetLineNumberDisplay(bool b) { aLineNumberWindow->Show(b); Resize(); } uno::Reference< awt::XVclWindowPeer > EditorWindow::GetComponentInterface(bool bCreate) { uno::Reference< awt::XVclWindowPeer > xPeer( Window::GetComponentInterface(false)); if (!xPeer.is() && bCreate) { // Make sure edit engine and view are available: if (!pEditEngine) CreateEditEngine(); xPeer = createTextWindowPeer(*GetEditView()); SetComponentInterface(xPeer); } return xPeer; } static sal_uInt32 getCorrectedPropCount(SbxArray* p) { sal_uInt32 nPropCount = p->Count(); if (nPropCount >= 3 && p->Get(nPropCount - 1)->GetName().equalsIgnoreAsciiCase("Dbg_Methods") && p->Get(nPropCount - 2)->GetName().equalsIgnoreAsciiCase("Dbg_Properties") && p->Get(nPropCount - 3)->GetName().equalsIgnoreAsciiCase("Dbg_SupportedInterfaces")) { nPropCount -= 3; } return nPropCount; } IMPL_LINK(WatchWindow, RequestingChildrenHdl, const weld::TreeIter&, rParent, bool) { if( !StarBASIC::IsRunning() ) return true; if (m_xTreeListBox->iter_has_child(rParent)) return true; WatchItem* pItem = weld::fromId(m_xTreeListBox->get_id(rParent)); std::unique_ptr xRet = m_xTreeListBox->make_iterator(); SbxDimArray* pArray = pItem->mpArray.get(); SbxDimArray* pRootArray = pItem->GetRootArray(); bool bArrayIsRootArray = false; if( !pArray && pRootArray ) { pArray = pRootArray; bArrayIsRootArray = true; } SbxObject* pObj = pItem->mpObject.get(); if( pObj ) { createAllObjectProperties( pObj ); SbxArray* pProps = pObj->GetProperties(); const sal_uInt32 nPropCount = getCorrectedPropCount(pProps); pItem->maMemberList.reserve(nPropCount); for( sal_uInt32 i = 0 ; i < nPropCount ; ++i ) { SbxVariable* pVar = pProps->Get(i); pItem->maMemberList.push_back(pVar->GetName()); OUString const& rName = pItem->maMemberList.back(); WatchItem* pWatchItem = new WatchItem(rName); OUString sId(weld::toId(pWatchItem)); m_xTreeListBox->insert(&rParent, -1, &rName, &sId, nullptr, nullptr, false, xRet.get()); m_xTreeListBox->set_text(*xRet, "", 1); m_xTreeListBox->set_text(*xRet, "", 2); } if (nPropCount > 0 && !m_nUpdateWatchesId) { m_nUpdateWatchesId = Application::PostUserEvent(LINK(this, WatchWindow, ExecuteUpdateWatches)); } } else if( pArray ) { sal_uInt16 nElementCount = 0; // Loop through indices of current level int nParentLevel = bArrayIsRootArray ? pItem->nDimLevel : 0; int nThisLevel = nParentLevel + 1; sal_Int32 nMin, nMax; if (pArray->GetDim(nThisLevel, nMin, nMax)) { for (sal_Int32 i = nMin; i <= nMax; i++) { WatchItem* pChildItem = new WatchItem(pItem->maName); // Copy data and create name OUStringBuffer aIndexStr = "("; pChildItem->mpArrayParentItem = pItem; pChildItem->nDimLevel = nThisLevel; pChildItem->nDimCount = pItem->nDimCount; pChildItem->vIndices.resize(pChildItem->nDimCount); sal_Int32 j; for (j = 0; j < nParentLevel; j++) { sal_Int32 n = pChildItem->vIndices[j] = pItem->vIndices[j]; aIndexStr.append( OUString::number(n) + "," ); } pChildItem->vIndices[nParentLevel] = i; aIndexStr.append( OUString::number(i) + ")" ); OUString aDisplayName; WatchItem* pArrayRootItem = pChildItem->GetRootItem(); if (pArrayRootItem && pArrayRootItem->mpArrayParentItem) aDisplayName = pItem->maDisplayName; else aDisplayName = pItem->maName; aDisplayName += aIndexStr; pChildItem->maDisplayName = aDisplayName; OUString sId(weld::toId(pChildItem)); m_xTreeListBox->insert(&rParent, -1, &aDisplayName, &sId, nullptr, nullptr, false, xRet.get()); m_xTreeListBox->set_text(*xRet, "", 1); m_xTreeListBox->set_text(*xRet, "", 2); nElementCount++; } } if (nElementCount > 0 && !m_nUpdateWatchesId) { m_nUpdateWatchesId = Application::PostUserEvent(LINK(this, WatchWindow, ExecuteUpdateWatches)); } } return true; } IMPL_LINK_NOARG(WatchWindow, ExecuteUpdateWatches, void*, void) { m_nUpdateWatchesId = nullptr; UpdateWatches(); } SbxBase* WatchWindow::ImplGetSBXForEntry(const weld::TreeIter& rEntry, bool& rbArrayElement) { SbxBase* pSBX = nullptr; rbArrayElement = false; WatchItem* pItem = weld::fromId(m_xTreeListBox->get_id(rEntry)); OUString aVName( pItem->maName ); std::unique_ptr xParentEntry = m_xTreeListBox->make_iterator(&rEntry); bool bParentEntry = m_xTreeListBox->iter_parent(*xParentEntry); WatchItem* pParentItem = bParentEntry ? weld::fromId(m_xTreeListBox->get_id(*xParentEntry)) : nullptr; if( pParentItem ) { SbxObject* pObj = pParentItem->mpObject.get(); SbxDimArray* pArray; if( pObj ) { pSBX = pObj->Find( aVName, SbxClassType::DontCare ); if (SbxVariable const* pVar = IsSbxVariable(pSBX)) { // Force getting value SbxValues aRes; aRes.eType = SbxVOID; pVar->Get( aRes ); } } // Array? else if( (pArray = pItem->GetRootArray()) != nullptr ) { rbArrayElement = true; if( pParentItem->nDimLevel + 1 == pParentItem->nDimCount ) pSBX = pArray->Get(pItem->vIndices.empty() ? nullptr : &*pItem->vIndices.begin()); } } else { pSBX = StarBASIC::FindSBXInCurrentScope( aVName ); } return pSBX; } IMPL_LINK(WatchWindow, EditingEntryHdl, const weld::TreeIter&, rIter, bool) { WatchItem* pItem = weld::fromId(m_xTreeListBox->get_id(rIter)); bool bEdit = false; if (StarBASIC::IsRunning() && StarBASIC::GetActiveMethod() && !SbxBase::IsError()) { // No out of scope entries bool bArrayElement; SbxBase* pSbx = ImplGetSBXForEntry(rIter, bArrayElement); if (IsSbxVariable(pSbx) || bArrayElement) { // Accept no objects and only end nodes of arrays for editing if( !pItem->mpObject.is() && ( !pItem->mpArray.is() || pItem->nDimLevel == pItem->nDimCount ) ) { aEditingRes = m_xTreeListBox->get_text(rIter, 1); aEditingRes = comphelper::string::strip(aEditingRes, ' '); bEdit = true; } } } return bEdit; } IMPL_LINK(WatchWindow, EditedEntryHdl, const IterString&, rIterString, bool) { const weld::TreeIter& rIter = rIterString.first; OUString aResult = comphelper::string::strip(rIterString.second, ' '); sal_uInt16 nResultLen = aResult.getLength(); sal_Unicode cFirst = aResult[0]; sal_Unicode cLast = aResult[ nResultLen - 1 ]; if( cFirst == '\"' && cLast == '\"' ) aResult = aResult.copy( 1, nResultLen - 2 ); if (aResult == aEditingRes) return false; bool bArrayElement; SbxBase* pSBX = ImplGetSBXForEntry(rIter, bArrayElement); if (SbxVariable* pVar = IsSbxVariable(pSBX)) { SbxDataType eType = pVar->GetType(); if ( static_cast(eType) != sal_uInt8(SbxOBJECT) && ( eType & SbxARRAY ) == 0 ) { // If the type is variable, the conversion of the SBX does not matter, // else the string is converted. pVar->PutStringExt( aResult ); } } if ( SbxBase::IsError() ) { SbxBase::ResetError(); } UpdateWatches(); // The text should never be taken/copied 1:1, // as the UpdateWatches will be lost return false; } namespace { void implCollapseModifiedObjectEntry(const weld::TreeIter& rParent, weld::TreeView& rTree) { rTree.collapse_row(rParent); std::unique_ptr xDeleteEntry = rTree.make_iterator(&rParent); while (rTree.iter_children(*xDeleteEntry)) { implCollapseModifiedObjectEntry(*xDeleteEntry, rTree); WatchItem* pItem = weld::fromId(rTree.get_id(*xDeleteEntry)); delete pItem; rTree.remove(*xDeleteEntry); rTree.copy_iterator(rParent, *xDeleteEntry); } } OUString implCreateTypeStringForDimArray( WatchItem* pItem, SbxDataType eType ) { OUString aRetStr = getBasicTypeName( eType ); SbxDimArray* pArray = pItem->mpArray.get(); if( !pArray ) pArray = pItem->GetRootArray(); if( pArray ) { int nDimLevel = pItem->nDimLevel; int nDims = pItem->nDimCount; if( nDimLevel < nDims ) { aRetStr += "("; for( int i = nDimLevel ; i < nDims ; i++ ) { sal_Int32 nMin, nMax; pArray->GetDim(sal::static_int_cast(i + 1), nMin, nMax); aRetStr += OUString::number(nMin) + " to " + OUString::number(nMax); if( i < nDims - 1 ) aRetStr += ", "; } aRetStr += ")"; } } return aRetStr; } } // namespace void WatchWindow::implEnableChildren(const weld::TreeIter& rEntry, bool bEnable) { if (bEnable) { if (!m_xTreeListBox->get_row_expanded(rEntry)) m_xTreeListBox->set_children_on_demand(rEntry, true); } else { assert(!m_xTreeListBox->get_row_expanded(rEntry)); m_xTreeListBox->set_children_on_demand(rEntry, false); } } void WatchWindow::UpdateWatches(bool bBasicStopped) { SbMethod* pCurMethod = StarBASIC::GetActiveMethod(); ErrCode eOld = SbxBase::GetError(); setBasicWatchMode( true ); m_xTreeListBox->all_foreach([this, pCurMethod, bBasicStopped](weld::TreeIter& rEntry){ WatchItem* pItem = weld::fromId(m_xTreeListBox->get_id(rEntry)); DBG_ASSERT( !pItem->maName.isEmpty(), "Var? - Must not be empty!" ); OUString aWatchStr; OUString aTypeStr; if ( pCurMethod ) { bool bCollapse = false; TriState eEnableChildren = TRISTATE_INDET; bool bArrayElement; SbxBase* pSBX = ImplGetSBXForEntry(rEntry, bArrayElement); // Array? If no end node create type string if( bArrayElement && pItem->nDimLevel < pItem->nDimCount ) { SbxDimArray* pRootArray = pItem->GetRootArray(); SbxDataType eType = pRootArray->GetType(); aTypeStr = implCreateTypeStringForDimArray( pItem, eType ); eEnableChildren = TRISTATE_TRUE; } if (SbxVariable* pVar = dynamic_cast(pSBX)) { // extra treatment of arrays SbxDataType eType = pVar->GetType(); if ( eType & SbxARRAY ) { // consider multidimensional arrays! if (SbxDimArray* pNewArray = dynamic_cast(pVar->GetObject())) { SbxDimArray* pOldArray = pItem->mpArray.get(); bool bArrayChanged = false; if (pOldArray != nullptr) { // Compare Array dimensions to see if array has changed // Can be a copy, so comparing pointers does not work sal_Int32 nOldDims = pOldArray->GetDims(); sal_Int32 nNewDims = pNewArray->GetDims(); if( nOldDims != nNewDims ) { bArrayChanged = true; } else { for( sal_Int32 i = 0 ; i < nOldDims ; i++ ) { sal_Int32 nOldMin, nOldMax; sal_Int32 nNewMin, nNewMax; pOldArray->GetDim(i + 1, nOldMin, nOldMax); pNewArray->GetDim(i + 1, nNewMin, nNewMax); if( nOldMin != nNewMin || nOldMax != nNewMax ) { bArrayChanged = true; break; } } } } else { bArrayChanged = true; } eEnableChildren = TRISTATE_TRUE; // #i37227 Clear always and replace array if( pNewArray != pOldArray ) { pItem->clearWatchItem(); eEnableChildren = TRISTATE_TRUE; pItem->mpArray = pNewArray; sal_Int32 nDims = pNewArray->GetDims(); pItem->nDimLevel = 0; pItem->nDimCount = nDims; } if( bArrayChanged && pOldArray != nullptr ) { bCollapse = true; } aTypeStr = implCreateTypeStringForDimArray( pItem, eType ); } else { aWatchStr += ""; } } else if ( static_cast(eType) == sal_uInt8(SbxOBJECT) ) { if (SbxObject* pObj = dynamic_cast(pVar->GetObject())) { if ( pItem->mpObject.is() && !pItem->maMemberList.empty() ) { createAllObjectProperties(pObj); SbxArray* pProps = pObj->GetProperties(); const sal_uInt32 nPropCount = getCorrectedPropCount(pProps); // Check if member list has changed bCollapse = pItem->maMemberList.size() != nPropCount; for( sal_uInt32 i = 0 ; !bCollapse && i < nPropCount ; i++ ) { SbxVariable* pVar_ = pProps->Get(i); if( pItem->maMemberList[i] != pVar_->GetName() ) bCollapse = true; } } pItem->mpObject = pObj; eEnableChildren = TRISTATE_TRUE; aTypeStr = getBasicObjectTypeName( pObj ); } else { aWatchStr = "Null"; if( pItem->mpObject.is() ) { bCollapse = true; eEnableChildren = TRISTATE_FALSE; } } } else { if( pItem->mpObject.is() ) { bCollapse = true; eEnableChildren = TRISTATE_FALSE; } bool bString = (static_cast(eType) == sal_uInt8(SbxSTRING)); OUString aStrStr( "\"" ); if( bString ) { aWatchStr += aStrStr; } // tdf#57308 - avoid a second call to retrieve the data const SbxFlagBits nFlags = pVar->GetFlags(); pVar->SetFlag(SbxFlagBits::NoBroadcast); aWatchStr += pVar->GetOUString(); pVar->SetFlags(nFlags); if( bString ) { aWatchStr += aStrStr; } } if( aTypeStr.isEmpty() ) { if( !pVar->IsFixed() ) { aTypeStr = "Variant/"; } aTypeStr += getBasicTypeName( pVar->GetType() ); } } else if( !bArrayElement ) { aWatchStr += ""; } if( bCollapse ) { implCollapseModifiedObjectEntry(rEntry, *m_xTreeListBox); pItem->clearWatchItem(); } if (eEnableChildren != TRISTATE_INDET) implEnableChildren(rEntry, eEnableChildren == TRISTATE_TRUE); } else if( bBasicStopped ) { if( pItem->mpObject.is() || pItem->mpArray.is() ) { implCollapseModifiedObjectEntry(rEntry, *m_xTreeListBox); pItem->mpObject.clear(); pItem->mpArray.clear(); } pItem->clearWatchItem(); } m_xTreeListBox->set_text(rEntry, aWatchStr, 1); m_xTreeListBox->set_text(rEntry, aTypeStr, 2); return false; }); SbxBase::ResetError(); if( eOld != ERRCODE_NONE ) SbxBase::SetError( eOld ); setBasicWatchMode( false ); } IMPL_LINK_NOARG(CodeCompleteWindow, ImplDoubleClickHdl, weld::TreeView&, bool) { InsertSelectedEntry(); return true; } IMPL_LINK_NOARG(CodeCompleteWindow, ImplSelectHdl, weld::TreeView&, void) { //give back the focus to the parent pParent->GrabFocus(); } TextView* CodeCompleteWindow::GetParentEditView() { return pParent->GetEditView(); } void CodeCompleteWindow::InsertSelectedEntry() { OUString sSelectedEntry = m_xListBox->get_selected_text(); if( !aFuncBuffer.isEmpty() ) { // if the user typed in something: remove, and insert GetParentEditView()->SetSelection(pParent->GetLastHighlightPortionTextSelection()); GetParentEditView()->DeleteSelected(); if (!sSelectedEntry.isEmpty()) { // if the user selected something GetParentEditView()->InsertText(sSelectedEntry); } } else { if (!sSelectedEntry.isEmpty()) { // if the user selected something GetParentEditView()->InsertText(sSelectedEntry); } } HideAndRestoreFocus(); } void CodeCompleteWindow::SetMatchingEntries() { for (sal_Int32 i = 0, nEntryCount = m_xListBox->n_children(); i< nEntryCount; ++i) { OUString sEntry = m_xListBox->get_text(i); if (sEntry.startsWithIgnoreAsciiCase(aFuncBuffer)) { m_xListBox->select(i); break; } } } IMPL_LINK(CodeCompleteWindow, KeyInputHdl, const KeyEvent&, rKEvt, bool) { return HandleKeyInput(rKEvt); } bool CodeCompleteWindow::HandleKeyInput( const KeyEvent& rKeyEvt ) { bool bHandled = true; sal_Unicode aChar = rKeyEvt.GetKeyCode().GetCode(); if( (( aChar >= KEY_A ) && ( aChar <= KEY_Z )) || ((aChar >= KEY_0) && (aChar <= KEY_9)) ) { aFuncBuffer.append(rKeyEvt.GetCharCode()); SetMatchingEntries(); } else { switch( aChar ) { case KEY_POINT: break; case KEY_ESCAPE: // hide, do nothing HideAndRestoreFocus(); break; case KEY_RIGHT: { TextSelection aTextSelection( GetParentEditView()->GetSelection() ); if( aTextSelection.GetEnd().GetPara() != GetTextSelection().GetEnd().GetPara()-1 ) { HideAndRestoreFocus(); } break; } case KEY_LEFT: { TextSelection aTextSelection( GetParentEditView()->GetSelection() ); if( aTextSelection.GetStart().GetIndex()-1 < GetTextSelection().GetStart().GetIndex() ) {//leave the cursor where it is HideAndRestoreFocus(); } break; } case KEY_TAB: { TextSelection aTextSelection = pParent->GetLastHighlightPortionTextSelection(); OUString sTypedText = pParent->GetEditEngine()->GetText(aTextSelection); if( !aFuncBuffer.isEmpty() ) { sal_Int32 nInd = m_xListBox->get_selected_index(); if (nInd != -1) { int nEntryCount = m_xListBox->n_children(); //if there is something selected bool bFound = false; for (sal_Int32 i = nInd; i != nEntryCount; ++i) { OUString sEntry = m_xListBox->get_text(i); if( sEntry.startsWithIgnoreAsciiCase( aFuncBuffer ) && (std::u16string_view(aFuncBuffer) != sTypedText) && (i != nInd) ) { m_xListBox->select(i); bFound = true; break; } } if( !bFound ) SetMatchingEntries(); GetParentEditView()->SetSelection( aTextSelection ); GetParentEditView()->DeleteSelected(); GetParentEditView()->InsertText(m_xListBox->get_selected_text()); } } break; } case KEY_SPACE: HideAndRestoreFocus(); break; case KEY_BACKSPACE: case KEY_DELETE: if( !aFuncBuffer.isEmpty() ) { //if there was something inserted by tab: add it to aFuncBuffer TextSelection aSel( GetParentEditView()->GetSelection() ); TextPaM aEnd( GetParentEditView()->CursorEndOfLine(GetTextSelection().GetEnd()) ); GetParentEditView()->SetSelection(TextSelection(GetTextSelection().GetStart(), aEnd ) ); OUString aTabInsertedStr( GetParentEditView()->GetSelected() ); GetParentEditView()->SetSelection( aSel ); if( !aTabInsertedStr.isEmpty() && aTabInsertedStr != std::u16string_view(aFuncBuffer) ) { aFuncBuffer = aTabInsertedStr; } aFuncBuffer.remove(aFuncBuffer.getLength()-1, 1); SetMatchingEntries(); } else { ClearAndHide(); bHandled = false; } break; case KEY_RETURN: InsertSelectedEntry(); break; case KEY_UP: { int nInd = m_xListBox->get_selected_index(); if (nInd) m_xListBox->select(nInd - 1); break; } case KEY_DOWN: { int nInd = m_xListBox->get_selected_index(); if (nInd + 1 < m_xListBox->n_children()) m_xListBox->select(nInd + 1); break; } default: bHandled = false; break; } } return bHandled; } void CodeCompleteWindow::HideAndRestoreFocus() { Hide(); pParent->GrabFocus(); } CodeCompleteWindow::CodeCompleteWindow(EditorWindow* pPar) : InterimItemWindow(pPar, "modules/BasicIDE/ui/codecomplete.ui", "CodeComplete") , pParent(pPar) , m_xListBox(m_xBuilder->weld_tree_view("treeview")) { m_xListBox->connect_row_activated(LINK(this, CodeCompleteWindow, ImplDoubleClickHdl)); m_xListBox->connect_changed(LINK(this, CodeCompleteWindow, ImplSelectHdl)); m_xListBox->connect_key_press(LINK(this, CodeCompleteWindow, KeyInputHdl)); m_xListBox->make_sorted(); m_xListBox->set_size_request(150, 150); // default, this will adopt the line length SetSizePixel(m_xContainer->get_preferred_size()); } CodeCompleteWindow::~CodeCompleteWindow() { disposeOnce(); } void CodeCompleteWindow::dispose() { m_xListBox.reset(); pParent.clear(); InterimItemWindow::dispose(); } void CodeCompleteWindow::InsertEntry( const OUString& aStr ) { m_xListBox->append_text(aStr); } void CodeCompleteWindow::ClearListBox() { m_xListBox->clear(); aFuncBuffer.setLength(0); } void CodeCompleteWindow::SetTextSelection( const TextSelection& aSel ) { m_aTextSelection = aSel; } void CodeCompleteWindow::ResizeAndPositionListBox() { if (m_xListBox->n_children() < 1) return; // if there is at least one element inside // calculate basic position: under the current line tools::Rectangle aRect = static_cast(pParent->GetEditEngine())->PaMtoEditCursor( pParent->GetEditView()->GetSelection().GetEnd() ); tools::Long nViewYOffset = pParent->GetEditView()->GetStartDocPos().Y(); Point aPos = aRect.BottomRight();// this variable will be used later (if needed) aPos.setY( (aPos.Y() - nViewYOffset) + nBasePad ); // get line count const sal_uInt16 nLines = static_cast(std::min(6, m_xListBox->n_children())); m_xListBox->set_size_request(-1, m_xListBox->get_height_rows(nLines)); Size aSize = m_xContainer->get_preferred_size(); //set the size SetSizePixel( aSize ); //calculate position const tools::Rectangle aVisArea( pParent->GetEditView()->GetStartDocPos(), pParent->GetOutputSizePixel() ); //the visible area const Point& aBottomPoint = aVisArea.BottomRight(); if( aVisArea.TopRight().getY() + aPos.getY() + aSize.getHeight() > aBottomPoint.getY() ) {//clipped at the bottom: move it up const tools::Long& nParentFontHeight = pParent->GetEditEngine()->GetFont().GetFontHeight(); //parent's font (in the IDE): needed for height aPos.AdjustY( -(aSize.getHeight() + nParentFontHeight + nCursorPad) ); } if( aVisArea.TopLeft().getX() + aPos.getX() + aSize.getWidth() > aBottomPoint.getX() ) {//clipped at the right side, move it a bit left aPos.AdjustX( -(aSize.getWidth() + aVisArea.TopLeft().getX()) ); } //set the position SetPosPixel( aPos ); } void CodeCompleteWindow::SelectFirstEntry() { if (m_xListBox->n_children() > 0) m_xListBox->select(0); } void CodeCompleteWindow::ClearAndHide() { ClearListBox(); HideAndRestoreFocus(); } UnoTypeCodeCompletetor::UnoTypeCodeCompletetor( const std::vector< OUString >& aVect, const OUString& sVarType ) : bCanComplete( true ) { if( aVect.empty() || sVarType.isEmpty() ) { bCanComplete = false;//invalid parameters, nothing to code complete return; } try { // Get the base class for reflection: xClass = css::reflection::theCoreReflection::get( comphelper::getProcessComponentContext())->forName(sVarType); } catch( const Exception& ) { bCanComplete = false; return; } //start from aVect[1]: aVect[0] is the variable name bCanComplete = std::none_of(aVect.begin() + 1, aVect.end(), [this](const OUString& rMethName) { return (!CodeCompleteOptions::IsExtendedTypeDeclaration() || !CheckMethod(rMethName)) && !CheckField(rMethName); }); } std::vector< OUString > UnoTypeCodeCompletetor::GetXIdlClassMethods() const { std::vector< OUString > aRetVect; if( bCanComplete && ( xClass != nullptr ) ) { const Sequence< Reference< reflection::XIdlMethod > > aMethods = xClass->getMethods(); for(Reference< reflection::XIdlMethod > const & rMethod : aMethods) { aRetVect.push_back( rMethod->getName() ); } } return aRetVect;//this is empty when cannot code complete } std::vector< OUString > UnoTypeCodeCompletetor::GetXIdlClassFields() const { std::vector< OUString > aRetVect; if( bCanComplete && ( xClass != nullptr ) ) { const Sequence< Reference< reflection::XIdlField > > aFields = xClass->getFields(); for(Reference< reflection::XIdlField > const & rxField : aFields) { aRetVect.push_back( rxField->getName() ); } } return aRetVect;//this is empty when cannot code complete } bool UnoTypeCodeCompletetor::CheckField( const OUString& sFieldName ) {// modifies xClass!!! if ( xClass == nullptr ) return false; Reference< reflection::XIdlField> xField = xClass->getField( sFieldName ); if( xField != nullptr ) { xClass = xField->getType(); if( xClass != nullptr ) { return true; } } return false; } bool UnoTypeCodeCompletetor::CheckMethod( const OUString& sMethName ) {// modifies xClass!!! if ( xClass == nullptr ) return false; Reference< reflection::XIdlMethod> xMethod = xClass->getMethod( sMethName ); if( xMethod != nullptr ) //method OK, check return type { xClass = xMethod->getReturnType(); if( xClass != nullptr ) { return true; } } return false; } } // namespace basctl /* vim:set shiftwidth=4 softtabstop=4 expandtab: */