Kaydet (Commit) 021c3a0e authored tarafından Tamás Zolnai's avatar Tamás Zolnai

Unfloat: Introduce Unfloat table button as a frame control

Use the same button style and color what LO uses for header/footer
button.
Later it might be a good idea to have a separate configurable color
for this button, but for now it's seems unneeded to introduce a
new configure option for this.

Change-Id: Idf86aaa44e29846a2ee211a81fe5376b4335bb05
Reviewed-on: https://gerrit.libreoffice.org/65818
Tested-by: Jenkins
Reviewed-by: 's avatarTamás Zolnai <tamas.zolnai@collabora.com>
üst d02f8977
...@@ -607,6 +607,7 @@ $(eval $(call gb_Library_add_exception_objects,sw,\ ...@@ -607,6 +607,7 @@ $(eval $(call gb_Library_add_exception_objects,sw,\
sw/source/uibase/docvw/AnnotationWin2 \ sw/source/uibase/docvw/AnnotationWin2 \
sw/source/uibase/docvw/DashedLine \ sw/source/uibase/docvw/DashedLine \
sw/source/uibase/docvw/FrameControlsManager \ sw/source/uibase/docvw/FrameControlsManager \
sw/source/uibase/docvw/FloatingTableButton \
sw/source/uibase/docvw/PageBreakWin \ sw/source/uibase/docvw/PageBreakWin \
sw/source/uibase/docvw/OverlayRanges \ sw/source/uibase/docvw/OverlayRanges \
sw/source/uibase/docvw/PostItMgr \ sw/source/uibase/docvw/PostItMgr \
......
...@@ -227,8 +227,6 @@ private: ...@@ -227,8 +227,6 @@ private:
SAL_DLLPRIVATE bool ImpEndCreate(); SAL_DLLPRIVATE bool ImpEndCreate();
SAL_DLLPRIVATE static ObjCntType GetObjCntType( const SdrObject& rObj );
/// Methods for copying of draw objects. /// Methods for copying of draw objects.
SAL_DLLPRIVATE bool CopyDrawSel( SwFEShell* pDestShell, const Point& rSttPt, SAL_DLLPRIVATE bool CopyDrawSel( SwFEShell* pDestShell, const Point& rSttPt,
const Point& rInsPt, bool bIsMove, const Point& rInsPt, bool bIsMove,
...@@ -311,6 +309,7 @@ public: ...@@ -311,6 +309,7 @@ public:
* drag&drop of controls into header */ * drag&drop of controls into header */
bool IsSelContainsControl() const; bool IsSelContainsControl() const;
static ObjCntType GetObjCntType( const SdrObject& rObj );
ObjCntType GetObjCntType( const Point &rPt, SdrObject *&rpObj ) const; ObjCntType GetObjCntType( const Point &rPt, SdrObject *&rpObj ) const;
ObjCntType GetObjCntTypeOfSelection() const; ObjCntType GetObjCntTypeOfSelection() const;
......
...@@ -1206,6 +1206,8 @@ ...@@ -1206,6 +1206,8 @@
#define STR_DELETE_FOOTER NC_("STR_DELETE_FOOTER", "Delete Footer...") #define STR_DELETE_FOOTER NC_("STR_DELETE_FOOTER", "Delete Footer...")
#define STR_FORMAT_FOOTER NC_("STR_FORMAT_FOOTER", "Format Footer...") #define STR_FORMAT_FOOTER NC_("STR_FORMAT_FOOTER", "Format Footer...")
#define STR_UNFLOAT_TABLE NC_("STR_UNFLOAT_TABLE", "Un-float Table")
#define STR_GRFILTER_OPENERROR NC_("STR_GRFILTER_OPENERROR", "Image file cannot be opened") #define STR_GRFILTER_OPENERROR NC_("STR_GRFILTER_OPENERROR", "Image file cannot be opened")
#define STR_GRFILTER_IOERROR NC_("STR_GRFILTER_IOERROR", "Image file cannot be read") #define STR_GRFILTER_IOERROR NC_("STR_GRFILTER_IOERROR", "Image file cannot be read")
#define STR_GRFILTER_FORMATERROR NC_("STR_GRFILTER_FORMATERROR", "Unknown image format") #define STR_GRFILTER_FORMATERROR NC_("STR_GRFILTER_FORMATERROR", "Unknown image format")
......
...@@ -245,7 +245,8 @@ enum FrameControlType ...@@ -245,7 +245,8 @@ enum FrameControlType
{ {
PageBreak, PageBreak,
Header, Header,
Footer Footer,
FloatingTable
}; };
#endif #endif
......
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* 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 <FloatingTableButton.hxx>
#include <HeaderFooterWin.hxx>
#include <edtwin.hxx>
#include <strings.hrc>
#include <vcl/metric.hxx>
#include <viewopt.hxx>
#include <drawinglayer/primitive2d/textprimitive2d.hxx>
#include <drawinglayer/processor2d/baseprocessor2d.hxx>
#include <drawinglayer/attribute/fontattribute.hxx>
#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <drawinglayer/processor2d/processorfromoutputdevice.hxx>
#include <basegfx/vector/b2dvector.hxx>
#define TEXT_PADDING 3
#define BOX_DISTANCE 3
#define BUTTON_WIDTH 12
FloatingTableButton::FloatingTableButton(SwEditWin* pEditWin, const SwFrame* pFrame)
: SwFrameMenuButtonBase(pEditWin, pFrame)
, m_sLabel(SwResId(STR_UNFLOAT_TABLE))
{
}
FloatingTableButton::~FloatingTableButton() { disposeOnce(); }
void FloatingTableButton::SetOffset(Point aBottomRightPixel)
{
// Compute the text size and get the box position & size from it
tools::Rectangle aTextRect;
GetTextBoundRect(aTextRect, m_sLabel);
tools::Rectangle aTextPxRect = LogicToPixel(aTextRect);
FontMetric aFontMetric = GetFontMetric(GetFont());
Size aBoxSize(aTextPxRect.GetWidth() + BUTTON_WIDTH + TEXT_PADDING * 2,
aFontMetric.GetLineHeight() + TEXT_PADDING * 2);
Point aBoxPos(aBottomRightPixel.X() - aBoxSize.Width() - BOX_DISTANCE,
aBottomRightPixel.Y() - aBoxSize.Height());
if (AllSettings::GetLayoutRTL())
{
aBoxPos.setX(aBottomRightPixel.X() + BOX_DISTANCE);
}
// Set the position & Size of the window
SetPosSizePixel(aBoxPos, aBoxSize);
}
void FloatingTableButton::MouseButtonDown(const MouseEvent& /*rMEvt*/)
{
// Move the table outside of the frame
}
void FloatingTableButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
{
SetMapMode(MapMode(MapUnit::MapPixel));
drawinglayer::primitive2d::Primitive2DContainer aSeq;
const ::tools::Rectangle aRect(
::tools::Rectangle(Point(0, 0), rRenderContext.PixelToLogic(GetSizePixel())));
// Create button
SwFrameButtonPainter::PaintButton(aSeq, aRect, false);
// Create the text primitive
basegfx::BColor aLineColor = SwViewOption::GetHeaderFooterMarkColor().getBColor();
basegfx::B2DVector aFontSize;
drawinglayer::attribute::FontAttribute aFontAttr
= drawinglayer::primitive2d::getFontAttributeFromVclFont(
aFontSize, rRenderContext.GetFont(), false, false);
FontMetric aFontMetric = rRenderContext.GetFontMetric(rRenderContext.GetFont());
double nTextOffsetY = aFontMetric.GetAscent() + TEXT_PADDING;
double nTextOffsetX = std::abs(aRect.GetWidth() - rRenderContext.GetTextWidth(m_sLabel)) / 2.0;
Point aTextPos(nTextOffsetX, nTextOffsetY);
basegfx::B2DHomMatrix aTextMatrix(basegfx::utils::createScaleTranslateB2DHomMatrix(
aFontSize.getX(), aFontSize.getY(), static_cast<double>(aTextPos.X()),
static_cast<double>(aTextPos.Y())));
aSeq.push_back(drawinglayer::primitive2d::Primitive2DReference(
new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
aTextMatrix, m_sLabel, 0, m_sLabel.getLength(), std::vector<double>(), aFontAttr,
css::lang::Locale(), aLineColor)));
// Create the processor and process the primitives
const drawinglayer::geometry::ViewInformation2D aNewViewInfos;
std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor(
drawinglayer::processor2d::createBaseProcessor2DFromOutputDevice(rRenderContext,
aNewViewInfos));
pProcessor->process(aSeq);
}
void FloatingTableButton::ShowAll(bool bShow) { Show(bShow); }
bool FloatingTableButton::Contains(const Point& rDocPt) const
{
::tools::Rectangle aRect(GetPosPixel(), GetSizePixel());
if (aRect.IsInside(rDocPt))
return true;
return false;
}
void FloatingTableButton::SetReadonly(bool bReadonly) { ShowAll(!bReadonly); }
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
\ No newline at end of file
...@@ -11,7 +11,9 @@ ...@@ -11,7 +11,9 @@
#include <FrameControlsManager.hxx> #include <FrameControlsManager.hxx>
#include <HeaderFooterWin.hxx> #include <HeaderFooterWin.hxx>
#include <PageBreakWin.hxx> #include <PageBreakWin.hxx>
#include <FloatingTableButton.hxx>
#include <pagefrm.hxx> #include <pagefrm.hxx>
#include <flyfrm.hxx>
#include <viewopt.hxx> #include <viewopt.hxx>
#include <view.hxx> #include <view.hxx>
#include <wrtsh.hxx> #include <wrtsh.hxx>
...@@ -137,6 +139,39 @@ void SwFrameControlsManager::SetPageBreakControl( const SwPageFrame* pPageFrame ...@@ -137,6 +139,39 @@ void SwFrameControlsManager::SetPageBreakControl( const SwPageFrame* pPageFrame
pControl->ShowAll( true ); pControl->ShowAll( true );
} }
void SwFrameControlsManager::SetFloatingTableButton( const SwFlyFrame* pFlyFrame, bool bShow, Point aBottomRightPixel )
{
if(pFlyFrame == nullptr)
return;
// Check if we already have the control
SwFrameControlPtr pControl;
SwFrameControlPtrMap& rControls = m_aControls[FloatingTable];
SwFrameControlPtrMap::iterator lb = rControls.lower_bound(pFlyFrame);
if (lb != rControls.end() && !(rControls.key_comp()(pFlyFrame, lb->first)))
pControl = lb->second;
else if (!bShow) // Do not create the control when it's not shown
return;
else
{
SwFrameControlPtr pNewControl( new SwFrameControl(
VclPtr<FloatingTableButton>::Create( m_pEditWin, pFlyFrame ).get() ) );
const SwViewOption* pViewOpt = m_pEditWin->GetView().GetWrtShell().GetViewOptions();
pNewControl->SetReadonly( pViewOpt->IsReadonly() );
rControls.insert(lb, make_pair(pFlyFrame, pNewControl));
pControl.swap( pNewControl );
}
FloatingTableButton* pButton = dynamic_cast<FloatingTableButton*>(pControl->GetWindow());
assert(pButton != nullptr);
pButton->SetOffset(aBottomRightPixel);
pControl->ShowAll( bShow );
}
SwFrameMenuButtonBase::SwFrameMenuButtonBase( SwEditWin* pEditWin, const SwFrame* pFrame ) : SwFrameMenuButtonBase::SwFrameMenuButtonBase( SwEditWin* pEditWin, const SwFrame* pFrame ) :
MenuButton( pEditWin, WB_DIALOGCONTROL ), MenuButton( pEditWin, WB_DIALOGCONTROL ),
m_pEditWin( pEditWin ), m_pEditWin( pEditWin ),
...@@ -146,7 +181,13 @@ SwFrameMenuButtonBase::SwFrameMenuButtonBase( SwEditWin* pEditWin, const SwFrame ...@@ -146,7 +181,13 @@ SwFrameMenuButtonBase::SwFrameMenuButtonBase( SwEditWin* pEditWin, const SwFrame
const SwPageFrame* SwFrameMenuButtonBase::GetPageFrame() const SwPageFrame* SwFrameMenuButtonBase::GetPageFrame()
{ {
return static_cast< const SwPageFrame * >( m_pFrame ); if (m_pFrame->IsPageFrame())
return static_cast<const SwPageFrame*>( m_pFrame );
if (m_pFrame->IsFlyFrame())
return static_cast<const SwFlyFrame*>(m_pFrame)->GetAnchorFrame()->FindPageFrame();
return m_pFrame->FindPageFrame();
} }
void SwFrameMenuButtonBase::dispose() void SwFrameMenuButtonBase::dispose()
......
...@@ -79,7 +79,7 @@ namespace ...@@ -79,7 +79,7 @@ namespace
return basegfx::utils::hsl2rgb( aHslDark ); return basegfx::utils::hsl2rgb( aHslDark );
} }
B2DPolygon lcl_GetPolygon( const ::tools::Rectangle& rRect, bool bHeader ) B2DPolygon lcl_GetPolygon( const ::tools::Rectangle& rRect, bool bOnTop )
{ {
const double nRadius = 3; const double nRadius = 3;
const double nKappa((M_SQRT2 - 1.0) * 4.0 / 3.0); const double nKappa((M_SQRT2 - 1.0) * 4.0 / 3.0);
...@@ -111,7 +111,7 @@ namespace ...@@ -111,7 +111,7 @@ namespace
aPolygon.append( B2DPoint( rRect.Right(), rRect.Top() ) ); aPolygon.append( B2DPoint( rRect.Right(), rRect.Top() ) );
if ( !bHeader ) if ( !bOnTop )
{ {
B2DRectangle aBRect( rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom() ); B2DRectangle aBRect( rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom() );
B2DHomMatrix aRotation = createRotateAroundPoint( B2DHomMatrix aRotation = createRotateAroundPoint(
...@@ -123,6 +123,42 @@ namespace ...@@ -123,6 +123,42 @@ namespace
} }
} }
void SwFrameButtonPainter::PaintButton(drawinglayer::primitive2d::Primitive2DContainer& rSeq,
const tools::Rectangle& rRect, bool bOnTop)
{
rSeq.clear();
B2DPolygon aPolygon = lcl_GetPolygon(rRect, bOnTop);
// Colors
basegfx::BColor aLineColor = SwViewOption::GetHeaderFooterMarkColor().getBColor();
basegfx::BColor aFillColor = lcl_GetFillColor(aLineColor);
basegfx::BColor aLighterColor = lcl_GetLighterGradientColor(aFillColor);
const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
if (rSettings.GetHighContrastMode())
{
aFillColor = rSettings.GetDialogColor().getBColor();
aLineColor = rSettings.GetDialogTextColor().getBColor();
rSeq.push_back(drawinglayer::primitive2d::Primitive2DReference(
new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(B2DPolyPolygon(aPolygon), aFillColor)));
}
else
{
B2DRectangle aGradientRect(rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom());
double nAngle = M_PI;
if (bOnTop)
nAngle = 0;
FillGradientAttribute aFillAttrs(drawinglayer::attribute::GradientStyle::Linear, 0.0, 0.0, 0.0, nAngle, aLighterColor, aFillColor, 10);
rSeq.push_back(drawinglayer::primitive2d::Primitive2DReference(
new drawinglayer::primitive2d::FillGradientPrimitive2D(aGradientRect, aFillAttrs)));
}
// Create the border lines primitive
rSeq.push_back(drawinglayer::primitive2d::Primitive2DReference(
new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(aPolygon, aLineColor)));
}
SwHeaderFooterWin::SwHeaderFooterWin( SwEditWin* pEditWin, const SwFrame *pFrame, bool bHeader ) : SwHeaderFooterWin::SwHeaderFooterWin( SwEditWin* pEditWin, const SwFrame *pFrame, bool bHeader ) :
SwFrameMenuButtonBase( pEditWin, pFrame ), SwFrameMenuButtonBase( pEditWin, pFrame ),
m_aBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "modules/swriter/ui/headerfootermenu.ui", ""), m_aBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "modules/swriter/ui/headerfootermenu.ui", ""),
...@@ -254,42 +290,13 @@ void SwHeaderFooterWin::Paint(vcl::RenderContext& rRenderContext, const ::tools: ...@@ -254,42 +290,13 @@ void SwHeaderFooterWin::Paint(vcl::RenderContext& rRenderContext, const ::tools:
{ {
// Use pixels for the rest of the drawing // Use pixels for the rest of the drawing
SetMapMode(MapMode(MapUnit::MapPixel)); SetMapMode(MapMode(MapUnit::MapPixel));
drawinglayer::primitive2d::Primitive2DContainer aSeq;
const ::tools::Rectangle aRect(::tools::Rectangle(Point(0, 0), rRenderContext.PixelToLogic(GetSizePixel()))); const ::tools::Rectangle aRect(::tools::Rectangle(Point(0, 0), rRenderContext.PixelToLogic(GetSizePixel())));
drawinglayer::primitive2d::Primitive2DContainer aSeq(3);
B2DPolygon aPolygon = lcl_GetPolygon(aRect, m_bIsHeader);
// Colors
basegfx::BColor aLineColor = SwViewOption::GetHeaderFooterMarkColor().getBColor();
basegfx::BColor aFillColor = lcl_GetFillColor(aLineColor);
basegfx::BColor aLighterColor = lcl_GetLighterGradientColor(aFillColor);
const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); SwFrameButtonPainter::PaintButton(aSeq, aRect, m_bIsHeader);
if (rSettings.GetHighContrastMode())
{
aFillColor = rSettings.GetDialogColor().getBColor();
aLineColor = rSettings.GetDialogTextColor().getBColor();
aSeq[0] = drawinglayer::primitive2d::Primitive2DReference(
new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(B2DPolyPolygon(aPolygon), aFillColor));
}
else
{
B2DRectangle aGradientRect(aRect.Left(), aRect.Top(), aRect.Right(), aRect.Bottom());
double nAngle = M_PI;
if (m_bIsHeader)
nAngle = 0;
FillGradientAttribute aFillAttrs(drawinglayer::attribute::GradientStyle::Linear, 0.0, 0.0, 0.0, nAngle, aLighterColor, aFillColor, 10);
aSeq[0] = drawinglayer::primitive2d::Primitive2DReference(
new drawinglayer::primitive2d::FillGradientPrimitive2D(aGradientRect, aFillAttrs));
}
// Create the border lines primitive
aSeq[1] = drawinglayer::primitive2d::Primitive2DReference(
new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(aPolygon, aLineColor));
// Create the text primitive // Create the text primitive
basegfx::BColor aLineColor = SwViewOption::GetHeaderFooterMarkColor().getBColor();
B2DVector aFontSize; B2DVector aFontSize;
FontAttribute aFontAttr = drawinglayer::primitive2d::getFontAttributeFromVclFont(aFontSize, rRenderContext.GetFont(), false, false); FontAttribute aFontAttr = drawinglayer::primitive2d::getFontAttributeFromVclFont(aFontSize, rRenderContext.GetFont(), false, false);
...@@ -301,10 +308,10 @@ void SwHeaderFooterWin::Paint(vcl::RenderContext& rRenderContext, const ::tools: ...@@ -301,10 +308,10 @@ void SwHeaderFooterWin::Paint(vcl::RenderContext& rRenderContext, const ::tools:
aFontSize.getX(), aFontSize.getY(), aFontSize.getX(), aFontSize.getY(),
double(aTextPos.X()), double(aTextPos.Y()))); double(aTextPos.X()), double(aTextPos.Y())));
aSeq[2] = drawinglayer::primitive2d::Primitive2DReference( aSeq.push_back(drawinglayer::primitive2d::Primitive2DReference(
new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
aTextMatrix, m_sLabel, 0, m_sLabel.getLength(), aTextMatrix, m_sLabel, 0, m_sLabel.getLength(),
std::vector<double>(), aFontAttr, css::lang::Locale(), aLineColor)); std::vector<double>(), aFontAttr, css::lang::Locale(), aLineColor)));
// Create the 'plus' or 'arrow' primitive // Create the 'plus' or 'arrow' primitive
B2DRectangle aSignArea(B2DPoint(aRect.Right() - BUTTON_WIDTH, 0.0), B2DRectangle aSignArea(B2DPoint(aRect.Right() - BUTTON_WIDTH, 0.0),
......
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* 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/.
*/
#ifndef INCLUDED_SW_SOURCE_UIBASE_DOCVW_FLOATINGTABLEBUTTON_HXX
#define INCLUDED_SW_SOURCE_UIBASE_DOCVW_FLOATINGTABLEBUTTON_HXX
#include "FrameControl.hxx"
#include <vcl/menubtn.hxx>
class FloatingTableButton : public SwFrameMenuButtonBase
{
OUString m_sLabel;
public:
FloatingTableButton(SwEditWin* pEditWin, const SwFrame* pFrame);
virtual ~FloatingTableButton() override;
void SetOffset(Point aBottomRightPixel);
virtual void MouseButtonDown(const MouseEvent& rMEvt) override;
virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override;
virtual void ShowAll(bool bShow) override;
virtual bool Contains(const Point& rDocPt) const override;
virtual void SetReadonly(bool bReadonly) override;
};
#endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
\ No newline at end of file
...@@ -47,6 +47,7 @@ class SwFrameControlsManager ...@@ -47,6 +47,7 @@ class SwFrameControlsManager
// Helper methods // Helper methods
void SetHeaderFooterControl( const SwPageFrame* pPageFrame, FrameControlType eType, Point aOffset ); void SetHeaderFooterControl( const SwPageFrame* pPageFrame, FrameControlType eType, Point aOffset );
void SetPageBreakControl( const SwPageFrame* pPageFrame ); void SetPageBreakControl( const SwPageFrame* pPageFrame );
void SetFloatingTableButton( const SwFlyFrame* pFlyFrame, bool bShow, Point aTopLeftPixel = Point() );
}; };
#endif #endif
......
...@@ -12,6 +12,16 @@ ...@@ -12,6 +12,16 @@
#include "FrameControl.hxx" #include "FrameControl.hxx"
#include <vcl/builder.hxx> #include <vcl/builder.hxx>
#include <vcl/menubtn.hxx> #include <vcl/menubtn.hxx>
#include <vcl/timer.hxx>
#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
class SwFrameButtonPainter
{
public:
static void PaintButton(drawinglayer::primitive2d::Primitive2DContainer& rSeq,
const tools::Rectangle& rRect, bool bOnTop);
};
/** Class for the header and footer separator control window. /** Class for the header and footer separator control window.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment