summaryrefslogtreecommitdiffstats
path: root/sfx2/source/notebookbar/SfxNotebookBar.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sfx2/source/notebookbar/SfxNotebookBar.cxx
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--sfx2/source/notebookbar/SfxNotebookBar.cxx584
1 files changed, 584 insertions, 0 deletions
diff --git a/sfx2/source/notebookbar/SfxNotebookBar.cxx b/sfx2/source/notebookbar/SfxNotebookBar.cxx
new file mode 100644
index 000000000..aa602ba17
--- /dev/null
+++ b/sfx2/source/notebookbar/SfxNotebookBar.cxx
@@ -0,0 +1,584 @@
+/* -*- 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/.
+ */
+
+#include <sfx2/bindings.hxx>
+#include <sfx2/viewsh.hxx>
+#include <sfx2/dispatch.hxx>
+#include <sfx2/notebookbar/SfxNotebookBar.hxx>
+#include <vcl/notebookbar/notebookbar.hxx>
+#include <vcl/syswin.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/lok.hxx>
+#include <com/sun/star/frame/UnknownModuleException.hpp>
+#include <com/sun/star/frame/XLayoutManager.hpp>
+#include <officecfg/Office/UI/ToolbarMode.hxx>
+#include <com/sun/star/frame/XModuleManager.hpp>
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <unotools/confignode.hxx>
+#include <comphelper/types.hxx>
+#include <framework/addonsoptions.hxx>
+#include <vcl/notebookbar/NotebookBarAddonsMerger.hxx>
+#include <vector>
+#include <map>
+#include <vcl/WeldedTabbedNotebookbar.hxx>
+
+using namespace sfx2;
+using namespace css::uno;
+using namespace css::ui;
+using namespace css;
+
+constexpr OUStringLiteral MENUBAR_STR = u"private:resource/menubar/menubar";
+
+const char MERGE_NOTEBOOKBAR_URL[] = "URL";
+
+bool SfxNotebookBar::m_bLock = false;
+bool SfxNotebookBar::m_bHide = false;
+std::map<const SfxViewShell*, std::shared_ptr<WeldedTabbedNotebookbar>> SfxNotebookBar::m_pNotebookBarWeldedWrapper;
+
+static void NotebookbarAddonValues(
+ std::vector<Image>& aImageValues,
+ std::vector<css::uno::Sequence<css::uno::Sequence<css::beans::PropertyValue>>>&
+ aExtensionValues)
+{
+ framework::AddonsOptions aAddonsItems;
+
+ for (int nIdx = 0; nIdx < aAddonsItems.GetAddonsNotebookBarCount(); nIdx++)
+ {
+ const css::uno::Sequence<css::uno::Sequence<css::beans::PropertyValue>> aExtension
+ = aAddonsItems.GetAddonsNotebookBarPart(nIdx);
+ for (const css::uno::Sequence<css::beans::PropertyValue>& rExtensionVal : aExtension)
+ {
+ Image aImage;
+ bool isBigImage = true;
+ for (const auto& rProp : rExtensionVal)
+ {
+ if (rProp.Name == MERGE_NOTEBOOKBAR_URL)
+ {
+ OUString sImage;
+ rProp.Value >>= sImage;
+ aImage = Image(aAddonsItems.GetImageFromURL(sImage, isBigImage));
+ }
+ }
+ aImageValues.push_back(aImage);
+ }
+ aExtensionValues.push_back(aExtension);
+ }
+}
+
+static Reference<frame::XLayoutManager> lcl_getLayoutManager( const Reference<frame::XFrame>& xFrame )
+{
+ css::uno::Reference<css::frame::XLayoutManager> xLayoutManager;
+
+ if (xFrame.is())
+ {
+ Reference<css::beans::XPropertySet> xPropSet(xFrame, UNO_QUERY);
+
+ if (xPropSet.is())
+ {
+ Any aValue = xPropSet->getPropertyValue("LayoutManager");
+ aValue >>= xLayoutManager;
+ }
+ }
+
+ return xLayoutManager;
+}
+
+static OUString lcl_getAppName( vcl::EnumContext::Application eApp )
+{
+ switch ( eApp )
+ {
+ case vcl::EnumContext::Application::Writer:
+ return "Writer";
+ case vcl::EnumContext::Application::Calc:
+ return "Calc";
+ case vcl::EnumContext::Application::Impress:
+ return "Impress";
+ case vcl::EnumContext::Application::Draw:
+ return "Draw";
+ case vcl::EnumContext::Application::Formula:
+ return "Formula";
+ default:
+ return OUString();
+ }
+}
+
+static void lcl_setNotebookbarFileName( vcl::EnumContext::Application eApp, const OUString& sFileName )
+{
+ std::shared_ptr<comphelper::ConfigurationChanges> aBatch(
+ comphelper::ConfigurationChanges::create() );
+ switch ( eApp )
+ {
+ case vcl::EnumContext::Application::Writer:
+ officecfg::Office::UI::ToolbarMode::ActiveWriter::set( sFileName, aBatch );
+ break;
+ case vcl::EnumContext::Application::Calc:
+ officecfg::Office::UI::ToolbarMode::ActiveCalc::set( sFileName, aBatch );
+ break;
+ case vcl::EnumContext::Application::Impress:
+ officecfg::Office::UI::ToolbarMode::ActiveImpress::set( sFileName, aBatch );
+ break;
+ case vcl::EnumContext::Application::Draw:
+ officecfg::Office::UI::ToolbarMode::ActiveDraw::set( sFileName, aBatch );
+ break;
+ default:
+ break;
+ }
+ aBatch->commit();
+}
+
+static OUString lcl_getNotebookbarFileName( vcl::EnumContext::Application eApp )
+{
+ switch ( eApp )
+ {
+ case vcl::EnumContext::Application::Writer:
+ return officecfg::Office::UI::ToolbarMode::ActiveWriter::get();
+ case vcl::EnumContext::Application::Calc:
+ return officecfg::Office::UI::ToolbarMode::ActiveCalc::get();
+ case vcl::EnumContext::Application::Impress:
+ return officecfg::Office::UI::ToolbarMode::ActiveImpress::get();
+ case vcl::EnumContext::Application::Draw:
+ return officecfg::Office::UI::ToolbarMode::ActiveDraw::get();
+
+ default:
+ break;
+ }
+ return OUString();
+}
+
+static utl::OConfigurationTreeRoot lcl_getCurrentImplConfigRoot()
+{
+ return utl::OConfigurationTreeRoot(::comphelper::getProcessComponentContext(),
+ "org.openoffice.Office.UI.ToolbarMode/",
+ true);
+}
+
+static utl::OConfigurationNode lcl_getCurrentImplConfigNode(const Reference<css::frame::XFrame>& xFrame,
+ utl::OConfigurationTreeRoot const & rNotebookbarNode )
+{
+ if (!rNotebookbarNode.isValid())
+ return utl::OConfigurationNode();
+
+ const Reference<frame::XModuleManager> xModuleManager = frame::ModuleManager::create( ::comphelper::getProcessComponentContext() );
+
+ vcl::EnumContext::Application eApp = vcl::EnumContext::GetApplicationEnum( xModuleManager->identify( xFrame ) );
+ OUString aActive = lcl_getNotebookbarFileName( eApp );
+
+ const utl::OConfigurationNode aImplsNode = rNotebookbarNode.openNode("Applications/" + lcl_getAppName( eApp) + "/Modes");
+ const Sequence<OUString> aModeNodeNames( aImplsNode.getNodeNames() );
+
+ for ( const auto& rModeNodeName : aModeNodeNames )
+ {
+ const utl::OConfigurationNode aImplNode( aImplsNode.openNode( rModeNodeName ) );
+ if ( !aImplNode.isValid() )
+ continue;
+
+ OUString aCommandArg = comphelper::getString( aImplNode.getNodeValue( "CommandArg" ) );
+
+ if ( aCommandArg == aActive )
+ {
+ return aImplNode;
+ }
+ }
+
+ return utl::OConfigurationNode();
+}
+
+void SfxNotebookBar::CloseMethod(SfxBindings& rBindings)
+{
+ SfxFrame& rFrame = rBindings.GetDispatcher_Impl()->GetFrame()->GetFrame();
+ CloseMethod(rFrame.GetSystemWindow());
+}
+
+void SfxNotebookBar::CloseMethod(SystemWindow* pSysWindow)
+{
+ if (pSysWindow)
+ {
+ RemoveListeners(pSysWindow);
+ if(pSysWindow->GetNotebookBar())
+ pSysWindow->CloseNotebookBar();
+ if (SfxViewFrame::Current())
+ SfxNotebookBar::ShowMenubar(SfxViewFrame::Current(), true);
+ }
+}
+
+void SfxNotebookBar::LockNotebookBar()
+{
+ m_bHide = true;
+ m_bLock = true;
+}
+
+void SfxNotebookBar::UnlockNotebookBar()
+{
+ m_bHide = false;
+ m_bLock = false;
+}
+
+bool SfxNotebookBar::IsActive()
+{
+ if (m_bHide)
+ return false;
+
+ vcl::EnumContext::Application eApp = vcl::EnumContext::Application::Any;
+
+ if (SfxViewFrame::Current())
+ {
+ const Reference<frame::XFrame>& xFrame = SfxViewFrame::Current()->GetFrame().GetFrameInterface();
+ if (!xFrame.is())
+ return false;
+
+ const Reference<frame::XModuleManager> xModuleManager = frame::ModuleManager::create( ::comphelper::getProcessComponentContext() );
+ try
+ {
+ eApp = vcl::EnumContext::GetApplicationEnum(xModuleManager->identify(xFrame));
+ }
+ catch (css::frame::UnknownModuleException& e)
+ {
+ SAL_WARN("sfx.appl", "SfxNotebookBar::IsActive(): " + e.Message);
+ return false;
+ }
+ }
+ else
+ return false;
+
+ OUString appName(lcl_getAppName( eApp ));
+
+ if (appName.isEmpty())
+ return false;
+
+
+ OUString aPath = "org.openoffice.Office.UI.ToolbarMode/Applications/" + appName;
+
+ const utl::OConfigurationTreeRoot aAppNode(
+ ::comphelper::getProcessComponentContext(),
+ aPath,
+ false);
+ if ( !aAppNode.isValid() )
+ return false;
+
+ OUString aActive = comphelper::getString( aAppNode.getNodeValue( "Active" ) );
+
+ if (comphelper::LibreOfficeKit::isActive() && aActive == "notebookbar_online.ui")
+ return true;
+
+ const utl::OConfigurationNode aModesNode = aAppNode.openNode("Modes");
+ const Sequence<OUString> aModeNodeNames( aModesNode.getNodeNames() );
+
+ for ( const auto& rModeNodeName : aModeNodeNames )
+ {
+ const utl::OConfigurationNode aModeNode( aModesNode.openNode( rModeNodeName ) );
+ if ( !aModeNode.isValid() )
+ continue;
+
+ OUString aCommandArg = comphelper::getString( aModeNode.getNodeValue( "CommandArg" ) );
+
+ if ( aCommandArg == aActive )
+ {
+ return comphelper::getBOOL( aModeNode.getNodeValue( "HasNotebookbar" ) );
+ }
+ }
+ return false;
+}
+
+void SfxNotebookBar::ResetActiveToolbarModeToDefault(vcl::EnumContext::Application eApp)
+{
+ const OUString appName( lcl_getAppName( eApp ) );
+
+ if ( appName.isEmpty() )
+ return;
+
+ const OUString aPath = "org.openoffice.Office.UI.ToolbarMode/Applications/" + appName;
+
+ utl::OConfigurationTreeRoot aAppNode(
+ ::comphelper::getProcessComponentContext(),
+ aPath,
+ true);
+ if ( !aAppNode.isValid() )
+ return;
+
+ aAppNode.setNodeValue( "Active", Any( OUString( "Default" ) ) );
+ aAppNode.commit();
+}
+
+void SfxNotebookBar::ExecMethod(SfxBindings& rBindings, const OUString& rUIName)
+{
+ // Save active UI file name
+ if ( !rUIName.isEmpty() && SfxViewFrame::Current() )
+ {
+ const Reference<frame::XFrame>& xFrame = SfxViewFrame::Current()->GetFrame().GetFrameInterface();
+ if (xFrame.is())
+ {
+ const Reference<frame::XModuleManager> xModuleManager = frame::ModuleManager::create( ::comphelper::getProcessComponentContext() );
+ vcl::EnumContext::Application eApp = vcl::EnumContext::GetApplicationEnum(xModuleManager->identify(xFrame));
+ lcl_setNotebookbarFileName( eApp, rUIName );
+ }
+ }
+
+ // trigger the StateMethod
+ rBindings.Invalidate(SID_NOTEBOOKBAR);
+ rBindings.Update();
+}
+
+bool SfxNotebookBar::StateMethod(SfxBindings& rBindings, std::u16string_view rUIFile,
+ bool bReloadNotebookbar)
+{
+ SfxFrame& rFrame = rBindings.GetDispatcher_Impl()->GetFrame()->GetFrame();
+ return StateMethod(rFrame.GetSystemWindow(), rFrame.GetFrameInterface(), rUIFile,
+ bReloadNotebookbar);
+}
+
+bool SfxNotebookBar::StateMethod(SystemWindow* pSysWindow,
+ const Reference<css::frame::XFrame>& xFrame,
+ std::u16string_view rUIFile, bool bReloadNotebookbar)
+{
+ if (!pSysWindow)
+ {
+ if (SfxViewFrame::Current() && SfxViewFrame::Current()->GetWindow().GetSystemWindow())
+ pSysWindow = SfxViewFrame::Current()->GetWindow().GetSystemWindow();
+ else
+ return false;
+ }
+
+ if (IsActive())
+ {
+ css::uno::Reference<css::uno::XComponentContext> xContext = comphelper::getProcessComponentContext();
+ const Reference<frame::XModuleManager> xModuleManager = frame::ModuleManager::create( xContext );
+ OUString aModuleName = xModuleManager->identify( xFrame );
+ vcl::EnumContext::Application eApp = vcl::EnumContext::GetApplicationEnum( aModuleName );
+
+ OUString sFile;
+ if (comphelper::LibreOfficeKit::isActive())
+ sFile = "notebookbar_online.ui";
+ else
+ sFile = lcl_getNotebookbarFileName( eApp );
+
+ OUString sNewFile = rUIFile + sFile;
+ OUString sCurrentFile;
+ VclPtr<NotebookBar> pNotebookBar = pSysWindow->GetNotebookBar();
+ if ( pNotebookBar )
+ sCurrentFile = pNotebookBar->GetUIFilePath();
+
+ bool bChangedFile = sNewFile != sCurrentFile;
+
+ if ((!sFile.isEmpty() && bChangedFile) || !pNotebookBar || !pNotebookBar->IsVisible()
+ || bReloadNotebookbar || comphelper::LibreOfficeKit::isActive())
+ {
+ const SfxViewShell* pViewShell = SfxViewShell::Current();
+
+ // Notebookbar was loaded too early what caused:
+ // * in LOK: Paste Special feature was incorrectly initialized
+ // Skip first request so Notebookbar will be initialized after document was loaded
+ static std::map<const void*, bool> bSkippedFirstInit;
+ if (comphelper::LibreOfficeKit::isActive() && eApp == vcl::EnumContext::Application::Writer
+ && bSkippedFirstInit.find(pViewShell) == bSkippedFirstInit.end())
+ {
+ bSkippedFirstInit[pViewShell] = true;
+ ResetActiveToolbarModeToDefault(eApp);
+ return false;
+ }
+
+ RemoveListeners(pSysWindow);
+
+ OUString aBuf = rUIFile + sFile;
+
+ //Addons For Notebookbar
+ std::vector<Image> aImageValues;
+ std::vector<css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > > > aExtensionValues;
+ NotebookBarAddonsItem aNotebookBarAddonsItem;
+ NotebookbarAddonValues(aImageValues , aExtensionValues);
+ aNotebookBarAddonsItem.aAddonValues = aExtensionValues;
+ aNotebookBarAddonsItem.aImageValues = aImageValues;
+
+ // setup if necessary
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // update the current LOK language and locale for the dialog tunneling
+ comphelper::LibreOfficeKit::setLanguageTag(pViewShell->GetLOKLanguageTag());
+ comphelper::LibreOfficeKit::setLocale(pViewShell->GetLOKLocale());
+ }
+
+ pSysWindow->SetNotebookBar(aBuf, xFrame, aNotebookBarAddonsItem , bReloadNotebookbar);
+ pNotebookBar = pSysWindow->GetNotebookBar();
+ pNotebookBar->Show();
+
+
+ bool hasWeldedWrapper = m_pNotebookBarWeldedWrapper.find(pViewShell) != m_pNotebookBarWeldedWrapper.end();
+ if ((!hasWeldedWrapper || bReloadNotebookbar) && pNotebookBar->IsWelded())
+ {
+ sal_uInt64 nWindowId = reinterpret_cast<sal_uInt64>(pViewShell);
+ m_pNotebookBarWeldedWrapper.emplace(std::make_pair(pViewShell,
+ new WeldedTabbedNotebookbar(pNotebookBar->GetMainContainer(),
+ pNotebookBar->GetUIFilePath(),
+ xFrame,
+ nWindowId)));
+ pNotebookBar->SetDisposeCallback(LINK(nullptr, SfxNotebookBar, VclDisposeHdl), pViewShell);
+ }
+
+ pNotebookBar->GetParent()->Resize();
+
+ utl::OConfigurationTreeRoot aRoot(lcl_getCurrentImplConfigRoot());
+ const utl::OConfigurationNode aModeNode(lcl_getCurrentImplConfigNode(xFrame, aRoot));
+ SfxNotebookBar::ShowMenubar( comphelper::getBOOL( aModeNode.getNodeValue( "HasMenubar" ) ) );
+
+ SfxViewFrame* pView = SfxViewFrame::Current();
+
+ if(pView)
+ {
+ pNotebookBar->ControlListenerForCurrentController(true);
+ }
+ }
+
+ return true;
+ }
+ else if (auto pNotebookBar = pSysWindow->GetNotebookBar())
+ {
+ vcl::Window* pParent = pNotebookBar->GetParent();
+ RemoveListeners(pSysWindow);
+ pSysWindow->CloseNotebookBar();
+ pParent->Resize();
+ SfxNotebookBar::ShowMenubar(true);
+ }
+
+ return false;
+}
+
+void SfxNotebookBar::RemoveListeners(SystemWindow const * pSysWindow)
+{
+ if (auto pNotebookBar = pSysWindow->GetNotebookBar())
+ {
+ pNotebookBar->StopListeningAllControllers();
+ }
+}
+
+void SfxNotebookBar::ShowMenubar(bool bShow)
+{
+ if (m_bLock)
+ return;
+
+ m_bLock = true;
+
+ Reference<frame::XFrame> xFrame;
+ vcl::EnumContext::Application eCurrentApp = vcl::EnumContext::Application::NONE;
+ uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext();
+ const Reference<frame::XModuleManager> xModuleManager = frame::ModuleManager::create( xContext );
+
+ if ( SfxViewFrame::Current() )
+ {
+ xFrame = SfxViewFrame::Current()->GetFrame().GetFrameInterface();
+ eCurrentApp = vcl::EnumContext::GetApplicationEnum( xModuleManager->identify( xFrame ) );
+ }
+
+ SfxViewFrame* pViewFrame = SfxViewFrame::GetFirst();
+ while( pViewFrame )
+ {
+ xFrame = pViewFrame->GetFrame().GetFrameInterface();
+ if ( xFrame.is() )
+ {
+ vcl::EnumContext::Application eApp =
+ vcl::EnumContext::GetApplicationEnum( xModuleManager->identify( xFrame ) );
+
+ if ( eApp == eCurrentApp )
+ {
+ const Reference<frame::XLayoutManager>& xLayoutManager =
+ lcl_getLayoutManager( xFrame );
+
+ if (xLayoutManager.is())
+ {
+ if (xLayoutManager->getElement(MENUBAR_STR).is())
+ {
+ if (xLayoutManager->isElementVisible(MENUBAR_STR) && !bShow)
+ xLayoutManager->hideElement(MENUBAR_STR);
+ else if(!xLayoutManager->isElementVisible(MENUBAR_STR) && bShow)
+ xLayoutManager->showElement(MENUBAR_STR);
+ }
+ }
+ }
+ }
+
+ pViewFrame = SfxViewFrame::GetNext( *pViewFrame );
+ }
+ m_bLock = false;
+}
+
+void SfxNotebookBar::ShowMenubar(SfxViewFrame const * pViewFrame, bool bShow)
+{
+ if (m_bLock)
+ return;
+
+ m_bLock = true;
+
+ Reference<frame::XFrame> xFrame = pViewFrame->GetFrame().GetFrameInterface();
+ if (xFrame.is())
+ {
+ const Reference<frame::XLayoutManager>& xLayoutManager = lcl_getLayoutManager(xFrame);
+ if (xLayoutManager.is())
+ {
+ if (xLayoutManager->getElement(MENUBAR_STR).is())
+ {
+ if (xLayoutManager->isElementVisible(MENUBAR_STR) && !bShow)
+ xLayoutManager->hideElement(MENUBAR_STR);
+ else if (!xLayoutManager->isElementVisible(MENUBAR_STR) && bShow)
+ xLayoutManager->showElement(MENUBAR_STR);
+ }
+ }
+ }
+ m_bLock = false;
+}
+
+void SfxNotebookBar::ToggleMenubar()
+{
+ if (!SfxViewFrame::Current())
+ return;
+
+ const Reference<frame::XFrame>& xFrame = SfxViewFrame::Current()->GetFrame().GetFrameInterface();
+ if (!xFrame.is())
+ return;
+
+ const Reference<frame::XLayoutManager>& xLayoutManager =
+ lcl_getLayoutManager(xFrame);
+
+ bool bShow = true;
+ if (xLayoutManager.is() && xLayoutManager->getElement(MENUBAR_STR).is())
+ {
+ if (xLayoutManager->isElementVisible(MENUBAR_STR))
+ {
+ SfxNotebookBar::ShowMenubar(false);
+ bShow = false;
+ }
+ else
+ SfxNotebookBar::ShowMenubar(true);
+ }
+
+ // Save menubar settings
+ if (IsActive())
+ {
+ utl::OConfigurationTreeRoot aRoot(lcl_getCurrentImplConfigRoot());
+ utl::OConfigurationNode aModeNode(lcl_getCurrentImplConfigNode(xFrame, aRoot));
+ aModeNode.setNodeValue( "HasMenubar", toAny<bool>( bShow ) );
+ aRoot.commit();
+ }
+}
+
+void SfxNotebookBar::ReloadNotebookBar(std::u16string_view sUIPath)
+{
+ if (SfxNotebookBar::IsActive())
+ {
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ sfx2::SfxNotebookBar::StateMethod(pViewShell->GetViewFrame()->GetBindings(), sUIPath, true);
+ }
+}
+
+IMPL_STATIC_LINK(SfxNotebookBar, VclDisposeHdl, const SfxViewShell*, pViewShell, void)
+{
+ m_pNotebookBarWeldedWrapper.erase(pViewShell);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */