/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace dbaui; class SQLEditView::ChangesListener: public cppu::WeakImplHelper< css::beans::XPropertiesChangeListener > { public: explicit ChangesListener(SQLEditView& editor): editor_(editor) {} private: virtual ~ChangesListener() override {} virtual void SAL_CALL disposing(css::lang::EventObject const &) override { std::unique_lock g(editor_.m_mutex); editor_.m_notifier.clear(); } virtual void SAL_CALL propertiesChange( css::uno::Sequence< css::beans::PropertyChangeEvent > const &) override { SolarMutexGuard g; editor_.ImplSetFont(); } SQLEditView& editor_; }; SQLEditView::SQLEditView(std::unique_ptr xScrolledWindow) : m_xScrolledWindow(std::move(xScrolledWindow)) , m_aUpdateDataTimer("dbaccess SQLEditView m_aUpdateDataTimer") , m_aHighlighter(HighlighterLanguage::SQL) , m_bInUpdate(false) , m_bDisableInternalUndo(false) { m_xScrolledWindow->connect_vadjustment_changed(LINK(this, SQLEditView, ScrollHdl)); } void SQLEditView::DisableInternalUndo() { GetEditEngine()->EnableUndo(false); m_bDisableInternalUndo = true; } void SQLEditView::SetItemPoolFont(SfxItemPool* pItemPool) { 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::OnlyOne)); sFontName = aTmpFont.GetFamilyName(); } Size aFontSize(0, officecfg::Office::Common::Font::SourceViewFont::FontHeight::get()); vcl::Font aAppFont(sFontName, aFontSize); pItemPool->SetPoolDefaultItem(SvxFontItem(aAppFont.GetFamilyType(), aAppFont.GetFamilyName(), "", PITCH_DONTKNOW, RTL_TEXTENCODING_DONTKNOW, EE_CHAR_FONTINFO)); pItemPool->SetPoolDefaultItem(SvxFontItem(aAppFont.GetFamilyType(), aAppFont.GetFamilyName(), "", PITCH_DONTKNOW, RTL_TEXTENCODING_DONTKNOW, EE_CHAR_FONTINFO_CJK)); pItemPool->SetPoolDefaultItem(SvxFontItem(aAppFont.GetFamilyType(), aAppFont.GetFamilyName(), "", PITCH_DONTKNOW, RTL_TEXTENCODING_DONTKNOW, EE_CHAR_FONTINFO_CTL)); pItemPool->SetPoolDefaultItem( SvxFontHeightItem(aAppFont.GetFontHeight() * 20, 100, EE_CHAR_FONTHEIGHT)); pItemPool->SetPoolDefaultItem( SvxFontHeightItem(aAppFont.GetFontHeight() * 20, 100, EE_CHAR_FONTHEIGHT_CJK)); pItemPool->SetPoolDefaultItem( SvxFontHeightItem(aAppFont.GetFontHeight() * 20, 100, EE_CHAR_FONTHEIGHT_CTL)); } void SQLEditView::makeEditEngine() { assert(!m_pItemPool); m_pItemPool = EditEngine::CreatePool(); SetItemPoolFont(m_pItemPool.get()); m_xEditEngine.reset(new EditEngine(m_pItemPool.get())); } void SQLEditView::SetDrawingArea(weld::DrawingArea* pDrawingArea) { WeldEditView::SetDrawingArea(pDrawingArea); EditEngine& rEditEngine = *GetEditEngine(); rEditEngine.SetDefaultHorizontalTextDirection(EEHorizontalTextDirection::L2R); rEditEngine.SetModifyHdl(LINK(this, SQLEditView, ModifyHdl)); rEditEngine.SetStatusEventHdl(LINK(this, SQLEditView, EditStatusHdl)); m_aUpdateDataTimer.SetTimeout(150); m_aUpdateDataTimer.SetInvokeHandler(LINK(this, SQLEditView, ImplUpdateDataHdl)); ImplSetFont(); // Listen for change of Font and Color Settings: // Using "this" in ctor is a little fishy, but should work here at least as // long as there are no derivations: m_listener = new ChangesListener(*this); css::uno::Reference< css::beans::XMultiPropertySet > n( officecfg::Office::Common::Font::SourceViewFont::get(), css::uno::UNO_QUERY_THROW); { std::unique_lock g(m_mutex); m_notifier = n; } css::uno::Sequence< OUString > s { "FontHeight", "FontName" }; n->addPropertiesChangeListener(s, m_listener); m_ColorConfig.AddListener(this); } SQLEditView::~SQLEditView() { css::uno::Reference< css::beans::XMultiPropertySet > n; { std::unique_lock g(m_mutex); n = m_notifier; } if (n.is()) { n->removePropertiesChangeListener(m_listener); } m_ColorConfig.RemoveListener(this); } void SQLEditView::SetTextAndUpdate(const OUString& rNewText) { SetText(rNewText); UpdateData(); } IMPL_LINK_NOARG(SQLEditView, ModifyHdl, LinkParamNone*, void) { if (m_bInUpdate) return; m_aUpdateDataTimer.Start(); } IMPL_LINK_NOARG(SQLEditView, ImplUpdateDataHdl, Timer*, void) { UpdateData(); } Color SQLEditView::GetColorValue(TokenType aToken) { return GetSyntaxHighlightColor(m_aColorConfig, m_aHighlighter.GetLanguage(), aToken); } void SQLEditView::UpdateData() { m_bInUpdate = true; EditEngine& rEditEngine = *GetEditEngine(); bool bModified = rEditEngine.IsModified(); bool bUndoEnabled = rEditEngine.IsUndoEnabled(); rEditEngine.EnableUndo(false); // syntax highlighting for (sal_Int32 nLine=0; nLine < rEditEngine.GetParagraphCount(); ++nLine) { OUString aLine( rEditEngine.GetText( nLine ) ); ESelection aAllLine(nLine, 0, nLine, EE_TEXTPOS_ALL); rEditEngine.RemoveAttribs(aAllLine, false, EE_CHAR_COLOR); rEditEngine.RemoveAttribs(aAllLine, false, EE_CHAR_WEIGHT); rEditEngine.RemoveAttribs(aAllLine, false, EE_CHAR_WEIGHT_CJK); rEditEngine.RemoveAttribs(aAllLine, false, EE_CHAR_WEIGHT_CTL); std::vector aPortions; m_aHighlighter.getHighlightPortions( aLine, aPortions ); for (auto const& portion : aPortions) { SfxItemSet aSet(rEditEngine.GetEmptyItemSet()); aSet.Put(SvxColorItem(GetColorValue(portion.tokenType), EE_CHAR_COLOR)); rEditEngine.QuickSetAttribs(aSet, ESelection(nLine, portion.nBegin, nLine, portion.nEnd)); } } rEditEngine.ClearModifyFlag(); m_bInUpdate = false; rEditEngine.EnableUndo(bUndoEnabled); if (bModified) m_aModifyLink.Call(nullptr); Invalidate(); } void SQLEditView::DoBracketHilight(sal_uInt16 nKey) { ESelection aCurrentPos = m_xEditView->GetSelection(); sal_Int32 nStartPos = aCurrentPos.nStartPos; const sal_uInt32 nStartPara = aCurrentPos.nStartPara; sal_uInt16 nCount = 0; int nChar = -1; switch (nKey) { case '\'': // no break case '"': { nChar = nKey; break; } case '}' : { nChar = '{'; break; } case ')': { nChar = '('; break; } case ']': { nChar = '['; break; } } if (nChar == -1) return; bool bUndoEnabled = m_xEditEngine->IsUndoEnabled(); m_xEditEngine->EnableUndo(false); sal_uInt32 nPara = nStartPara; do { if (nPara == nStartPara && nStartPos == 0) continue; OUString aLine( m_xEditEngine->GetText( nPara ) ); if (aLine.isEmpty()) continue; for (sal_Int32 i = (nPara==nStartPara) ? nStartPos-1 : aLine.getLength()-1; i>0; --i) { if (aLine[i] == nChar) { if (!nCount) { SfxItemSet aSet(m_xEditEngine->GetEmptyItemSet()); aSet.Put(SvxColorItem(Color(0,0,0), EE_CHAR_COLOR)); aSet.Put(SvxWeightItem(WEIGHT_ULTRABOLD, EE_CHAR_WEIGHT)); aSet.Put(SvxWeightItem(WEIGHT_ULTRABOLD, EE_CHAR_WEIGHT_CJK)); aSet.Put(SvxWeightItem(WEIGHT_ULTRABOLD, EE_CHAR_WEIGHT_CTL)); m_xEditEngine->QuickSetAttribs(aSet, ESelection(nPara, i, nPara, i + 1)); m_xEditEngine->QuickSetAttribs(aSet, ESelection(nStartPara, nStartPos, nStartPara, nStartPos)); return; } else --nCount; } if (aLine[i] == nKey) ++nCount; } } while (nPara--); m_xEditEngine->EnableUndo(bUndoEnabled); } Color SQLEditView::GetSyntaxHighlightColor(const svtools::ColorConfig& rColorConfig, HighlighterLanguage eLanguage, TokenType aToken) { Color aColor; switch (eLanguage) { case HighlighterLanguage::SQL: { switch (aToken) { case TokenType::Identifier: aColor = rColorConfig.GetColorValue(svtools::SQLIDENTIFIER).nColor; break; case TokenType::Number: aColor = rColorConfig.GetColorValue(svtools::SQLNUMBER).nColor; break; case TokenType::String: aColor = rColorConfig.GetColorValue(svtools::SQLSTRING).nColor; break; case TokenType::Operator: aColor = rColorConfig.GetColorValue(svtools::SQLOPERATOR).nColor; break; case TokenType::Keywords: aColor = rColorConfig.GetColorValue(svtools::SQLKEYWORD).nColor; break; case TokenType::Parameter: aColor = rColorConfig.GetColorValue(svtools::SQLPARAMETER).nColor; break; case TokenType::Comment: aColor = rColorConfig.GetColorValue(svtools::SQLCOMMENT).nColor; break; default: aColor = Color(0,0,0); } break; } case HighlighterLanguage::Basic: { switch (aToken) { case TokenType::Identifier: aColor = Color(255,0,0); break; case TokenType::Comment: aColor = Color(0,0,45); break; case TokenType::Number: aColor = Color(204,102,204); break; case TokenType::String: aColor = Color(0,255,45); break; case TokenType::Operator: aColor = Color(0,0,100); break; case TokenType::Keywords: aColor = Color(0,0,255); break; case TokenType::Error : aColor = Color(0,255,255); break; default: aColor = Color(0,0,0); } break; } default: aColor = Color(0,0,0); } return aColor; } bool SQLEditView::KeyInput(const KeyEvent& rKEvt) { DoBracketHilight(rKEvt.GetCharCode()); if (m_bDisableInternalUndo) { KeyFuncType eFunc = rKEvt.GetKeyCode().GetFunction(); if (eFunc == KeyFuncType::UNDO || eFunc == KeyFuncType::REDO) return false; } return WeldEditView::KeyInput(rKEvt); } bool SQLEditView::Command(const CommandEvent& rCEvt) { if (rCEvt.GetCommand() == CommandEventId::ContextMenu) { ::tools::Rectangle aRect(rCEvt.GetMousePosPixel(), Size(1, 1)); weld::Widget* pPopupParent = GetDrawingArea(); std::unique_ptr xBuilder(Application::CreateBuilder(pPopupParent, "vcl/ui/editmenu.ui")); std::unique_ptr xContextMenu(xBuilder->weld_menu("menu")); bool bEnableCut = true; bool bEnableCopy = true; bool bEnableDelete = true; bool bEnablePaste = true; bool bEnableSpecialChar = true; EditView* pEditView = GetEditView(); if (!pEditView->HasSelection()) { bEnableCut = false; bEnableCopy = false; bEnableDelete = false; } if (pEditView->IsReadOnly()) { bEnableCut = false; bEnablePaste = false; bEnableDelete = false; bEnableSpecialChar = false; } xContextMenu->set_sensitive("cut", bEnableCut); xContextMenu->set_sensitive("copy", bEnableCopy); xContextMenu->set_sensitive("delete", bEnableDelete); xContextMenu->set_sensitive("paste", bEnablePaste); xContextMenu->set_sensitive("specialchar", bEnableSpecialChar); xContextMenu->set_visible("undo", false); xContextMenu->set_visible("specialchar", vcl::GetGetSpecialCharsFunction() != nullptr); OUString sCommand = xContextMenu->popup_at_rect(pPopupParent, aRect); if (sCommand == "cut") pEditView->Cut(); else if (sCommand == "copy") pEditView->Copy(); else if (sCommand == "paste") pEditView->Paste(); else if (sCommand == "delete") pEditView->DeleteSelected(); else if (sCommand == "selectall") { sal_Int32 nPar = m_xEditEngine->GetParagraphCount(); if (nPar) { sal_Int32 nLen = m_xEditEngine->GetTextLen(nPar - 1); pEditView->SetSelection(ESelection(0, 0, nPar - 1, nLen)); } } else if (sCommand == "specialchar") { OUString aChars = vcl::GetGetSpecialCharsFunction()(pPopupParent, m_xEditEngine->GetStandardFont(0)); if (!aChars.isEmpty()) { pEditView->InsertText(aChars); } } return true; } return WeldEditView::Command(rCEvt); } void SQLEditView::EditViewScrollStateChange() { // editengine height has changed or editview scroll pos has changed SetScrollBarRange(); } void SQLEditView::SetScrollBarRange() { EditEngine *pEditEngine = GetEditEngine(); if (!pEditEngine) return; if (!m_xScrolledWindow) return; EditView* pEditView = GetEditView(); if (!pEditView) return; int nVUpper = pEditEngine->GetTextHeight(); int nVCurrentDocPos = pEditView->GetVisArea().Top(); const Size aOut(pEditView->GetOutputArea().GetSize()); int nVStepIncrement = aOut.Height() * 2 / 10; int nVPageIncrement = aOut.Height() * 8 / 10; int nVPageSize = aOut.Height(); /* limit the page size to below nUpper because gtk's gtk_scrolled_window_start_deceleration has effectively... lower = gtk_adjustment_get_lower upper = gtk_adjustment_get_upper - gtk_adjustment_get_page_size and requires that upper > lower or the deceleration animation never ends */ nVPageSize = std::min(nVPageSize, nVUpper); m_xScrolledWindow->vadjustment_configure(nVCurrentDocPos, 0, nVUpper, nVStepIncrement, nVPageIncrement, nVPageSize); } IMPL_LINK_NOARG(SQLEditView, ScrollHdl, weld::ScrolledWindow&, void) { DoScroll(); } IMPL_LINK_NOARG(SQLEditView, EditStatusHdl, EditStatus&, void) { Resize(); } void SQLEditView::DoScroll() { if (m_xEditView) { auto currentDocPos = m_xEditView->GetVisArea().Top(); auto nDiff = currentDocPos - m_xScrolledWindow->vadjustment_get_value(); // we expect SetScrollBarRange callback to be triggered by Scroll // to set where we ended up m_xEditView->Scroll(0, nDiff); } } void SQLEditView::ConfigurationChanged(utl::ConfigurationBroadcaster*, ConfigurationHints) { UpdateData(); } void SQLEditView::ImplSetFont() { // see SmEditWindow::DataChanged for a similar case SetItemPoolFont(m_pItemPool.get()); // change default font // re-create with the new font EditEngine& rEditEngine = *GetEditEngine(); OUString aTxt(rEditEngine.GetText()); rEditEngine.Clear(); SetTextAndUpdate(aTxt); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */