/* -*- 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 "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 namespace basctl { using namespace ::com::sun::star; using namespace ::com::sun::star::uno; namespace { sal_uInt16 const NoMarker = 0xFFFF; long const nBasePad = 2; long const nCursorPad = 5; long nVirtToolBoxHeight; // inited in WatchWindow, used in Stackwindow long nHeaderBarHeight; // 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(BitmapEx(rId)); } int const nScrollLine = 12; int const nScrollPage = 60; int const DWBORDER = 3; char const cSuffixes[] = "%&!#@$"; } // 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, OUString const& 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(Color(COL_WHITE)); // White line on top rRenderContext.DrawLine(Point(0, 0), Point(aSz.Width(), 0)); // Black line at bottom rRenderContext.SetLineColor(Color(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 ( nIndexStart != -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 ( strchr( cSuffixes, rVar[ nLastChar ] ) ) rVar = rVar.replaceAt( nLastChar, 1, "" ); } if ( !rIndex.isEmpty() ) { sal_uInt16 nLastChar = rIndex.getLength()-1; if ( strchr( cSuffixes, rIndex[ nLastChar ] ) ) rIndex = rIndex.replaceAt( nLastChar, 1, "" ); } } } // 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 { osl::MutexGuard 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_uLong nRange) : SfxProgress(pObjSh, rText, nRange), nCurState(0) { } void StepProgress () { SetState(++nCurState); } private: sal_uLong nCurState; }; EditorWindow::EditorWindow (vcl::Window* pParent, ModulWindow* pModulWindow) : Window(pParent, WB_BORDER), rModulWindow(*pModulWindow), nCurTextWidth(0), aHighlighter(HighlighterLanguage::Basic), bHighlighting(false), bDoSyntaxHighlight(true), bDelayHighlight(true), pCodeCompleteWnd(VclPtr::Create(this)) { SetBackground(Wallpaper(rModulWindow.GetLayout().GetBackgroundColor())); SetPointer( Pointer( PointerStyle::Text ) ); SetHelpId( HID_BASICIDE_EDITORWINDOW ); listener_ = new ChangesListener(*this); Reference< beans::XMultiPropertySet > n( officecfg::Office::Common::Font::SourceViewFont::get(), UNO_QUERY_THROW); { osl::MutexGuard g(mutex_); notifier_ = n; } const Sequence aPropertyNames{"FontHeight", "FontName"}; n->addPropertiesChangeListener(aPropertyNames, listener_.get()); } EditorWindow::~EditorWindow() { disposeOnce(); } void EditorWindow::dispose() { Reference< beans::XMultiPropertySet > n; { osl::MutexGuard g(mutex_); n = notifier_; } if (n.is()) { n->removePropertiesChangeListener(listener_.get()); } 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; Point aTopLeft; 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 ( strchr( cSuffixes, aWord[ nLastChar ] ) ) aWord = aWord.replaceAt( nLastChar, 1, "" ); 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() ) { aTopLeft = GetEditView()->GetTextEngine()->PaMtoEditCursor( aStartOfWord ).BottomLeft(); aTopLeft = GetEditView()->GetWindowPos( aTopLeft ); aTopLeft.AdjustX(5 ); aTopLeft.AdjustY(5 ); aTopLeft = OutputToScreenPixel( aTopLeft ); } } } Help::ShowQuickHelp( this, tools::Rectangle( aTopLeft, Size( 1, 1 ) ), aHelpText, QuickHelpFlags::Top|QuickHelpFlags::Left); bDone = true; } } if ( !bDone ) Window::RequestHelp( rHEvt ); } void EditorWindow::Resize() { // ScrollBars, etc. happens in Adjust... if ( pEditView ) { long nVisY = pEditView->GetStartDocPos().Y(); pEditView->ShowCursor(); Size aOutSz( GetOutputSizePixel() ); 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 ) pEditView->MouseButtonDown( rEvt ); if( pCodeCompleteWnd->IsVisible() ) { if( pEditView->GetSelection() != pCodeCompleteWnd->GetTextSelection() ) {//selection changed, code complete window should be hidden pCodeCompleteWnd->GetListBox()->HideAndRestoreFocus(); } } } void EditorWindow::Command( const CommandEvent& rCEvt ) { if ( pEditView ) { pEditView->Command( rCEvt ); if ( ( rCEvt.GetCommand() == CommandEventId::Wheel ) || ( rCEvt.GetCommand() == CommandEventId::StartAutoScroll ) || ( rCEvt.GetCommand() == CommandEventId::AutoScroll ) ) { HandleScrollCommand( rCEvt, rModulWindow.GetHScrollBar(), &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 if (ScopedVclPtrInstance(nullptr, MessBoxStyle::OkCancel, IDEResId(RID_STR_WILLSTOPPRG))->Execute() == 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() ) { pCodeCompleteWnd->GetListBox()->KeyInput(rKEvt); if( rKEvt.GetKeyCode().GetCode() == KEY_UP || rKEvt.GetKeyCode().GetCode() == KEY_DOWN || rKEvt.GetKeyCode().GetCode() == KEY_TAB || rKEvt.GetKeyCode().GetCode() == KEY_POINT) 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 ( !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 ) {// 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->Count32(); ++i ) { if( pArr->Get32(i)->GetName().equalsIgnoreAsciiCase( sStr ) ) { sStr = pArr->Get32(i)->GetName(); //if found, get the correct case pEditEngine->ReplaceText( sTextSelection, sStr ); pEditView->SetSelection( aSel ); return; } } } } } TextSelection EditorWindow::GetLastHighlightPortionTextSelection() {//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(); OUString sStr = aLine.copy( r.nBegin, r.nEnd - r.nBegin ); TextPaM aStart( nLine, r.nBegin ); TextPaM aEnd( nLine, r.nBegin + sStr.getLength() ); 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(); OUString sStr = aCurrLine.copy(r.nBegin, r.nEnd - r.nBegin); if( r.tokenType == TokenType::Keywords ) { if( sStr.equalsIgnoreAsciiCase("sub") || sStr.equalsIgnoreAsciiCase("function") ) { pEditView->InsertText( sText );//append to the end GetEditView()->SetSelection(aSel); break; } if( sStr.equalsIgnoreAsciiCase("end") ) break; } } } } } bool EditorWindow::GetProcedureName(OUString const & 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) { OUString sTokStr = rLine.copy(portion.nBegin, portion.nEnd - portion.nBegin); if( portion.tokenType == TokenType::Keywords && ( sTokStr.equalsIgnoreAsciiCase("sub") || sTokStr.equalsIgnoreAsciiCase("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() ) {//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() ) { 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.size() > 0 ) 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() { SetSourceInBasic(); Window::LoseFocus(); } 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( const OUString& rStr, sal_Int32 fromIndex ) { sal_Int32 iRetPos = -1; sal_Int32 iLF = rStr.indexOf( LINE_SEP, fromIndex ); if( iLF != -1 ) { iRetPos = iLF; } else { iRetPos = rStr.indexOf( LINE_SEP_CR, fromIndex ); } return iRetPos; } 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.Update(); // 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 ) { if (TextHint const* pTextHint = dynamic_cast(&rHint)) { TextHint const& rTextHint = *pTextHint; if( rTextHint.GetId() == SfxHintId::TextViewScrolled ) { if ( rModulWindow.GetHScrollBar() ) rModulWindow.GetHScrollBar()->SetThumbPos( pEditView->GetStartDocPos().X() ); rModulWindow.GetEditVScrollBar().SetThumbPos( pEditView->GetStartDocPos().Y() ); 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() ) { long nOutHeight = GetOutputSizePixel().Height(); long nTextHeight = pEditEngine->GetTextHeight(); if ( nTextHeight < nOutHeight ) pEditView->Scroll( 0, pEditView->GetStartDocPos().Y() ); rModulWindow.GetLineNumberWindow().Invalidate(); } SetScrollBarRanges(); } else if( rTextHint.GetId() == SfxHintId::TextFormatted ) { if ( rModulWindow.GetHScrollBar() ) { const long nWidth = pEditEngine->CalcTextWidth(); if ( nWidth != nCurTextWidth ) { nCurTextWidth = nWidth; rModulWindow.GetHScrollBar()->SetRange( Range( 0, nCurTextWidth-1) ); rModulWindow.GetHScrollBar()->SetThumbPos( pEditView->GetStartDocPos().X() ); } } 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_uLong nLine ) { SbxArrayRef pMethods = rModulWindow.GetSbModule()->GetMethods(); for( sal_uInt16 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; if ( rModulWindow.GetHScrollBar() ) rModulWindow.GetHScrollBar()->SetRange( Range( 0, nCurTextWidth-1 ) ); rModulWindow.GetEditVScrollBar().SetRange( Range( 0, pEditEngine->GetTextHeight()-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(); if (rModulWindow.GetHScrollBar()) { rModulWindow.GetHScrollBar()->SetVisibleSize(aOutSz.Width()); rModulWindow.GetHScrollBar()->SetPageSize(aOutSz.Width() * 8 / 10); rModulWindow.GetHScrollBar()->SetLineSize(GetTextWidth( "x" ) ); rModulWindow.GetHScrollBar()->SetThumbPos(pEditView->GetStartDocPos().X()); rModulWindow.GetHScrollBar()->Show(); } } void EditorWindow::ImpDoHighlight( sal_uLong nLine ) { if ( bDoSyntaxHighlight ) { 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() { OUString sFontName(officecfg::Office::Common::Font::SourceViewFont::FontName::get().get_value_or(OUString())); if (sFontName.isEmpty()) { vcl::Font aTmpFont(OutputDevice::GetDefaultFont(DefaultFontType::FIXED, Application::GetSettings().GetUILanguageTag().getLanguageType(), GetDefaultFontFlags::NONE, this)); sFontName = aTmpFont.GetFamilyName(); } Size aFontSize(0, officecfg::Office::Common::Font::SourceViewFont::FontHeight::get()); vcl::Font aFont(sFontName, aFontSize); aFont.SetColor(rModulWindow.GetLayout().GetFontColor()); SetPointFont(*this, aFont); // FIXME RenderContext aFont = GetFont(); rModulWindow.GetBreakPointWindow().SetFont(aFont); rModulWindow.GetLineNumberWindow().SetFont(aFont); if (pEditEngine) { bool const bModified = pEditEngine->IsModified(); pEditEngine->SetFont(aFont); pEditEngine->SetModified(bModified); } } void EditorWindow::DoSyntaxHighlight( sal_uLong 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_uLong 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_uLong 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 ); long nLineHeight = GetTextHeight(); Size aSz = rModulWindow.GetBreakPointWindow().GetOutputSize(); tools::Rectangle aInvRect( Point( 0, 0 ), aSz ); 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_uLong 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(); } // 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(); 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); size_t 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 = GetOutputSize(); long const nLineHeight = GetTextHeight(); Image aMarker = GetImage(bErrorMarker ? OUStringLiteral(RID_BMP_ERRORMARKER) : OUStringLiteral(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 ); sal_uLong nY = nMarkerPos * nLineHeight - nCurYOffset; Point aPos(0, nY); aPos += aMarkerOff; rRenderContext.DrawImage(aPos, aMarker); } void BreakPointWindow::DoScroll( long nVertScroll ) { nCurYOffset -= nVertScroll; Window::Scroll( 0, nVertScroll ); } void BreakPointWindow::SetMarkerPos( sal_uInt16 nLine, bool bError ) { if ( SyncYOffset() ) Update(); 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* pBrk = GetBreakPoints().at( i ); size_t nLine = pBrk->nLine-1; size_t nY = nLine*nLineHeight; if ( ( nYPos > nY ) && ( nYPos < ( nY + nLineHeight ) ) ) return pBrk; } return nullptr; } void BreakPointWindow::MouseButtonDown( const MouseEvent& rMEvt ) { if ( rMEvt.GetClicks() == 2 ) { Point aMousePos( PixelToLogic( rMEvt.GetPosPixel() ) ); long nLineHeight = GetTextHeight(); if(nLineHeight) { long nYPos = aMousePos.Y() + nCurYOffset; long nLine = nYPos / nLineHeight + 1; rModulWindow.ToggleBreakPoint( static_cast(nLine) ); Invalidate(); } } } void BreakPointWindow::Command( const CommandEvent& rCEvt ) { if ( rCEvt.GetCommand() == CommandEventId::ContextMenu ) { if (!mpUIBuilder) mpUIBuilder.reset(new VclBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "modules/BasicIDE/ui/breakpointmenus.ui", "")); Point aPos( rCEvt.IsMouseEvent() ? rCEvt.GetMousePosPixel() : Point(1,1) ); Point aEventPos( PixelToLogic( aPos ) ); BreakPoint* pBrk = rCEvt.IsMouseEvent() ? FindBreakPoint( aEventPos ) : nullptr; if ( pBrk ) { // test if break point is enabled... VclPtr xBrkPropMenu = mpUIBuilder->get_menu("breakmenu"); xBrkPropMenu->CheckItem(xBrkPropMenu->GetItemId("active"), pBrk->bEnabled); OString sCommand = xBrkPropMenu->GetItemIdent(xBrkPropMenu->Execute(this, aPos)); if (sCommand == "active") { pBrk->bEnabled = !pBrk->bEnabled; rModulWindow.UpdateBreakPoint( *pBrk ); Invalidate(); } else if (sCommand == "properties") { ScopedVclPtrInstance aBrkDlg(this, GetBreakPoints()); aBrkDlg->SetCurrentBreakPoint( pBrk ); aBrkDlg->Execute(); Invalidate(); } } else { VclPtr xBrkListMenu = mpUIBuilder->get_menu("breaklistmenu"); OString sCommand = xBrkListMenu->GetItemIdent(xBrkListMenu->Execute(this, aPos)); if (sCommand == "manage") { ScopedVclPtrInstance< BreakPointDialog > aBrkDlg( this, GetBreakPoints() ); aBrkDlg->Execute(); Invalidate(); } } } } bool BreakPointWindow::SyncYOffset() { TextView* pView = rModulWindow.GetEditView(); if ( pView ) { 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)); } void BreakPointWindow::dispose() { mpUIBuilder.reset(); Window::dispose(); } namespace { const sal_uInt16 ITEM_ID_VARIABLE = 1; const sal_uInt16 ITEM_ID_VALUE = 2; const sal_uInt16 ITEM_ID_TYPE = 3; } WatchWindow::WatchWindow (Layout* pParent) : DockingWindow(pParent) , aWatchStr(IDEResId( RID_STR_REMOVEWATCH)) , aXEdit(VclPtr::Create(this, WB_BORDER | WB_3DLOOK)) , aRemoveWatchButton(VclPtr::Create(this, WB_SMALLSTYLE)) , aTreeListBox(VclPtr::Create(this, WB_BORDER | WB_3DLOOK | WB_HASBUTTONS | WB_HASLINES | WB_HSCROLL | WB_TABSTOP | WB_HASLINESATROOT | WB_HASBUTTONSATROOT)) , aHeaderBar(VclPtr::Create(this, WB_BUTTONSTYLE | WB_BORDER)) { aXEdit->SetAccessibleName(IDEResId(RID_STR_WATCHNAME)); aXEdit->SetHelpId(HID_BASICIDE_WATCHWINDOW_EDIT); aXEdit->SetSizePixel(aXEdit->LogicToPixel(Size(80, 12), MapMode(MapUnit::MapAppFont))); aTreeListBox->SetAccessibleName(IDEResId(RID_STR_WATCHNAME)); long nTextLen = GetTextWidth( aWatchStr ) + DWBORDER + 3; aXEdit->SetPosPixel( Point( nTextLen, 3 ) ); aXEdit->SetAccHdl( LINK( this, WatchWindow, EditAccHdl ) ); aXEdit->GetAccelerator().InsertItem( 1, vcl::KeyCode( KEY_RETURN ) ); aXEdit->GetAccelerator().InsertItem( 2, vcl::KeyCode( KEY_ESCAPE ) ); aXEdit->Show(); aRemoveWatchButton->Disable(); aRemoveWatchButton->SetClickHdl( LINK( this, WatchWindow, ButtonHdl ) ); aRemoveWatchButton->SetPosPixel( Point( nTextLen + aXEdit->GetSizePixel().Width() + 4, 2 ) ); aRemoveWatchButton->SetHelpId(HID_BASICIDE_REMOVEWATCH); aRemoveWatchButton->SetModeImage(Image(BitmapEx(RID_BMP_REMOVEWATCH))); aRemoveWatchButton->SetQuickHelpText(IDEResId(RID_STR_REMOVEWATCHTIP)); Size aSz( aRemoveWatchButton->GetModeImage().GetSizePixel() ); aSz.AdjustWidth(6 ); aSz.AdjustHeight(6 ); aRemoveWatchButton->SetSizePixel( aSz ); aRemoveWatchButton->Show(); long nRWBtnSize = aRemoveWatchButton->GetModeImage().GetSizePixel().Height() + 10; nVirtToolBoxHeight = aXEdit->GetSizePixel().Height() + 7; if ( nRWBtnSize > nVirtToolBoxHeight ) nVirtToolBoxHeight = nRWBtnSize; nHeaderBarHeight = 16; aTreeListBox->SetHelpId(HID_BASICIDE_WATCHWINDOW_LIST); aTreeListBox->EnableInplaceEditing(true); aTreeListBox->SetSelectHdl( LINK( this, WatchWindow, TreeListHdl ) ); aTreeListBox->SetPosPixel( Point( DWBORDER, nVirtToolBoxHeight + nHeaderBarHeight ) ); aTreeListBox->SetHighlightRange( 1, 5 ); Point aPnt( DWBORDER, nVirtToolBoxHeight + 1 ); aHeaderBar->SetPosPixel( aPnt ); aHeaderBar->SetEndDragHdl( LINK( this, WatchWindow, implEndDragHdl ) ); long nVarTabWidth = 220; long nValueTabWidth = 100; long const nTypeTabWidth = 1250; aHeaderBar->InsertItem( ITEM_ID_VARIABLE, IDEResId(RID_STR_WATCHVARIABLE), nVarTabWidth ); aHeaderBar->InsertItem( ITEM_ID_VALUE, IDEResId(RID_STR_WATCHVALUE), nValueTabWidth ); aHeaderBar->InsertItem( ITEM_ID_TYPE, IDEResId(RID_STR_WATCHTYPE), nTypeTabWidth ); long tabs[ 4 ]; tabs[ 0 ] = 3; // two tabs tabs[ 1 ] = 0; tabs[ 2 ] = nVarTabWidth; tabs[ 3 ] = nVarTabWidth + nValueTabWidth; aTreeListBox->SvHeaderTabListBox::SetTabs( tabs, MapUnit::MapPixel ); aTreeListBox->InitHeaderBar( aHeaderBar.get() ); aTreeListBox->SetNodeDefaultImages( ); aHeaderBar->Show(); aTreeListBox->Show(); SetText(IDEResId(RID_STR_WATCHNAME)); SetHelpId( HID_BASICIDE_WATCHWINDOW ); // make watch window keyboard accessible GetSystemWindow()->GetTaskPaneList()->AddWindow( this ); } WatchWindow::~WatchWindow() { disposeOnce(); } void WatchWindow::dispose() { aXEdit.disposeAndClear(); aRemoveWatchButton.disposeAndClear(); aHeaderBar.disposeAndClear(); aTreeListBox.disposeAndClear(); if (!IsDisposed()) GetSystemWindow()->GetTaskPaneList()->RemoveWindow( this ); DockingWindow::dispose(); } void WatchWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) { rRenderContext.DrawText(Point(DWBORDER, 7), aWatchStr); lcl_DrawIDEWindowFrame(this, rRenderContext); } void WatchWindow::Resize() { Size aSz = GetOutputSizePixel(); Size aBoxSz( aSz.Width() - 2*DWBORDER, aSz.Height() - nVirtToolBoxHeight - DWBORDER ); if ( aBoxSz.Width() < 4 ) aBoxSz.setWidth( 0 ); if ( aBoxSz.Height() < 4 ) aBoxSz.setHeight( 0 ); aBoxSz.AdjustHeight( -nHeaderBarHeight ); aTreeListBox->SetSizePixel( aBoxSz ); aTreeListBox->GetHScroll()->SetPageSize( aTreeListBox->GetHScroll()->GetVisibleSize() ); aBoxSz.setHeight( nHeaderBarHeight ); aHeaderBar->SetSizePixel( aBoxSz ); Invalidate(); } 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 const& rName): maName(rName), nDimLevel(0), nDimCount(0), mpArrayParentItem(nullptr) { } void clearWatchItem () { maMemberList.clear(); } WatchItem* GetRootItem(); SbxDimArray* GetRootArray(); }; 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 aWatchStr_ = aVar + "\t\t"; SvTreeListEntry* pNewEntry = aTreeListBox->InsertEntry( aWatchStr_, nullptr, true ); pNewEntry->SetUserData( pWatchItem ); aTreeListBox->Select(pNewEntry); aTreeListBox->MakeVisible(pNewEntry); aRemoveWatchButton->Enable(); UpdateWatches(false); } void WatchWindow::RemoveSelectedWatch() { SvTreeListEntry* pEntry = aTreeListBox->GetCurEntry(); if ( pEntry ) { aTreeListBox->GetModel()->Remove( pEntry ); pEntry = aTreeListBox->GetCurEntry(); if ( pEntry ) aXEdit->SetText( static_cast(pEntry->GetUserData())->maName ); else aXEdit->SetText( OUString() ); if ( !aTreeListBox->GetEntryCount() ) aRemoveWatchButton->Disable(); } } IMPL_LINK( WatchWindow, ButtonHdl, Button *, pButton, void ) { if (pButton == aRemoveWatchButton.get()) if (SfxDispatcher* pDispatcher = GetDispatcher()) pDispatcher->Execute(SID_BASICIDE_REMOVEWATCH); } IMPL_LINK_NOARG(WatchWindow, TreeListHdl, SvTreeListBox*, void) { SvTreeListEntry* pCurEntry = aTreeListBox->GetCurEntry(); if ( pCurEntry && pCurEntry->GetUserData() ) aXEdit->SetText( static_cast(pCurEntry->GetUserData())->maName ); } IMPL_LINK_NOARG( WatchWindow, implEndDragHdl, HeaderBar *, void ) { const sal_Int32 TAB_WIDTH_MIN = 10; sal_Int32 nMaxWidth = aHeaderBar->GetSizePixel().getWidth() - 2 * TAB_WIDTH_MIN; sal_Int32 nVariableWith = aHeaderBar->GetItemSize( ITEM_ID_VARIABLE ); if( nVariableWith < TAB_WIDTH_MIN ) aHeaderBar->SetItemSize( ITEM_ID_VARIABLE, TAB_WIDTH_MIN ); else if( nVariableWith > nMaxWidth ) aHeaderBar->SetItemSize( ITEM_ID_VARIABLE, nMaxWidth ); sal_Int32 nValueWith = aHeaderBar->GetItemSize( ITEM_ID_VALUE ); if( nValueWith < TAB_WIDTH_MIN ) aHeaderBar->SetItemSize( ITEM_ID_VALUE, TAB_WIDTH_MIN ); else if( nValueWith > nMaxWidth ) aHeaderBar->SetItemSize( ITEM_ID_VALUE, nMaxWidth ); if (aHeaderBar->GetItemSize( ITEM_ID_TYPE ) < TAB_WIDTH_MIN) aHeaderBar->SetItemSize( ITEM_ID_TYPE, TAB_WIDTH_MIN ); sal_Int32 nPos = 0; sal_uInt16 nTabs = aHeaderBar->GetItemCount(); for( sal_uInt16 i = 1 ; i < nTabs ; ++i ) { nPos += aHeaderBar->GetItemSize( i ); aTreeListBox->SetTab( i, nPos, MapUnit::MapPixel ); } } IMPL_LINK( WatchWindow, EditAccHdl, Accelerator&, rAcc, void ) { switch ( rAcc.GetCurKeyCode().GetCode() ) { case KEY_RETURN: { OUString aCurText( aXEdit->GetText() ); if ( !aCurText.isEmpty() ) { AddWatch( aCurText ); aXEdit->SetSelection( Selection( 0, 0xFFFF ) ); } } break; case KEY_ESCAPE: { aXEdit->SetText( OUString() ); } break; } } void WatchWindow::UpdateWatches( bool bBasicStopped ) { aTreeListBox->UpdateWatches( bBasicStopped ); } // StackWindow StackWindow::StackWindow (Layout* pParent) : DockingWindow(pParent), aTreeListBox( VclPtr::Create(this, WB_BORDER | WB_3DLOOK | WB_HSCROLL | WB_TABSTOP) ), aStackStr( IDEResId( RID_STR_STACK ) ) { aTreeListBox->SetHelpId(HID_BASICIDE_STACKWINDOW_LIST); aTreeListBox->SetAccessibleName(IDEResId(RID_STR_STACKNAME)); aTreeListBox->SetPosPixel( Point( DWBORDER, nVirtToolBoxHeight ) ); aTreeListBox->SetHighlightRange(); aTreeListBox->SetSelectionMode( SelectionMode::NONE ); aTreeListBox->InsertEntry( OUString() ); aTreeListBox->Show(); SetText(IDEResId(RID_STR_STACKNAME)); SetHelpId( HID_BASICIDE_STACKWINDOW ); // make stack window keyboard accessible GetSystemWindow()->GetTaskPaneList()->AddWindow( this ); } StackWindow::~StackWindow() { disposeOnce(); } void StackWindow::dispose() { if (!IsDisposed()) GetSystemWindow()->GetTaskPaneList()->RemoveWindow( this ); aTreeListBox.disposeAndClear(); DockingWindow::dispose(); } void StackWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) { rRenderContext.DrawText(Point(DWBORDER, 7), aStackStr); lcl_DrawIDEWindowFrame(this, rRenderContext); } void StackWindow::Resize() { Size aSz = GetOutputSizePixel(); Size aBoxSz(aSz.Width() - 2*DWBORDER, aSz.Height() - nVirtToolBoxHeight - DWBORDER); if ( aBoxSz.Width() < 4 ) aBoxSz.setWidth( 0 ); if ( aBoxSz.Height() < 4 ) aBoxSz.setHeight( 0 ); aTreeListBox->SetSizePixel( aBoxSz ); Invalidate(); } void StackWindow::UpdateCalls() { aTreeListBox->SetUpdateMode(false); aTreeListBox->Clear(); if (StarBASIC::IsRunning()) { ErrCode eOld = SbxBase::GetError(); aTreeListBox->SetSelectionMode( SelectionMode::Single ); sal_Int32 nScope = 0; SbMethod* pMethod = StarBASIC::GetActiveMethod( nScope ); while ( pMethod ) { OUString aEntry( OUString::number(nScope )); if ( aEntry.getLength() < 2 ) aEntry = " " + aEntry; aEntry += ": " + pMethod->GetName(); SbxArray* pParams = pMethod->GetParameters(); SbxInfo* pInfo = pMethod->GetInfo(); if ( pParams ) { aEntry += "("; // 0 is the sub's name... for ( sal_uInt16 nParam = 1; nParam < pParams->Count(); nParam++ ) { SbxVariable* pVar = pParams->Get( nParam ); assert(pVar && "Parameter?!"); if ( !pVar->GetName().isEmpty() ) { aEntry += pVar->GetName(); } else if ( pInfo ) { const SbxParamInfo* pParam = pInfo->GetParam( nParam ); if ( pParam ) { aEntry += pParam->aName; } } aEntry += "="; SbxDataType eType = pVar->GetType(); if( eType & SbxARRAY ) { aEntry += "..." ; } else if( eType != SbxOBJECT ) { aEntry += pVar->GetOUString(); } if ( nParam < ( pParams->Count() - 1 ) ) { aEntry += ", "; } } aEntry += ")"; } aTreeListBox->InsertEntry( aEntry ); nScope++; pMethod = StarBASIC::GetActiveMethod( nScope ); } SbxBase::ResetError(); if( eOld != ERRCODE_NONE ) SbxBase::SetError( eOld ); } else { aTreeListBox->SetSelectionMode( SelectionMode::NONE ); aTreeListBox->InsertEntry( OUString() ); } aTreeListBox->SetUpdateMode(true); } 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, WB_VSCROLL | WB_DRAG) ) { aEdtWindow->Show(); aBrkWindow->Show(); aEWVScrollBar->SetLineSize(nScrollLine); aEWVScrollBar->SetPageSize(nScrollPage); aEWVScrollBar->SetScrollHdl( LINK( this, ComplexEditorWindow, ScrollHdl ) ); aEWVScrollBar->Show(); } ComplexEditorWindow::~ComplexEditorWindow() { disposeOnce(); } void ComplexEditorWindow::dispose() { aBrkWindow.disposeAndClear(); aLineNumberWindow.disposeAndClear(); aEdtWindow.disposeAndClear(); aEWVScrollBar.disposeAndClear(); vcl::Window::dispose(); } void ComplexEditorWindow::Resize() { Size aOutSz = GetOutputSizePixel(); Size aSz(aOutSz); aSz.AdjustWidth( -(2*DWBORDER) ); aSz.AdjustHeight( -(2*DWBORDER) ); long nBrkWidth = 20; long nSBWidth = aEWVScrollBar->GetSizePixel().Width(); Size aBrkSz(nBrkWidth, aSz.Height()); Size aLnSz(aLineNumberWindow->GetWidth(), aSz.Height()); if (aLineNumberWindow->IsVisible()) { aBrkWindow->SetPosSizePixel( Point( DWBORDER, DWBORDER ), aBrkSz ); aLineNumberWindow->SetPosSizePixel(Point(DWBORDER + aBrkSz.Width() - 1, DWBORDER), aLnSz); Size aEWSz(aSz.Width() - nBrkWidth - aLineNumberWindow->GetWidth() - nSBWidth + 2, aSz.Height()); aEdtWindow->SetPosSizePixel( Point( DWBORDER + aBrkSz.Width() + aLnSz.Width() - 1, DWBORDER ), aEWSz ); } else { aBrkWindow->SetPosSizePixel( Point( DWBORDER, DWBORDER ), aBrkSz ); Size aEWSz(aSz.Width() - nBrkWidth - nSBWidth + 2, aSz.Height()); aEdtWindow->SetPosSizePixel(Point(DWBORDER + aBrkSz.Width() - 1, DWBORDER), aEWSz); } aEWVScrollBar->SetPosSizePixel( Point( aOutSz.Width() - DWBORDER - nSBWidth, DWBORDER ), Size( nSBWidth, aSz.Height() ) ); } IMPL_LINK(ComplexEditorWindow, ScrollHdl, ScrollBar *, pCurScrollBar, void ) { if (aEdtWindow->GetEditView()) { DBG_ASSERT( pCurScrollBar == aEWVScrollBar.get(), "Who is scrolling?" ); long nDiff = aEdtWindow->GetEditView()->GetStartDocPos().Y() - pCurScrollBar->GetThumbPos(); aEdtWindow->GetEditView()->Scroll( 0, nDiff ); aBrkWindow->DoScroll( nDiff ); aLineNumberWindow->DoScroll( nDiff ); aEdtWindow->GetEditView()->ShowCursor(false); pCurScrollBar->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::XWindowPeer > EditorWindow::GetComponentInterface(bool bCreate) { uno::Reference< awt::XWindowPeer > xPeer( Window::GetComponentInterface(false)); if (!xPeer.is() && bCreate) { // Make sure edit engine and view are available: if (!pEditEngine) CreateEditEngine(); xPeer = svt::createTextWindowPeer(*GetEditView()); SetComponentInterface(xPeer); } return xPeer; } // WatchTreeListBox WatchTreeListBox::WatchTreeListBox( vcl::Window* pParent, WinBits nWinBits ) : SvHeaderTabListBox( pParent, nWinBits ) {} WatchTreeListBox::~WatchTreeListBox() { disposeOnce(); } void WatchTreeListBox::dispose() { // Destroy user data SvTreeListEntry* pEntry = First(); while ( pEntry ) { delete static_cast(pEntry->GetUserData()); pEntry->SetUserData(nullptr); pEntry = Next( pEntry ); } SvHeaderTabListBox::dispose(); } void WatchTreeListBox::SetTabs() { SvHeaderTabListBox::SetTabs(); sal_uInt16 nTabCount_ = aTabs.size(); for( sal_uInt16 i = 0 ; i < nTabCount_ ; i++ ) { SvLBoxTab* pTab = aTabs[i]; if( i == 2 ) pTab->nFlags |= SvLBoxTabFlags::EDITABLE; else pTab->nFlags &= ~SvLBoxTabFlags::EDITABLE; } } void WatchTreeListBox::RequestingChildren( SvTreeListEntry * pParent ) { if( !StarBASIC::IsRunning() ) return; if( GetChildCount( pParent ) > 0 ) return; SvTreeListEntry* pEntry = pParent; WatchItem* pItem = static_cast(pEntry->GetUserData()); 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(); sal_uInt16 nPropCount = pProps->Count(); if ( nPropCount >= 3 && pProps->Get( nPropCount -1 )->GetName().equalsIgnoreAsciiCase( "Dbg_Methods" ) && pProps->Get( nPropCount -2 )->GetName().equalsIgnoreAsciiCase( "Dbg_Properties" ) && pProps->Get( nPropCount -3 )->GetName().equalsIgnoreAsciiCase( "Dbg_SupportedInterfaces" ) ) { nPropCount -= 3; } pItem->maMemberList.reserve(nPropCount); for( sal_uInt16 i = 0 ; i < nPropCount ; ++i ) { SbxVariable* pVar = pProps->Get( i ); pItem->maMemberList.push_back(pVar->GetName()); OUString const& rName = pItem->maMemberList.back(); SvTreeListEntry* pChildEntry = SvTreeListBox::InsertEntry( rName, pEntry ); pChildEntry->SetUserData(new WatchItem(rName)); } if( nPropCount > 0 ) { UpdateWatches(); } } 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; pArray->GetDim32( nThisLevel, nMin, nMax ); for( sal_Int32 i = nMin ; i <= nMax ; i++ ) { WatchItem* pChildItem = new WatchItem(pItem->maName); // Copy data and create name OUString 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++ ) { short n = pChildItem->vIndices[j] = pItem->vIndices[j]; aIndexStr += OUString::number( n ) + ","; } pChildItem->vIndices[nParentLevel] = sal::static_int_cast( i ); aIndexStr += 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; SvTreeListEntry* pChildEntry = SvTreeListBox::InsertEntry( aDisplayName, pEntry ); nElementCount++; pChildEntry->SetUserData( pChildItem ); } if( nElementCount > 0 ) { UpdateWatches(); } } } SbxBase* WatchTreeListBox::ImplGetSBXForEntry( SvTreeListEntry* pEntry, bool& rbArrayElement ) { SbxBase* pSBX = nullptr; rbArrayElement = false; WatchItem* pItem = static_cast(pEntry->GetUserData()); OUString aVName( pItem->maName ); SvTreeListEntry* pParentEntry = GetParent( pEntry ); WatchItem* pParentItem = pParentEntry ? static_cast(pParentEntry->GetUserData()) : 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; } bool WatchTreeListBox::EditingEntry( SvTreeListEntry* pEntry, Selection& ) { WatchItem* pItem = static_cast(pEntry->GetUserData()); bool bEdit = false; if ( StarBASIC::IsRunning() && StarBASIC::GetActiveMethod() && !SbxBase::IsError() ) { // No out of scope entries bool bArrayElement; SbxBase* pSbx = ImplGetSBXForEntry( pEntry, 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 = SvHeaderTabListBox::GetEntryText( pEntry, ITEM_ID_VALUE-1 ); aEditingRes = comphelper::string::strip(aEditingRes, ' '); bEdit = true; } } } return bEdit; } bool WatchTreeListBox::EditedEntry( SvTreeListEntry* pEntry, const OUString& rNewText ) { OUString aResult = comphelper::string::strip(rNewText, ' '); 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 ); return aResult != aEditingRes && ImplBasicEntryEdited(pEntry, aResult); } bool WatchTreeListBox::ImplBasicEntryEdited( SvTreeListEntry* pEntry, const OUString& rResult ) { bool bArrayElement; SbxBase* pSBX = ImplGetSBXForEntry( pEntry, 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( rResult ); } } 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( SvTreeListEntry* pParent, WatchTreeListBox* pThis ) { pThis->Collapse( pParent ); SvTreeList* pModel = pThis->GetModel(); SvTreeListEntry* pDeleteEntry; while( (pDeleteEntry = pThis->SvTreeListBox::GetEntry( pParent, 0 )) != nullptr ) { implCollapseModifiedObjectEntry( pDeleteEntry, pThis ); delete static_cast(pDeleteEntry->GetUserData()); pModel->Remove( pDeleteEntry ); } } 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++ ) { short 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; } void implEnableChildren( SvTreeListEntry* pEntry, bool bEnable ) { if( bEnable ) { pEntry->SetFlags( (pEntry->GetFlags() & ~SvTLEntryFlags(SvTLEntryFlags::NO_NODEBMP | SvTLEntryFlags::HAD_CHILDREN)) | SvTLEntryFlags::CHILDREN_ON_DEMAND ); } else { pEntry->SetFlags( pEntry->GetFlags() & ~SvTLEntryFlags::CHILDREN_ON_DEMAND ); } } } // namespace void WatchTreeListBox::UpdateWatches( bool bBasicStopped ) { SbMethod* pCurMethod = StarBASIC::GetActiveMethod(); ErrCode eOld = SbxBase::GetError(); setBasicWatchMode( true ); SvTreeListEntry* pEntry = First(); while ( pEntry ) { WatchItem* pItem = static_cast(pEntry->GetUserData()); DBG_ASSERT( !pItem->maName.isEmpty(), "Var? - Must not be empty!" ); OUString aWatchStr; OUString aTypeStr; if ( pCurMethod ) { bool bArrayElement; SbxBase* pSBX = ImplGetSBXForEntry( pEntry, 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 ); implEnableChildren( pEntry, true ); } bool bCollapse = false; if (SbxVariable const* pVar = IsSbxVariable(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( pNewArray != nullptr && pOldArray != nullptr ) { // Compare Array dimensions to see if array has changed // Can be a copy, so comparing pointers does not work sal_uInt16 nOldDims = pOldArray->GetDims(); sal_uInt16 nNewDims = pNewArray->GetDims(); if( nOldDims != nNewDims ) { bArrayChanged = true; } else { for( int i = 0 ; i < nOldDims ; i++ ) { short nOldMin, nOldMax; short nNewMin, nNewMax; pOldArray->GetDim( sal::static_int_cast( i+1 ), nOldMin, nOldMax ); pNewArray->GetDim( sal::static_int_cast( i+1 ), nNewMin, nNewMax ); if( nOldMin != nNewMin || nOldMax != nNewMax ) { bArrayChanged = true; break; } } } } else if( pNewArray == nullptr || pOldArray == nullptr ) { bArrayChanged = true; } if( pNewArray ) { implEnableChildren( pEntry, true ); } // #i37227 Clear always and replace array if( pNewArray != pOldArray ) { pItem->clearWatchItem(); if( pNewArray ) { implEnableChildren( pEntry, true ); pItem->mpArray = pNewArray; sal_uInt16 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() ) { bool bObjChanged = false; // Check if member list has changed SbxArray* pProps = pObj->GetProperties(); sal_uInt16 nPropCount = pProps->Count(); for( sal_uInt16 i = 0 ; i < nPropCount - 3 ; i++ ) { SbxVariable* pVar_ = pProps->Get( i ); OUString aName( pVar_->GetName() ); if( pItem->maMemberList[i] != aName ) { bObjChanged = true; break; } } if( bObjChanged ) { bCollapse = true; } } pItem->mpObject = pObj; implEnableChildren( pEntry, true ); aTypeStr = getBasicObjectTypeName( pObj ); } else { aWatchStr = "Null"; if( pItem->mpObject.is() ) { bCollapse = true; pItem->clearWatchItem(); implEnableChildren( pEntry, false ); } } } else { if( pItem->mpObject.is() ) { bCollapse = true; pItem->clearWatchItem(); implEnableChildren( pEntry, false ); } bool bString = (static_cast(eType) == sal_uInt8(SbxSTRING)); OUString aStrStr( "\"" ); if( bString ) { aWatchStr += aStrStr; } aWatchStr += pVar->GetOUString(); if( bString ) { aWatchStr += aStrStr; } } if( aTypeStr.isEmpty() ) { if( !pVar->IsFixed() ) { aTypeStr = "Variant/"; } aTypeStr += getBasicTypeName( pVar->GetType() ); } } else if( !bArrayElement ) { aWatchStr += ""; } if( bCollapse ) { implCollapseModifiedObjectEntry( pEntry, this ); } } else if( bBasicStopped ) { if( pItem->mpObject.is() || pItem->mpArray.is() ) { implCollapseModifiedObjectEntry( pEntry, this ); pItem->mpObject = nullptr; } } SvHeaderTabListBox::SetEntryText( aWatchStr, pEntry, ITEM_ID_VALUE-1 ); SvHeaderTabListBox::SetEntryText( aTypeStr, pEntry, ITEM_ID_TYPE-1 ); pEntry = Next( pEntry ); } // Force redraw Invalidate(); SbxBase::ResetError(); if( eOld != ERRCODE_NONE ) SbxBase::SetError( eOld ); setBasicWatchMode( false ); } CodeCompleteListBox::CodeCompleteListBox( CodeCompleteWindow* pPar ) : ListBox(pPar, WB_SORT | WB_BORDER ), pCodeCompleteWindow( pPar ) { SetDoubleClickHdl(LINK(this, CodeCompleteListBox, ImplDoubleClickHdl)); SetSelectHdl(LINK(this, CodeCompleteListBox, ImplSelectHdl)); } CodeCompleteListBox::~CodeCompleteListBox() { disposeOnce(); } void CodeCompleteListBox::dispose() { pCodeCompleteWindow.clear(); ListBox::dispose(); } IMPL_LINK_NOARG(CodeCompleteListBox, ImplDoubleClickHdl, ListBox&, void) { InsertSelectedEntry(); } IMPL_LINK_NOARG(CodeCompleteListBox, ImplSelectHdl, ListBox&, void) {//give back the focus to the parent pCodeCompleteWindow->pParent->GrabFocus(); } TextView* CodeCompleteListBox::GetParentEditView() { return pCodeCompleteWindow->pParent->GetEditView(); } void CodeCompleteListBox::InsertSelectedEntry() { if( !aFuncBuffer.isEmpty() ) { // if the user typed in something: remove, and insert GetParentEditView()->SetSelection( pCodeCompleteWindow->pParent->GetLastHighlightPortionTextSelection() ); GetParentEditView()->DeleteSelected(); if( !GetSelectedEntry().isEmpty() ) {//if the user selected something GetParentEditView()->InsertText( GetSelectedEntry() ); } } else { if( !GetSelectedEntry().isEmpty() ) {//if the user selected something GetParentEditView()->InsertText( GetSelectedEntry() ); } } HideAndRestoreFocus(); } void CodeCompleteListBox::SetMatchingEntries() { for(sal_Int32 i=0; i< GetEntryCount(); ++i) { OUString sEntry = GetEntry(i); if( sEntry.startsWithIgnoreAsciiCase( aFuncBuffer.toString() ) ) { SelectEntry(sEntry); break; } } } void CodeCompleteListBox::KeyInput( const KeyEvent& rKeyEvt ) { 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_ESCAPE: // hide, do nothing HideAndRestoreFocus(); break; case KEY_RIGHT: { TextSelection aTextSelection( GetParentEditView()->GetSelection() ); if( aTextSelection.GetEnd().GetPara() != pCodeCompleteWindow->GetTextSelection().GetEnd().GetPara()-1 ) { HideAndRestoreFocus(); } break; } case KEY_LEFT: { TextSelection aTextSelection( GetParentEditView()->GetSelection() ); if( aTextSelection.GetStart().GetIndex()-1 < pCodeCompleteWindow->GetTextSelection().GetStart().GetIndex() ) {//leave the cursor where it is HideAndRestoreFocus(); } break; } case KEY_TAB: { TextSelection aTextSelection = pCodeCompleteWindow->pParent->GetLastHighlightPortionTextSelection(); OUString sTypedText = pCodeCompleteWindow->pParent->GetEditEngine()->GetText(aTextSelection); if( !aFuncBuffer.isEmpty() ) { sal_Int32 nInd = GetSelectedEntryPos(); if( nInd != LISTBOX_ENTRY_NOTFOUND ) {//if there is something selected bool bFound = false; if( nInd == GetEntryCount() ) nInd = 0; for( sal_Int32 i = nInd; i != GetEntryCount(); ++i ) { OUString sEntry = GetEntry(i); if( sEntry.startsWithIgnoreAsciiCase( aFuncBuffer.toString() ) && (aFuncBuffer.toString() != sTypedText) && (i != nInd) ) { SelectEntry( sEntry ); bFound = true; break; } } if( !bFound ) SetMatchingEntries(); GetParentEditView()->SetSelection( aTextSelection ); GetParentEditView()->DeleteSelected(); GetParentEditView()->InsertText( GetSelectedEntry() ); } } 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(pCodeCompleteWindow->GetTextSelection().GetEnd()) ); GetParentEditView()->SetSelection(TextSelection(pCodeCompleteWindow->GetTextSelection().GetStart(), aEnd ) ); OUString aTabInsertedStr( GetParentEditView()->GetSelected() ); GetParentEditView()->SetSelection( aSel ); if( !aTabInsertedStr.isEmpty() && aTabInsertedStr != aFuncBuffer.toString() ) { aFuncBuffer = aTabInsertedStr; } aFuncBuffer.remove(aFuncBuffer.getLength()-1, 1); SetMatchingEntries(); } else pCodeCompleteWindow->ClearAndHide(); break; case KEY_RETURN: InsertSelectedEntry(); break; case KEY_UP: case KEY_DOWN: NotifyEvent nEvt( MouseNotifyEvent::KEYINPUT, nullptr, &rKeyEvt ); PreNotify(nEvt); break; } } ListBox::KeyInput(rKeyEvt); } void CodeCompleteListBox::HideAndRestoreFocus() { pCodeCompleteWindow->Hide(); pCodeCompleteWindow->pParent->GrabFocus(); } CodeCompleteWindow::CodeCompleteWindow( EditorWindow* pPar ) : Window( pPar ), pParent( pPar ), pListBox( VclPtr::Create(this) ) { SetSizePixel( Size(151,151) ); //default, later it changes InitListBox(); } CodeCompleteWindow::~CodeCompleteWindow() { disposeOnce(); } void CodeCompleteWindow::dispose() { pListBox.disposeAndClear(); pParent.clear(); vcl::Window::dispose(); } void CodeCompleteWindow::InitListBox() { pListBox->SetSizePixel( Size(150,150) ); //default, this will adopt the line length pListBox->Show(); pListBox->EnableQuickSelection( false ); } void CodeCompleteWindow::InsertEntry( const OUString& aStr ) { pListBox->InsertEntry( aStr ); } void CodeCompleteWindow::ClearListBox() { pListBox->Clear(); pListBox->aFuncBuffer.setLength(0); } void CodeCompleteWindow::SetTextSelection( const TextSelection& aSel ) { aTextSelection = aSel; } void CodeCompleteWindow::ResizeAndPositionListBox() { if( pListBox->GetEntryCount() >= 1 ) {// 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() ); long nViewYOffset = pParent->GetEditView()->GetStartDocPos().Y(); Point aPos = aRect.BottomRight();// this variable will be used later (if needed) aPos.setY( (aPos.Y() - nViewYOffset) + nBasePad ); OUString aLongestEntry = pListBox->GetEntry( 0 );// grab the longest one: max search for( sal_Int32 i=1; i< pListBox->GetEntryCount(); ++i ) { if( pListBox->GetEntry( i ).getLength() > aLongestEntry.getLength() ) aLongestEntry = pListBox->GetEntry( i ); } // get column/line count const sal_uInt16& nColumns = aLongestEntry.getLength(); const sal_uInt16 nLines = static_cast( std::min( sal_Int32(6), pListBox->GetEntryCount() )); Size aSize = pListBox->CalcBlockSize( nColumns, nLines ); //set the size SetSizePixel( aSize ); //1 px smaller, to see the border aSize.setWidth( aSize.getWidth() - 1 ); aSize.setHeight( aSize.getHeight() - 1 ); pListBox->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 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( pListBox->GetEntryCount() > 0 ) { pListBox->SelectEntryPos( 0 ); } } void CodeCompleteWindow::ClearAndHide() { ClearListBox(); pListBox->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; } auto j = aVect.begin() + 1;//start from aVect[1]: aVect[0] is the variable name OUString sMethName; while( j != aVect.end() ) { sMethName = *j; if( CodeCompleteOptions::IsExtendedTypeDeclaration() ) { if( !CheckMethod(sMethName) && !CheckField(sMethName) ) { bCanComplete = false; break; } } else { if( !CheckField(sMethName) ) { bCanComplete = false; break; } } ++j; } } std::vector< OUString > UnoTypeCodeCompletetor::GetXIdlClassMethods() const { std::vector< OUString > aRetVect; if( bCanComplete && ( xClass != nullptr ) ) { Sequence< Reference< reflection::XIdlMethod > > aMethods = xClass->getMethods(); if( aMethods.getLength() != 0 ) { for(sal_Int32 l = 0; l < aMethods.getLength(); ++l) { aRetVect.push_back( aMethods[l]->getName() ); } } } return aRetVect;//this is empty when cannot code complete } std::vector< OUString > UnoTypeCodeCompletetor::GetXIdlClassFields() const { std::vector< OUString > aRetVect; if( bCanComplete && ( xClass != nullptr ) ) { Sequence< Reference< reflection::XIdlField > > aFields = xClass->getFields(); if( aFields.getLength() != 0 ) { for(sal_Int32 l = 0; l < aFields.getLength(); ++l) { aRetVect.push_back( aFields[l]->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: */