tdf#142448 sw offapi: add custom color metadata field shading

using the new com::sun::rdf::URIs::LO_EXT_SHADING
URI (modelled after odf:prefix and odf:suffix).
Custom color field shading of text:meta annotated text
ranges and text:meta-field metadata fields allows
quick visual check of metadata categories.

For example, RDF triple

content.xml#id1753384014 urn:org:documentfoundation:names:experimental🏢xmlns:loext:1.0odf#shading FF0000

sets red (FF0000) shading color for the text span
with xml:id="id1753384014".

Pressing Ctrl-F8 or View->Field Shadings can disable
custom color metadata field shading on the UI.

Note: neither LO_EXT_SHADING, nor odf:prefix and
odf:suffix changes invalidate the View (MetaPortion),
but run-time update of shading color can be triggered
without save and reload of the document e.g. by using
(temporary) bookmarks on the annotated text spans.

To run unit test with enabled visibility, use

(cd sw && make UITest_sw_styleInspector UITEST_TEST_NAME="styleInspector.styleNavigator.test_metadata_shading_color" SAL_USE_VCLPLUGIN=gen)

in Linux command line.

Change-Id: I5de93cfa32ac6793d7dbdc7b64e6f4beacb2e8d7
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116015
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
This commit is contained in:
László Németh
2021-05-23 12:46:09 +02:00
parent a04ac0ecea
commit a8a9b4f463
13 changed files with 243 additions and 21 deletions

View File

@@ -314,6 +314,14 @@ constants URIs
/// urn:oasis:names:tc:opendocument:xmlns:text:1.0meta-field
// const short TEXT_META_FIELD = 3000;
/** custom shading color of an annotated text range or metadata field
(replacement of the default field shading color)
@since LibreOffice 7.2
*/
/// urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0odf#shading
const short LO_EXT_SHADING = 2106;
};

View File

@@ -75,4 +75,27 @@
<range rdf:resource="http://www.w3.org/2001/XMLSchema-datatypes#string"/>
<label xml:lang="en">has suffix</label>
</owl:DatatypeProperty>
<owl:Class rdf:about="urn:oasis:names:tc:opendocument:xmlns:text:1.0meta">
<label xml:lang="en">OpenDocument Annotated text range Element</label>
<subClassOf rdf:resource="http://docs.oasis-open.org/ns/office/1.2/meta/odf#Element"/>
</owl:Class>
<owl:Class rdf:about="urn:oasis:names:tc:opendocument:xmlns:text:1.0nestedtextcontent">
<owl:equivalentClass>
<owl:Class>
<owl:unionOf rdf:parseType="Collection">
<owl:Class rdf:about="urn:oasis:names:tc:opendocument:xmlns:text:1.0meta">
<owl:Class rdf:about="urn:oasis:names:tc:opendocument:xmlns:text:1.0meta-field">
</owl:unionOf>
</owl:Class>
</owl:equivalentClass>
</owl:Class>
<owl:DatatypeProperty rdf:about="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0odf#shading">
<rdf:type rdf:resource="http://www.w3.org/2002/07/owl#FunctionalProperty"/>
<domain rdf:resource="urn:oasis:names:tc:opendocument:xmlns:text:1.0nestedtextcontent"/>
<range rdf:resource="http://www.w3.org/2001/XMLSchema-datatypes#string"/>
<label xml:lang="en">has shading color</label>
</owl:DatatypeProperty>
</rdf:RDF>

View File

@@ -189,7 +189,7 @@ private:
public:
/// get prefix/suffix from the RDF repository. @throws RuntimeException
void GetPrefixAndSuffix(
OUString *const o_pPrefix, OUString *const o_pSuffix);
OUString *const o_pPrefix, OUString *const o_pSuffix, OUString *const o_pShadingColor);
};
/// knows all meta-fields in the document.

Binary file not shown.

View File

@@ -7,6 +7,7 @@
from uitest.framework import UITestCase
from uitest.uihelper.common import get_state_as_dict, get_url_for_data_file
from libreoffice.uno.propertyvalue import mkPropertyValues
from com.sun.star.rdf.URIs import RDF_TYPE, RDFS_COMMENT, RDFS_LABEL, ODF_PREFIX, ODF_SUFFIX, LO_EXT_SHADING
class styleNavigator(UITestCase):
@@ -246,4 +247,110 @@ class styleNavigator(UITestCase):
self.xUITest.executeCommand(".uno:Sidebar")
self.ui_test.close_doc()
def test_metadata_shading_color(self):
writer_doc = self.ui_test.load_file(get_url_for_data_file("metacolor.odt"))
xWriterDoc = self.xUITest.getTopFocusWindow()
xWriterEdit = xWriterDoc.getChild("writer_edit")
self.xUITest.executeCommand(".uno:Sidebar")
xWriterEdit.executeAction("SIDEBAR", mkPropertyValues({"PANEL": "InspectorTextPanel"}))
xListBox = xWriterEdit.getChild('listbox_fonts')
# The cursor is on text without metadata
self.assertEqual(1, len(xListBox.getChild('0').getChildren()))
self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text'])
self.assertEqual(136, len(xListBox.getChild('0').getChild('0').getChildren()))
self.assertEqual(0, len(xListBox.getChild('1').getChildren()))
self.assertEqual(0, len(xListBox.getChild('2').getChildren()))
# go to next word with RDF annotation
self.xUITest.executeCommand(".uno:GoToNextWord")
self.xUITest.executeCommand(".uno:GoRight")
# FIXME jump over the control character (not visible in getString(), but it affects
# cursor position and availability of NestedTextContent)
self.xUITest.executeCommand(".uno:GoRight")
# The cursor is on text with annotated text range
xDirFormatting = xListBox.getChild('3')
self.assertEqual(2, len(xDirFormatting.getChildren()))
self.assertEqual("Metadata Reference\t", get_state_as_dict(xDirFormatting.getChild('0'))['Text'])
self.assertEqual("Nested Text Content\tipsum", get_state_as_dict(xDirFormatting.getChild('1'))['Text'])
xMetadata = xDirFormatting.getChild('0')
self.assertEqual(2, len(xMetadata.getChildren()))
self.assertEqual("xml:id\tID-f1a8a096-7a43-4bda-a462-d73c2800dd9a", get_state_as_dict(xMetadata.getChild('0'))['Text'])
# RGB code of the custom shading color of the annotated text range is FFB7E9
self.assertEqual("urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0odf#shading\tFFB7E9", get_state_as_dict(xMetadata.getChild('1'))['Text'])
self.assertEqual(0, len(xListBox.getChild('1').getChildren()))
self.assertEqual(0, len(xListBox.getChild('2').getChildren()))
# go to next word with RDF annotation
self.xUITest.executeCommand(".uno:GoToNextWord")
self.xUITest.executeCommand(".uno:GoToNextWord")
self.xUITest.executeCommand(".uno:GoToNextWord")
self.xUITest.executeCommand(".uno:GoRight")
# The cursor is on text with annotated text range
xDirFormatting = xListBox.getChild('3')
self.assertEqual(2, len(xDirFormatting.getChildren()))
self.assertEqual("Metadata Reference\t", get_state_as_dict(xDirFormatting.getChild('0'))['Text'])
self.assertEqual("Nested Text Content\tames", get_state_as_dict(xDirFormatting.getChild('1'))['Text'])
xMetadata = xDirFormatting.getChild('0')
print(get_state_as_dict(xMetadata))
self.assertEqual(2, len(xMetadata.getChildren()))
self.assertEqual("xml:id\tID-24478193-9630-4d03-8976-9e097c843a0b", get_state_as_dict(xMetadata.getChild('0'))['Text'])
# RGB code of the custom shading color of the annotated text range is 97E1E9 (the code can be lower case, see STRtoRGB)
self.assertEqual("urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0odf#shading\t97e1e9", get_state_as_dict(xMetadata.getChild('1'))['Text'])
self.assertEqual(0, len(xListBox.getChild('1').getChildren()))
self.assertEqual(0, len(xListBox.getChild('2').getChildren()))
# add a new annotated text range and check it in the Style Inspector
self.xUITest.executeCommand(".uno:GoToStartOfDoc")
# create annotated text range on "Lorem"
viewCursor = writer_doc.CurrentController.getViewCursor()
viewCursor.goRight(5, True)
rdf = writer_doc.createInstance("com.sun.star.text.InContentMetadata")
writer_doc.Text.insertTextContent(viewCursor, rdf, True)
# set its custom shading color using the new URI const LO_EXT_SHADING
repository = writer_doc.getRDFRepository()
# get metadata graph of file "custom.rdf" stored in the test document
xCustomGraph = [repository.getGraph(i) for i in repository.getGraphNames() if i.LocalName == "custom.rdf"]
self.assertEqual(1, len(xCustomGraph))
smgr = self.xContext.ServiceManager
xShadingURI = smgr.createInstance('com.sun.star.rdf.URI')
xShadingURI.initialize((LO_EXT_SHADING,))
xShadingColor = smgr.createInstance('com.sun.star.rdf.Literal')
xShadingColor.initialize(('BBFF88',))
xCustomGraph[0].addStatement(rdf, xShadingURI, xShadingColor)
# remove the selection
self.xUITest.executeCommand(".uno:GoLeft")
# FIXME: neither LO_EXT_SHADING, nor odf:prefix/odf:suffix changes update the View,
# so add a temporary bookmark to the text range to trigger the color change immediately
self.ui_test.execute_dialog_through_command(".uno:InsertBookmark")
xBookDlg = self.xUITest.getTopFocusWindow()
xInsertBtn = xBookDlg.getChild("insert")
xInsertBtn.executeAction("CLICK", tuple()) # first bookmark
self.xUITest.executeCommand(".uno:Undo")
xDirFormatting = xListBox.getChild('3')
xMetadata = xDirFormatting.getChild('0')
self.assertEqual(2, len(xMetadata.getChildren()))
# RGB code of the custom shading color of the annotated text range is BBFF88
self.assertEqual("urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0odf#shading\tBBFF88", get_state_as_dict(xMetadata.getChild('1'))['Text'])
self.xUITest.executeCommand(".uno:Sidebar")
self.ui_test.close_doc()
# vim: set shiftwidth=4 softtabstop=4 expandtab:

View File

@@ -263,7 +263,7 @@ public:
void getPrefixAndSuffix(
const css::uno::Reference< css::frame::XModel>& xModel,
const css::uno::Reference< css::rdf::XMetadatable>& xMetaField,
OUString *const o_pPrefix, OUString *const o_pSuffix);
OUString *const o_pPrefix, OUString *const o_pSuffix, OUString *const o_pShadowColor);
#endif // INCLUDED_SW_SOURCE_CORE_INC_UNOMETA_HXX

View File

@@ -1125,7 +1125,7 @@ void SwTextPaintInfo::DrawCheckBox(const SwFieldFormCheckboxPortion &rPor, bool
m_pOut->Pop();
}
void SwTextPaintInfo::DrawBackground( const SwLinePortion &rPor ) const
void SwTextPaintInfo::DrawBackground( const SwLinePortion &rPor, const Color *pColor ) const
{
OSL_ENSURE( OnWin(), "SwTextPaintInfo::DrawBackground: printer pollution ?" );
@@ -1145,7 +1145,11 @@ void SwTextPaintInfo::DrawBackground( const SwLinePortion &rPor ) const
}
else
{
pOut->SetFillColor( SwViewOption::GetFieldShadingsColor() );
if ( pColor )
pOut->SetFillColor( *pColor );
else
pOut->SetFillColor( SwViewOption::GetFieldShadingsColor() );
pOut->SetLineColor();
}
@@ -1313,7 +1317,7 @@ void SwTextPaintInfo::DrawBorder( const SwLinePortion &rPor ) const
}
void SwTextPaintInfo::DrawViewOpt( const SwLinePortion &rPor,
PortionType nWhich ) const
PortionType nWhich, const Color *pColor ) const
{
if( !OnWin() || IsMulti() )
return;
@@ -1366,7 +1370,7 @@ void SwTextPaintInfo::DrawViewOpt( const SwLinePortion &rPor,
}
}
if ( bDraw )
DrawBackground( rPor );
DrawBackground( rPor, pColor );
}
static void lcl_InitHyphValues( PropertyValues &rVals,

View File

@@ -399,8 +399,8 @@ public:
void DrawLineBreak( const SwLinePortion &rPor ) const;
void DrawRedArrow( const SwLinePortion &rPor ) const;
void DrawPostIts( bool bScript ) const;
void DrawBackground( const SwLinePortion &rPor ) const;
void DrawViewOpt( const SwLinePortion &rPor, PortionType nWhich ) const;
void DrawBackground( const SwLinePortion &rPor, const Color *pColor=nullptr ) const;
void DrawViewOpt( const SwLinePortion &rPor, PortionType nWhich, const Color *pColor=nullptr ) const;
void DrawBackBrush( const SwLinePortion &rPor ) const;
/**

View File

@@ -54,8 +54,15 @@
#include <IDocumentSettingAccess.hxx>
#include <IMark.hxx>
#include <IDocumentMarkAccess.hxx>
#include <vector>
#include <comphelper/processfactory.hxx>
#include <docsh.hxx>
#include <unocrsrhelper.hxx>
#include <com/sun/star/rdf/Statement.hpp>
#include <com/sun/star/rdf/URI.hpp>
#include <com/sun/star/rdf/URIs.hpp>
#include <com/sun/star/rdf/XDocumentMetadataAccess.hpp>
#include <com/sun/star/rdf/XLiteral.hpp>
#include <com/sun/star/text/XTextContent.hpp>
using namespace ::com::sun::star;
@@ -842,9 +849,11 @@ namespace {
class SwMetaPortion : public SwTextPortion
{
Color m_aShadowColor;
public:
SwMetaPortion() { SetWhichPor( PortionType::Meta ); }
virtual void Paint( const SwTextPaintInfo &rInf ) const override;
void SetShadowColor(const Color& rCol ) { m_aShadowColor = rCol; }
};
}
@@ -853,7 +862,12 @@ void SwMetaPortion::Paint( const SwTextPaintInfo &rInf ) const
{
if ( Width() )
{
rInf.DrawViewOpt( *this, PortionType::Meta );
rInf.DrawViewOpt( *this, PortionType::Meta,
// custom shading (RDF metadata)
COL_BLACK == m_aShadowColor
? nullptr
: &m_aShadowColor );
SwTextPortion::Paint( rInf );
}
}
@@ -919,7 +933,50 @@ SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const
pPor = new SwRefPortion;
else if (GetFnt()->IsMeta())
{
pPor = new SwMetaPortion;
auto pMetaPor = new SwMetaPortion;
// set custom LO_EXT_SHADING color, if it exists
SwTextFrame const*const pFrame(rInf.GetTextFrame());
SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
SwPaM aPam(aPosition);
uno::Reference<text::XTextContent> const xRet(
SwUnoCursorHelper::GetNestedTextContent(
*aPam.GetNode().GetTextNode(), aPosition.nContent.GetIndex(), false) );
if (xRet.is())
{
const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc();
static uno::Reference< uno::XComponentContext > xContext(
::comphelper::getProcessComponentContext());
static uno::Reference< rdf::XURI > xODF_SHADING(
rdf::URI::createKnown(xContext, rdf::URIs::LO_EXT_SHADING), uno::UNO_SET_THROW);
uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(
rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY);
const css::uno::Reference<css::rdf::XResource> xSubject(xRet, uno::UNO_QUERY);
const uno::Reference<rdf::XRepository>& xRepository =
xDocumentMetadataAccess->getRDFRepository();
const uno::Reference<container::XEnumeration> xEnum(
xRepository->getStatements(xSubject, xODF_SHADING, nullptr), uno::UNO_SET_THROW);
while (xEnum->hasMoreElements())
{
rdf::Statement stmt;
if (!(xEnum->nextElement() >>= stmt)) {
throw uno::RuntimeException();
}
const uno::Reference<rdf::XLiteral> xObject(stmt.Object, uno::UNO_QUERY);
if (!xObject.is()) continue;
if (xEnum->hasMoreElements()) {
SAL_INFO("sw.uno", "ignoring other odf:shading statements");
}
Color rColor = Color::STRtoRGB(xObject->getValue());
pMetaPor->SetShadowColor(rColor);
break;
}
}
pPor = pMetaPor;
}
else
{

View File

@@ -304,7 +304,8 @@ static SwFieldPortion * lcl_NewMetaPortion(SwTextAttr & rHint, const bool bPrefi
OSL_ENSURE(pField, "lcl_NewMetaPortion: no meta field?");
if (pField)
{
pField->GetPrefixAndSuffix(bPrefix ? &fix : nullptr, bPrefix ? nullptr : &fix);
OUString color;
pField->GetPrefixAndSuffix(bPrefix ? &fix : nullptr, bPrefix ? nullptr : &fix, &color);
}
return new SwFieldPortion( fix );
}

View File

@@ -730,7 +730,7 @@ MetaField::MetaField(SwFormatMeta * const i_pFormat,
}
void MetaField::GetPrefixAndSuffix(
OUString *const o_pPrefix, OUString *const o_pSuffix)
OUString *const o_pPrefix, OUString *const o_pSuffix, OUString *const o_pShadowsColor)
{
try
{
@@ -743,7 +743,7 @@ void MetaField::GetPrefixAndSuffix(
SwDocShell const * const pShell(pTextNode->GetDoc().GetDocShell());
const uno::Reference<frame::XModel> xModel(
pShell ? pShell->GetModel() : nullptr, uno::UNO_SET_THROW);
getPrefixAndSuffix(xModel, xMetaField, o_pPrefix, o_pSuffix);
getPrefixAndSuffix(xModel, xMetaField, o_pPrefix, o_pSuffix, o_pShadowsColor);
}
}
catch (const uno::Exception&)

View File

@@ -1422,7 +1422,7 @@ SwXMetaField::removeVetoableChangeListener(
}
static uno::Reference<rdf::XURI> const&
lcl_getURI(const bool bPrefix)
lcl_getURI(const sal_Int16 eKnown)
{
static uno::Reference< uno::XComponentContext > xContext(
::comphelper::getProcessComponentContext());
@@ -1432,7 +1432,18 @@ lcl_getURI(const bool bPrefix)
static uno::Reference< rdf::XURI > xOdfSuffix(
rdf::URI::createKnown(xContext, rdf::URIs::ODF_SUFFIX),
uno::UNO_SET_THROW);
return bPrefix ? xOdfPrefix : xOdfSuffix;
static uno::Reference< rdf::XURI > xOdfShading(
rdf::URI::createKnown(xContext, rdf::URIs::LO_EXT_SHADING),
uno::UNO_SET_THROW);
switch (eKnown)
{
case rdf::URIs::ODF_PREFIX:
return xOdfPrefix;
case rdf::URIs::ODF_SUFFIX:
return xOdfSuffix;
default:
return xOdfShading;
}
}
static OUString
@@ -1464,7 +1475,7 @@ void
getPrefixAndSuffix(
const uno::Reference<frame::XModel>& xModel,
const uno::Reference<rdf::XMetadatable>& xMetaField,
OUString *const o_pPrefix, OUString *const o_pSuffix)
OUString *const o_pPrefix, OUString *const o_pSuffix, OUString *const o_pShadingColor)
{
try {
const uno::Reference<rdf::XRepositorySupplier> xRS(
@@ -1475,11 +1486,15 @@ getPrefixAndSuffix(
xMetaField, uno::UNO_QUERY_THROW);
if (o_pPrefix)
{
*o_pPrefix = lcl_getPrefixOrSuffix(xRepo, xMeta, lcl_getURI(true));
*o_pPrefix = lcl_getPrefixOrSuffix(xRepo, xMeta, lcl_getURI(rdf::URIs::ODF_PREFIX));
}
if (o_pSuffix)
{
*o_pSuffix = lcl_getPrefixOrSuffix(xRepo, xMeta, lcl_getURI(false));
*o_pSuffix = lcl_getPrefixOrSuffix(xRepo, xMeta, lcl_getURI(rdf::URIs::ODF_SUFFIX));
}
if (o_pShadingColor)
{
*o_pShadingColor = lcl_getPrefixOrSuffix(xRepo, xMeta, lcl_getURI(rdf::URIs::LO_EXT_SHADING));
}
} catch (uno::RuntimeException &) {
throw;
@@ -1506,7 +1521,7 @@ SwXMetaField::getPresentation(sal_Bool bShowCommand)
const OUString content( getString() );
OUString prefix;
OUString suffix;
getPrefixAndSuffix(GetModel(), this, &prefix, &suffix);
getPrefixAndSuffix(GetModel(), this, &prefix, &suffix, nullptr);
return prefix + content + suffix;
}
}

View File

@@ -94,6 +94,8 @@ const char s_nsPkg [] =
"http://docs.oasis-open.org/ns/office/1.2/meta/pkg#";
const char s_nsODF [] =
"http://docs.oasis-open.org/ns/office/1.2/meta/odf#";
const char s_nsLO_EXT [] =
"urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0odf#";
void CURI::initFromConstant(const sal_Int16 i_Constant)
{
@@ -697,6 +699,11 @@ void CURI::initFromConstant(const sal_Int16 i_Constant)
ln = "StylesFile";
break;
case css::rdf::URIs::LO_EXT_SHADING:
ns = s_nsLO_EXT;
ln = "shading";
break;
default:
throw css::lang::IllegalArgumentException(
"CURI::initialize: invalid URIs constant argument", *this, 0);