[Libreoffice-commits] core.git: Branch 'libreoffice-4-0' - 2 commits - libcmis/libcmis-0.3.0-lotus-live-fix.patch libcmis/UnpackedTarball_cmis.mk sfx2/source sw/inc sw/source ucb/source

Cédric Bosdonnat cedric.bosdonnat at free.fr
Thu Feb 14 09:39:38 PST 2013


 libcmis/UnpackedTarball_cmis.mk            |    1 
 libcmis/libcmis-0.3.0-lotus-live-fix.patch |  122 +++++++++++++++++++
 sfx2/source/view/sfxbasecontroller.cxx     |    2 
 sw/inc/IDocumentStatistics.hxx             |   17 ++
 sw/inc/doc.hxx                             |   16 ++
 sw/inc/ndtxt.hxx                           |    4 
 sw/source/core/doc/doc.cxx                 |  184 ++++++++++++++++-------------
 sw/source/core/doc/docnew.cxx              |    4 
 sw/source/core/txtnode/txtedt.cxx          |   18 +-
 sw/source/ui/dialog/wordcountdialog.cxx    |    2 
 sw/source/ui/inc/view.hxx                  |    6 
 sw/source/ui/uiview/view.cxx               |    1 
 sw/source/ui/uiview/view2.cxx              |    9 +
 ucb/source/ucp/cmis/cmis_content.cxx       |  138 +++++++++++++++++----
 ucb/source/ucp/cmis/cmis_content.hxx       |    2 
 ucb/source/ucp/cmis/cmis_url.cxx           |   29 +++-
 16 files changed, 425 insertions(+), 130 deletions(-)

New commits:
commit 57d87496d0ac8c35597acc3cf81609eb4aaa55df
Author: Cédric Bosdonnat <cedric.bosdonnat at free.fr>
Date:   Thu Feb 14 10:27:21 2013 +0100

    CMIS: made it work with Lotus Live
    
    Making libcmis and LibreOffice work with Lotus Live service needed a few
    hacks to either better implement CMIS or workaround some bad
    implementations.
    
    As a general improvement, the CheckOut InfoBar isn't shown if the
    document can't be checked out.
    
    Change-Id: I7bb4211db0506998cef40ac1fb6375e647234085

diff --git a/libcmis/UnpackedTarball_cmis.mk b/libcmis/UnpackedTarball_cmis.mk
index a25c316..0a9f9f8 100644
--- a/libcmis/UnpackedTarball_cmis.mk
+++ b/libcmis/UnpackedTarball_cmis.mk
@@ -18,6 +18,7 @@ $(eval $(call gb_UnpackedTarball_add_patches,cmis, \
 	libcmis/libcmis-0.3.0-win.patch \
 	libcmis/libcmis-0.3.0.patch \
 	libcmis/libcmis-0.3.0-proxy.patch \
+	libcmis/libcmis-0.3.0-lotus-live-fix.patch \
 ))
 
 ifeq ($(OS)$(COM),WNTMSC)
diff --git a/libcmis/libcmis-0.3.0-lotus-live-fix.patch b/libcmis/libcmis-0.3.0-lotus-live-fix.patch
new file mode 100644
index 0000000..2aca934
--- /dev/null
+++ b/libcmis/libcmis-0.3.0-lotus-live-fix.patch
@@ -0,0 +1,122 @@
+diff --git src/libcmis/atom-folder.cxx src/libcmis/atom-folder.cxx
+index 68fb124..2756a5d 100644
+--- src/libcmis/atom-folder.cxx
++++ src/libcmis/atom-folder.cxx
+@@ -57,8 +57,11 @@ vector< libcmis::ObjectPtr > AtomFolder::getChildren( ) throw ( libcmis::Excepti
+ {
+     AtomLink* childrenLink = getLink( "down", "application/atom+xml;type=feed" );
+ 
++    // Some servers aren't giving the GetChildren properly... if not defined, we need to try
++    // as we may have the right to proceed.
+     if ( ( NULL == childrenLink ) || ( getAllowableActions( ).get() &&
+-                !getAllowableActions()->isAllowed( libcmis::ObjectAction::GetChildren ) ) )
++                ( !getAllowableActions()->isAllowed( libcmis::ObjectAction::GetChildren ) &&
++                  getAllowableActions()->isDefined( libcmis::ObjectAction::GetChildren ) ) ) )
+         throw libcmis::Exception( string( "GetChildren not allowed on node " ) + getId() );
+ 
+     vector< libcmis::ObjectPtr > children;
+@@ -182,7 +185,8 @@ libcmis::DocumentPtr AtomFolder::createDocument( const map< string, libcmis::Pro
+     AtomLink* childrenLink = getLink( "down", "application/atom+xml;type=feed" );
+ 
+     if ( ( NULL == childrenLink ) || ( getAllowableActions( ).get() &&
+-                !getAllowableActions()->isAllowed( libcmis::ObjectAction::CreateDocument ) ) )
++                !getAllowableActions()->isAllowed( libcmis::ObjectAction::CreateDocument ) &&
++                getAllowableActions()->isDefined( libcmis::ObjectAction::CreateDocument ) ) )
+         throw libcmis::Exception( string( "CreateDocument not allowed on folder " ) + getId() );
+ 
+     xmlBufferPtr buf = xmlBufferCreate( );
+@@ -210,9 +214,37 @@ libcmis::DocumentPtr AtomFolder::createDocument( const map< string, libcmis::Pro
+     }
+ 
+     string respBuf = response->getStream( )->str( );
+-    xmlDocPtr doc = xmlReadMemory( respBuf.c_str(), respBuf.size(), getInfosUrl().c_str(), NULL, 0 );
++    xmlDocPtr doc = xmlReadMemory( respBuf.c_str(), respBuf.size(), getInfosUrl().c_str(), NULL, XML_PARSE_NOERROR );
+     if ( NULL == doc )
+-        throw libcmis::Exception( "Failed to parse object infos" );
++    {
++        // We may not have the created document entry in the response body: this is
++        // the behaviour of some servers, but the standard says we need to look for
++        // the Location header.
++        map< string, string >& headers = response->getHeaders( );
++        map< string, string >::iterator it = headers.find( "Location" );
++
++        // Some servers like Lotus Live aren't sending Location header, but Content-Location
++        if ( it == headers.end( ) )
++            it = headers.find( "Content-Location" );
++
++        if ( it != headers.end() )
++        {
++            try
++            {
++                response = getSession( )->httpGetRequest( it->second );
++                respBuf = response->getStream( )->str( );
++                doc = xmlReadMemory( respBuf.c_str(), respBuf.size(), getInfosUrl().c_str(), NULL, XML_PARSE_NOERROR );
++            }
++            catch ( const CurlException& e )
++            {
++                throw e.getCmisException( );
++            }
++        }
++
++        // if doc is still NULL after that, then throw an exception
++        if ( NULL == doc )
++            throw libcmis::Exception( "Missing expected response from server" );
++    }
+ 
+     libcmis::ObjectPtr created = getSession( )->createObjectFromEntryDoc( doc );
+     xmlFreeDoc( doc );
+diff --git src/libcmis/atom-object.cxx src/libcmis/atom-object.cxx
+index b7b3b4a..812951d 100644
+--- src/libcmis/atom-object.cxx
++++ src/libcmis/atom-object.cxx
+@@ -140,6 +140,34 @@ libcmis::ObjectPtr AtomObject::updateProperties( const map< string, libcmis::Pro
+     return updated;
+ }
+ 
++libcmis::AllowableActionsPtr AtomObject::getAllowableActions( )
++{
++    if ( !m_allowableActions )
++    {
++        // For some reason we had no allowable actions before, get them now.
++        AtomLink* link = getLink( "http://docs.oasis-open.org/ns/cmis/link/200908/allowableactions", "application/cmisallowableactions+xml" );
++        if ( link )
++        {
++            try
++            {
++                libcmis::HttpResponsePtr response = getSession()->httpGetRequest( link->getHref() );
++                string buf = response->getStream()->str();
++                xmlDocPtr doc = xmlReadMemory( buf.c_str(), buf.size(), link->getHref().c_str(), NULL, 0 );
++                xmlNodePtr actionsNode = xmlDocGetRootElement( doc );
++                if ( actionsNode )
++                    m_allowableActions.reset( new libcmis::AllowableActions( actionsNode ) );
++
++                xmlFreeDoc( doc );
++            }
++            catch ( libcmis::Exception& )
++            {
++            }
++        }
++    }
++
++    return libcmis::Object::getAllowableActions();
++}
++
+ void AtomObject::refreshImpl( xmlDocPtr doc ) throw ( libcmis::Exception )
+ {
+     bool createdDoc = ( NULL == doc );
+diff --git src/libcmis/atom-object.hxx src/libcmis/atom-object.hxx
+index 2d1761d..452b4f5 100644
+--- src/libcmis/atom-object.hxx
++++ src/libcmis/atom-object.hxx
+@@ -69,6 +69,8 @@ class AtomObject : public virtual libcmis::Object
+         virtual libcmis::ObjectPtr updateProperties(
+                     const std::map< std::string, libcmis::PropertyPtr >& properties ) throw ( libcmis::Exception );
+ 
++        virtual libcmis::AllowableActionsPtr getAllowableActions( );
++
+         /** Reload the data from the server.
+               */
+         virtual void refresh( ) throw ( libcmis::Exception ) { refreshImpl( NULL ); }
+-- 
+1.7.10.4
+
diff --git a/sfx2/source/view/sfxbasecontroller.cxx b/sfx2/source/view/sfxbasecontroller.cxx
index f1b2b01..c97d014 100644
--- a/sfx2/source/view/sfxbasecontroller.cxx
+++ b/sfx2/source/view/sfxbasecontroller.cxx
@@ -1439,7 +1439,7 @@ void SfxBaseController::ShowInfoBars( )
     {
         // CMIS verifications
         REFERENCE< document::XCmisDocument > xCmisDoc( m_pData->m_pViewShell->GetObjectShell()->GetModel(), uno::UNO_QUERY );
-        if ( xCmisDoc.is( ) )
+        if ( xCmisDoc.is( ) && xCmisDoc->canCheckOut( ) )
         {
             beans::PropertyValues aCmisProperties = xCmisDoc->getCmisPropertiesValues( );
 
diff --git a/ucb/source/ucp/cmis/cmis_content.cxx b/ucb/source/ucp/cmis/cmis_content.cxx
index 5f94d7a..2a78b4e 100644
--- a/ucb/source/ucp/cmis/cmis_content.cxx
+++ b/ucb/source/ucp/cmis/cmis_content.cxx
@@ -307,7 +307,46 @@ namespace cmis
         if ( NULL == m_pObjectType.get( ) && m_bTransient )
         {
             string typeId = m_bIsFolder ? "cmis:folder" : "cmis:document";
-            m_pObjectType = getSession( xEnv )->getType( typeId );
+            // The type to create needs to be fetched from the possible children types
+            // defined in the parent folder. Then, we'll pick up the first one we find matching
+            // cmis:folder or cmis:document (depending what we need to create).
+            // The easy case will work in most cases, but not on some servers (like Lotus Live)
+            libcmis::Folder* pParent = NULL;
+            bool bTypeRestricted = false;
+            try
+            {
+                pParent = dynamic_cast< libcmis::Folder* >( getObject( xEnv ).get( ) );
+            }
+            catch ( const libcmis::Exception& )
+            {
+            }
+
+            if ( pParent )
+            {
+                map< string, libcmis::PropertyPtr >& aProperties = pParent->getProperties( );
+                map< string, libcmis::PropertyPtr >::iterator it = aProperties.find( "cmis:allowedChildObjectTypeIds" );
+                if ( it != aProperties.end( ) )
+                {
+                    libcmis::PropertyPtr pProperty = it->second;
+                    if ( pProperty )
+                    {
+                        vector< string > typesIds = pProperty->getStrings( );
+                        for ( vector< string >::iterator typeIt = typesIds.begin();
+                                typeIt != typesIds.end() && !m_pObjectType; ++typeIt )
+                        {
+                            bTypeRestricted = true;
+                            libcmis::ObjectTypePtr type = getSession( xEnv )->getType( *typeIt );
+
+                            // FIXME Improve performances by adding getBaseTypeId( ) method to libcmis
+                            if ( type->getBaseType( )->getId( ) == typeId )
+                                m_pObjectType = type;
+                        }
+                    }
+                }
+            }
+
+            if ( !bTypeRestricted )
+                m_pObjectType = getSession( xEnv )->getType( typeId );
         }
         return m_pObjectType;
     }
@@ -318,7 +357,39 @@ namespace cmis
         if ( !m_pObject.get() )
         {
             if ( !m_sObjectPath.isEmpty( ) )
-                m_pObject = getSession( xEnv )->getObjectByPath( OUSTR_TO_STDSTR( m_sObjectPath ) );
+            {
+                try
+                {
+                    m_pObject = getSession( xEnv )->getObjectByPath( OUSTR_TO_STDSTR( m_sObjectPath ) );
+                }
+                catch ( const libcmis::Exception& )
+                {
+                    // In some cases, getting the object from the path doesn't work,
+                    // but getting the parent from its path and the get the child in the list is OK.
+                    // It's weird, but needed to handle case where the path isn't the folders/files
+                    // names separated by '/' (as in Lotus Live)
+                    INetURLObject aParentUrl( m_sURL );
+                    string sName = OUSTR_TO_STDSTR( aParentUrl.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DECODE_WITH_CHARSET ) );
+                    aParentUrl.removeSegment( );
+                    rtl::OUString sParentUrl = aParentUrl.GetMainURL( INetURLObject::NO_DECODE );
+       
+                    Content aParent( m_xContext, m_pProvider, new ucbhelper::ContentIdentifier( sParentUrl ) );
+                    libcmis::FolderPtr pParentFolder = boost::dynamic_pointer_cast< libcmis::Folder >( aParent.getObject( xEnv ) );
+                    if ( pParentFolder )
+                    {
+                        vector< libcmis::ObjectPtr > children = pParentFolder->getChildren( );
+                        for ( vector< libcmis::ObjectPtr >::iterator it = children.begin( );
+                              it != children.end() && !m_pObject; ++it )
+                        {
+                            if ( ( *it )->getName( ) == sName )
+                                m_pObject = *it;
+                        }
+                    }
+
+                    if ( !m_pObject )
+                        throw libcmis::Exception( "Object not found" );
+                }
+            }
             else if (!m_sObjectId.isEmpty( ) )
                 m_pObject = getSession( xEnv )->getObject( OUSTR_TO_STDSTR( m_sObjectId ) );
             else
@@ -643,25 +714,6 @@ namespace cmis
         return uno::Reference< sdbc::XRow >( xRow.get() );
     }
 
-    bool Content::exists( const uno::Reference< ucb::XCommandEnvironment >& xEnv )
-    {
-        bool bExists = true;
-        try
-        {
-            if ( !m_sObjectPath.isEmpty( ) )
-                m_pSession->getObjectByPath( OUSTR_TO_STDSTR( m_sObjectPath ) );
-            else if ( !m_sObjectId.isEmpty( ) )
-                getSession( xEnv )->getObject( OUSTR_TO_STDSTR( m_sObjectId ) );
-            // No need to handle the root folder case... how can it not exists?
-        }
-        catch ( const libcmis::Exception& )
-        {
-            bExists = false;
-        }
-
-        return bExists;
-    }
-
     uno::Any Content::open(const ucb::OpenCommandArgument2 & rOpenCommand,
         const uno::Reference< ucb::XCommandEnvironment > & xEnv )
             throw( uno::Exception )
@@ -669,7 +721,7 @@ namespace cmis
         bool bIsFolder = isFolder( xEnv );
 
         // Handle the case of the non-existing file
-        if ( !exists( xEnv ) )
+        if ( !getObject( xEnv ) )
         {
             uno::Sequence< uno::Any > aArgs( 1 );
             aArgs[ 0 ] <<= m_xIdentifier->getContentIdentifier();
@@ -992,7 +1044,17 @@ namespace cmis
                         boost::shared_ptr< ostream > pOut( new ostringstream ( ios_base::binary | ios_base::in | ios_base::out ) );
                         uno::Reference < io::XOutputStream > xOutput = new ucbhelper::StdOutputStream( pOut );
                         copyData( xInputStream, xOutput );
-                        document->setContentStream( pOut, OUSTR_TO_STDSTR( rMimeType ), string( ), bReplaceExisting );
+                        try
+                        {
+                            document->setContentStream( pOut, OUSTR_TO_STDSTR( rMimeType ), string( ), bReplaceExisting );
+                        }
+                        catch ( const libcmis::Exception& )
+                        {
+                            ucbhelper::cancelCommandExecution( uno::makeAny
+                                ( uno::RuntimeException( "Error when setting document content",
+                                    static_cast< cppu::OWeakObject * >( this ) ) ),
+                                xEnv );
+                        }
                     }
                 }
                 else
@@ -1003,16 +1065,36 @@ namespace cmis
 
                     if ( bIsFolder )
                     {
-                        libcmis::FolderPtr pNew = pFolder->createFolder( m_pObjectProps );
-                        sNewPath = STD_TO_OUSTR( newPath );
+                        try
+                        {
+                            libcmis::FolderPtr pNew = pFolder->createFolder( m_pObjectProps );
+                            sNewPath = STD_TO_OUSTR( newPath );
+                        }
+                        catch ( const libcmis::Exception& )
+                        {
+                            ucbhelper::cancelCommandExecution( uno::makeAny
+                                ( uno::RuntimeException( "Error when creating folder",
+                                    static_cast< cppu::OWeakObject * >( this ) ) ),
+                                xEnv );
+                        }
                     }
                     else
                     {
                         boost::shared_ptr< ostream > pOut( new ostringstream ( ios_base::binary | ios_base::in | ios_base::out ) );
                         uno::Reference < io::XOutputStream > xOutput = new ucbhelper::StdOutputStream( pOut );
                         copyData( xInputStream, xOutput );
-                        pFolder->createDocument( m_pObjectProps, pOut, OUSTR_TO_STDSTR( rMimeType ), string() );
-                        sNewPath = STD_TO_OUSTR( newPath );
+                        try
+                        {
+                            pFolder->createDocument( m_pObjectProps, pOut, OUSTR_TO_STDSTR( rMimeType ), string() );
+                            sNewPath = STD_TO_OUSTR( newPath );
+                        }
+                        catch ( const libcmis::Exception& )
+                        {
+                            ucbhelper::cancelCommandExecution( uno::makeAny
+                                ( uno::RuntimeException( "Error when creating document",
+                                    static_cast< cppu::OWeakObject * >( this ) ) ),
+                                xEnv );
+                        }
                     }
                 }
 
@@ -1336,7 +1418,7 @@ namespace cmis
             {
                 URL aCmisUrl( m_sURL );
                 aUrl.removeSegment( );
-                aCmisUrl.setObjectPath( aUrl.GetURLPath( INetURLObject::NO_DECODE ) );
+                aCmisUrl.setObjectPath( aUrl.GetURLPath( INetURLObject::DECODE_WITH_CHARSET ) );
                 sRet = aCmisUrl.asString( );
             }
         }
diff --git a/ucb/source/ucp/cmis/cmis_content.hxx b/ucb/source/ucp/cmis/cmis_content.hxx
index b28c301..7260cb3 100644
--- a/ucb/source/ucp/cmis/cmis_content.hxx
+++ b/ucb/source/ucp/cmis/cmis_content.hxx
@@ -99,8 +99,6 @@ private:
     libcmis::Session* getSession( const com::sun::star::uno::Reference< com::sun::star::ucb::XCommandEnvironment >& xEnv );
     libcmis::ObjectTypePtr getObjectType( const com::sun::star::uno::Reference< com::sun::star::ucb::XCommandEnvironment >& xEnv );
 
-    bool exists( const com::sun::star::uno::Reference< com::sun::star::ucb::XCommandEnvironment >& xEnv );
-
 private:
     typedef rtl::Reference< Content > ContentRef;
     typedef std::list< ContentRef > ContentRefList;
diff --git a/ucb/source/ucp/cmis/cmis_url.cxx b/ucb/source/ucp/cmis/cmis_url.cxx
index 40d7a25..dc23c9e 100644
--- a/ucb/source/ucp/cmis/cmis_url.cxx
+++ b/ucb/source/ucp/cmis/cmis_url.cxx
@@ -107,13 +107,34 @@ namespace cmis
 
         if ( !m_sPath.isEmpty( ) )
         {
-            if ( m_sPath[0] != '/' )
-                sUrl += "/";
-            sUrl += m_sPath;
+            sal_Int32 nPos = -1;
+            rtl::OUString sEncodedPath;
+            do
+            {
+                sal_Int32 nStartPos = nPos + 1;
+                nPos = m_sPath.indexOf( '/', nStartPos );
+                sal_Int32 nLen = nPos - nStartPos;
+                if ( nPos == -1 )
+                    nLen = m_sPath.getLength( ) - nStartPos;
+                rtl::OUString sSegment = m_sPath.copy( nStartPos, nLen );
+
+                if ( !sSegment.isEmpty( ) )
+                {
+                    sEncodedPath += "/" + rtl::Uri::encode( sSegment,
+                            rtl_UriCharClassRelSegment,
+                            rtl_UriEncodeKeepEscapes,
+                            RTL_TEXTENCODING_UTF8 );
+                }
+            }
+            while ( nPos != -1 );
+            sUrl += sEncodedPath;
         }
         else if ( !m_sId.isEmpty( ) )
         {
-            sUrl += "#" + m_sId;
+            sUrl += "#" + rtl::Uri::encode( m_sId,
+                rtl_UriCharClassRelSegment,
+                rtl_UriEncodeKeepEscapes,
+                RTL_TEXTENCODING_UTF8 );
         }
 
         return sUrl;
commit 6dbc7fdd094b3c29de0932c4ea3afc1a5a10d7fc
Author: Michael Meeks <michael.meeks at suse.com>
Date:   Wed Feb 13 11:35:13 2013 +0000

    asynchronous word-count.
    
    Change-Id: Ie78819590bca52f36406022a3954651c42c52540
    Signed-off-by: Caolán McNamara <caolanm at redhat.com>

diff --git a/sw/inc/IDocumentStatistics.hxx b/sw/inc/IDocumentStatistics.hxx
index 4ec926a..2c5a0501 100644
--- a/sw/inc/IDocumentStatistics.hxx
+++ b/sw/inc/IDocumentStatistics.hxx
@@ -35,13 +35,26 @@
 
     /** Document - Statistics
     */
+    /// Returns a reference to the existing document statistics
     virtual const SwDocStat &GetDocStat() const = 0;
 
-    virtual const SwDocStat &GetUpdatedDocStat() = 0;
+    /**
+      * Updates the document statistics if the document has been
+      * modified and returns a reference to the result.
+      * \param bCompleteAsync if true will return a partial result,
+      * and potentially trigger a timeout to complete the work.
+      */
+    virtual const SwDocStat &GetUpdatedDocStat(bool bCompleteAsync) = 0;
 
+    /// Set the document statistics
     virtual void SetDocStat(const SwDocStat& rStat) = 0;
 
-    virtual void UpdateDocStat() = 0;
+    /**
+      * Updates the internal document's statistics
+      * \param bCompleteAsync if true it may do part of the
+      * work and trigger a timeout to complete it.
+      */
+    virtual void UpdateDocStat(bool bCompleteAsync) = 0;
 
 protected:
     virtual ~IDocumentStatistics() {};
diff --git a/sw/inc/doc.hxx b/sw/inc/doc.hxx
index 34ba7df..d71ce52 100644
--- a/sw/inc/doc.hxx
+++ b/sw/inc/doc.hxx
@@ -282,6 +282,7 @@ class SW_DLLPUBLIC SwDoc :
     */
     Timer       aIdleTimer;             ///< Own IdleTimer
     Timer       aOLEModifiedTimer;      ///< Timer for update modified OLE-Objecs
+    Timer       aStatsUpdateTimer;      ///< Timer for asynchronous stats calculation
     SwDBData    aDBData;                ///< database descriptor
     ::com::sun::star::uno::Sequence <sal_Int8 > aRedlinePasswd;
     String      sTOIAutoMarkURL;        ///< ::com::sun::star::util::URL of table of index AutoMark file
@@ -909,9 +910,9 @@ public:
     */
     virtual void DocInfoChgd();
     virtual const SwDocStat &GetDocStat() const;
-    virtual const SwDocStat &GetUpdatedDocStat();
+    virtual const SwDocStat &GetUpdatedDocStat(bool bCompleteAsync = false);
     virtual void SetDocStat(const SwDocStat& rStat);
-    virtual void UpdateDocStat();
+    virtual void UpdateDocStat(bool bCompleteAsync = false);
 
     /** IDocumentState
     */
@@ -2070,6 +2071,17 @@ private:
     void CopyMasterHeader(const SwPageDesc &rChged, const SwFmtHeader &rHead, SwPageDesc *pDesc, bool bLeft);
     /// Copies master footer to left / first one, if necessary - used by ChgPageDesc().
     void CopyMasterFooter(const SwPageDesc &rChged, const SwFmtFooter &rFoot, SwPageDesc *pDesc, bool bLeft);
+
+    /** continue computing a chunk of document statistics
+      * \param nTextNodes number of paragraphs to calculate before
+      * exiting
+      *
+      * returns false when there is no more to calculate
+      */
+    bool IncrementalDocStatCalculate(long nTextNodes = 250);
+
+    /// Our own 'StatsUpdateTimer' calls the following method
+    DECL_LINK( DoIdleStatsUpdate, Timer * );
 };
 
 // This method is called in Dtor of SwDoc and deletes cache of ContourObjects.
diff --git a/sw/inc/ndtxt.hxx b/sw/inc/ndtxt.hxx
index c0bf53b..2b0d8cd 100644
--- a/sw/inc/ndtxt.hxx
+++ b/sw/inc/ndtxt.hxx
@@ -745,8 +745,8 @@ public:
                             xub_StrLen nStart, xub_StrLen nEnd,
                             SwUndoTransliterate* pUndo = 0 );
 
-    /// count words in given range
-    void CountWords( SwDocStat& rStat, xub_StrLen nStart, xub_StrLen nEnd ) const;
+    /// count words in given range - returns true if we refreshed out count
+    bool CountWords( SwDocStat& rStat, xub_StrLen nStart, xub_StrLen nEnd ) const;
 
     /** Checks some global conditions like loading or destruction of document
        to economize notifications */
diff --git a/sw/source/core/doc/doc.cxx b/sw/source/core/doc/doc.cxx
index 80cfa84..7a27c23 100644
--- a/sw/source/core/doc/doc.cxx
+++ b/sw/source/core/doc/doc.cxx
@@ -55,6 +55,7 @@
 #include <editeng/rsiditem.hxx>
 #include <unotools/charclass.hxx>
 #include <unotools/localedatawrapper.hxx>
+#include <vcl/timer.hxx>
 
 #include <swatrset.hxx>
 #include <swmodule.hxx>
@@ -112,6 +113,7 @@
 #include <shellres.hxx>
 #include <txtfrm.hxx>
 #include <attrhint.hxx>
+#include <view.hxx>
 
 #include <wdocsh.hxx>           // SwWebDocShell
 #include <prtopt.hxx>           // SwPrintOptions
@@ -1146,10 +1148,6 @@ bool SwDoc::UpdateParRsid( SwTxtNode *pTxtNode, sal_uInt32 nVal )
     return pTxtNode->SetAttr( aRsid );
 }
 
-
-/*************************************************************************
- *             void SetDocStat( const SwDocStat& rStat );
- *************************************************************************/
 void SwDoc::SetDocStat( const SwDocStat& rStat )
 {
     *pDocStat = rStat;
@@ -1160,11 +1158,11 @@ const SwDocStat& SwDoc::GetDocStat() const
     return *pDocStat;
 }
 
-const SwDocStat& SwDoc::GetUpdatedDocStat()
+const SwDocStat& SwDoc::GetUpdatedDocStat( bool bCompleteAsync )
 {
-    if (pDocStat->bModified)
+    if( pDocStat->bModified )
     {
-        UpdateDocStat();
+        UpdateDocStat( bCompleteAsync );
     }
     return *pDocStat;
 }
@@ -1686,92 +1684,120 @@ void SwDoc::CalculatePagePairsForProspectPrinting(
     // thus we are done here.
 }
 
-/*************************************************************************
- *            void UpdateDocStat();
- *************************************************************************/
-void SwDoc::UpdateDocStat()
+// returns true while there is more to do
+bool SwDoc::IncrementalDocStatCalculate( long nTextNodes )
 {
-    if( pDocStat->bModified )
-    {
-        pDocStat->Reset();
-        pDocStat->nPara = 0;        // default is 1!
-        SwNode* pNd;
+    pDocStat->Reset();
+    pDocStat->nPara = 0; // default is 1!
+    SwNode* pNd;
 
-        for( sal_uLong i = GetNodes().Count(); i; )
+    // This is the inner loop - at least while the paras are dirty.
+    for( sal_uLong i = GetNodes().Count(); i > 0 && nTextNodes > 0; )
+    {
+        switch( ( pNd = GetNodes()[ --i ])->GetNodeType() )
         {
-            switch( ( pNd = GetNodes()[ --i ])->GetNodeType() )
-            {
-            case ND_TEXTNODE:
-                ((SwTxtNode*)pNd)->CountWords( *pDocStat, 0, ((SwTxtNode*)pNd)->GetTxt().Len() );
-                break;
-            case ND_TABLENODE:      ++pDocStat->nTbl;   break;
-            case ND_GRFNODE:        ++pDocStat->nGrf;   break;
-            case ND_OLENODE:        ++pDocStat->nOLE;   break;
-            case ND_SECTIONNODE:    break;
-            }
+        case ND_TEXTNODE:
+        {
+            SwTxtNode *pTxt = static_cast< SwTxtNode * >( pNd );
+            if( pTxt->CountWords( *pDocStat, 0, pTxt->GetTxt().Len() ) )
+                nTextNodes--;
+            break;
+        }
+        case ND_TABLENODE:      ++pDocStat->nTbl;   break;
+        case ND_GRFNODE:        ++pDocStat->nGrf;   break;
+        case ND_OLENODE:        ++pDocStat->nOLE;   break;
+        case ND_SECTIONNODE:    break;
         }
+    }
 
-        // #i93174#: notes contain paragraphs that are not nodes
+    // #i93174#: notes contain paragraphs that are not nodes
+    {
+        SwFieldType * const pPostits( GetSysFldType(RES_POSTITFLD) );
+        SwIterator<SwFmtFld,SwFieldType> aIter( *pPostits );
+        for( SwFmtFld* pFmtFld = aIter.First(); pFmtFld;  pFmtFld = aIter.Next() )
         {
-            SwFieldType * const pPostits( GetSysFldType(RES_POSTITFLD) );
-            SwIterator<SwFmtFld,SwFieldType> aIter( *pPostits );
-            for( SwFmtFld* pFmtFld = aIter.First(); pFmtFld;  pFmtFld = aIter.Next() )
+            if (pFmtFld->IsFldInDoc())
             {
-                if (pFmtFld->IsFldInDoc())
-                {
-                    SwPostItField const * const pField(
-                        static_cast<SwPostItField const*>(pFmtFld->GetFld()));
-                    pDocStat->nAllPara += pField->GetNumberOfParagraphs();
-                }
+                SwPostItField const * const pField(
+                    static_cast<SwPostItField const*>(pFmtFld->GetFld()));
+                pDocStat->nAllPara += pField->GetNumberOfParagraphs();
             }
         }
+    }
 
-        pDocStat->nPage     = GetCurrentLayout() ? GetCurrentLayout()->GetPageNum() : 0;    //swmod 080218
-        pDocStat->bModified = sal_False;
-
-        com::sun::star::uno::Sequence < com::sun::star::beans::NamedValue > aStat( pDocStat->nPage ? 8 : 7);
-        sal_Int32 n=0;
-        aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("TableCount"));
-        aStat[n++].Value <<= (sal_Int32)pDocStat->nTbl;
-        aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("ImageCount"));
-        aStat[n++].Value <<= (sal_Int32)pDocStat->nGrf;
-        aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("ObjectCount"));
-        aStat[n++].Value <<= (sal_Int32)pDocStat->nOLE;
-        if ( pDocStat->nPage )
-        {
-            aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("PageCount"));
-            aStat[n++].Value <<= (sal_Int32)pDocStat->nPage;
-        }
-        aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("ParagraphCount"));
-        aStat[n++].Value <<= (sal_Int32)pDocStat->nPara;
-        aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("WordCount"));
-        aStat[n++].Value <<= (sal_Int32)pDocStat->nWord;
-        aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("CharacterCount"));
-        aStat[n++].Value <<= (sal_Int32)pDocStat->nChar;
-        aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("NonWhitespaceCharacterCount"));
-        aStat[n++].Value <<= (sal_Int32)pDocStat->nCharExcludingSpaces;
-
-        // For e.g. autotext documents there is no pSwgInfo (#i79945)
-        SfxObjectShell * const pObjShell( GetDocShell() );
-        if (pObjShell)
-        {
-            const uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
+    pDocStat->nPage     = GetCurrentLayout() ? GetCurrentLayout()->GetPageNum() : 0;
+    pDocStat->bModified = sal_False;
+
+    com::sun::star::uno::Sequence < com::sun::star::beans::NamedValue > aStat( pDocStat->nPage ? 8 : 7);
+    sal_Int32 n=0;
+    aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("TableCount"));
+    aStat[n++].Value <<= (sal_Int32)pDocStat->nTbl;
+    aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("ImageCount"));
+    aStat[n++].Value <<= (sal_Int32)pDocStat->nGrf;
+    aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("ObjectCount"));
+    aStat[n++].Value <<= (sal_Int32)pDocStat->nOLE;
+    if ( pDocStat->nPage )
+    {
+        aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("PageCount"));
+        aStat[n++].Value <<= (sal_Int32)pDocStat->nPage;
+    }
+    aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("ParagraphCount"));
+    aStat[n++].Value <<= (sal_Int32)pDocStat->nPara;
+    aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("WordCount"));
+    aStat[n++].Value <<= (sal_Int32)pDocStat->nWord;
+    aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("CharacterCount"));
+    aStat[n++].Value <<= (sal_Int32)pDocStat->nChar;
+    aStat[n].Name = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("NonWhitespaceCharacterCount"));
+    aStat[n++].Value <<= (sal_Int32)pDocStat->nCharExcludingSpaces;
+
+    // For e.g. autotext documents there is no pSwgInfo (#i79945)
+    SfxObjectShell * const pObjShell( GetDocShell() );
+    if (pObjShell)
+    {
+        const uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
                 pObjShell->GetModel(), uno::UNO_QUERY_THROW);
-            const uno::Reference<document::XDocumentProperties> xDocProps(
+        const uno::Reference<document::XDocumentProperties> xDocProps(
                 xDPS->getDocumentProperties());
-            // #i96786#: do not set modified flag when updating statistics
-            const bool bDocWasModified( IsModified() );
-            const ModifyBlocker_Impl b(pObjShell);
-            xDocProps->setDocumentStatistics(aStat);
-            if (!bDocWasModified)
-            {
-                ResetModified();
-            }
+        // #i96786#: do not set modified flag when updating statistics
+        const bool bDocWasModified( IsModified() );
+        const ModifyBlocker_Impl b(pObjShell);
+        xDocProps->setDocumentStatistics(aStat);
+        if (!bDocWasModified)
+        {
+            ResetModified();
         }
+    }
+
+    // optionally update stat. fields
+    SwFieldType *pType = GetSysFldType(RES_DOCSTATFLD);
+    pType->UpdateFlds();
 
-        // optionally update stat. fields
-        SwFieldType *pType = GetSysFldType(RES_DOCSTATFLD);
-        pType->UpdateFlds();
+    return nTextNodes <= 0;
+}
+
+IMPL_LINK( SwDoc, DoIdleStatsUpdate, Timer *, pTimer )
+{
+    (void)pTimer;
+    if( IncrementalDocStatCalculate( 1000 ) )
+        aStatsUpdateTimer.Start();
+
+    SwView* pView = GetDocShell() ? GetDocShell()->GetView() : NULL;
+    if( pView )
+        pView->UpdateDocStats();
+    return 0;
+}
+
+void SwDoc::UpdateDocStat( bool bCompleteAsync )
+{
+    if( pDocStat->bModified )
+    {
+        if (!bCompleteAsync)
+        {
+            while (IncrementalDocStatCalculate()) {}
+            aStatsUpdateTimer.Stop();
+        }
+        else if (IncrementalDocStatCalculate())
+            aStatsUpdateTimer.Start();
     }
 }
 
diff --git a/sw/source/core/doc/docnew.cxx b/sw/source/core/doc/docnew.cxx
index d5f55f7..9eb5983 100644
--- a/sw/source/core/doc/docnew.cxx
+++ b/sw/source/core/doc/docnew.cxx
@@ -403,6 +403,9 @@ SwDoc::SwDoc()
     aOLEModifiedTimer.SetTimeout( 1000 );
     aOLEModifiedTimer.SetTimeoutHdl( LINK( this, SwDoc, DoUpdateModifiedOLE ));
 
+    aStatsUpdateTimer.SetTimeout( 100 );
+    aStatsUpdateTimer.SetTimeoutHdl( LINK( this, SwDoc, DoIdleStatsUpdate ) );
+
     // Create DBMgr
     pNewDBMgr = new SwNewDBMgr;
 
@@ -508,6 +511,7 @@ SwDoc::~SwDoc()
     SetDefault(aCharFmt);
 
     StopIdling();   // stop idle timer
+    aStatsUpdateTimer.Stop();
 
     delete pUnoCallBack, pUnoCallBack = 0;
     delete pURLStateChgd;
diff --git a/sw/source/core/txtnode/txtedt.cxx b/sw/source/core/txtnode/txtedt.cxx
index e0cc703..a538701 100644
--- a/sw/source/core/txtnode/txtedt.cxx
+++ b/sw/source/core/txtnode/txtedt.cxx
@@ -1842,22 +1842,24 @@ void SwTxtNode::ReplaceTextOnly( xub_StrLen nPos, xub_StrLen nLen,
     NotifyClients( 0, &aHint );
 }
 
-void SwTxtNode::CountWords( SwDocStat& rStat,
+// the return values allows us to see if we did the heavy-
+// lifting required to actually break and count the words.
+bool SwTxtNode::CountWords( SwDocStat& rStat,
                             xub_StrLen nStt, xub_StrLen nEnd ) const
 {
     if( nStt > nEnd )
     {   // bad call
-        return;
+        return false;
     }
     if (IsInRedlines())
     {   //not counting txtnodes used to hold deleted redline content
-        return;
+        return false;
     }
     bool bCountAll = ( (0 == nStt) && (GetTxt().Len() == nEnd) );
     ++rStat.nAllPara; // #i93174#: count _all_ paragraphs
     if ( IsHidden() )
     {   // not counting hidden paras
-        return;
+        return false;
     }
     // count words in numbering string if started at beginning of para:
     bool bCountNumbering = nStt == 0;
@@ -1874,7 +1876,7 @@ void SwTxtNode::CountWords( SwDocStat& rStat,
 
     if( nStt == nEnd && !bCountNumbering)
     {   // unnumbered empty node or empty selection
-        return;
+        return false;
     }
 
     // count of non-empty paras
@@ -1888,7 +1890,7 @@ void SwTxtNode::CountWords( SwDocStat& rStat,
         rStat.nAsianWord += GetParaNumberOfAsianWords();
         rStat.nChar += GetParaNumberOfChars();
         rStat.nCharExcludingSpaces += GetParaNumberOfCharsExcludingSpaces();
-        return;
+        return false;
     }
 
     // ConversionMap to expand fields, remove invisible and redline deleted text for scanner
@@ -1902,7 +1904,7 @@ void SwTxtNode::CountWords( SwDocStat& rStat,
     if (aExpandText.isEmpty() && !bCountNumbering)
     {
         OSL_ENSURE(aExpandText.getLength() >= 0, "Node text expansion error: length < 0." );
-        return;
+        return false;
     }
 
     //do the count
@@ -1990,6 +1992,8 @@ void SwTxtNode::CountWords( SwDocStat& rStat,
     rStat.nAsianWord += nTmpAsianWords;
     rStat.nChar += nTmpChars;
     rStat.nCharExcludingSpaces += nTmpCharsExcludingSpaces;
+
+    return true;
 }
 
 //
diff --git a/sw/source/ui/dialog/wordcountdialog.cxx b/sw/source/ui/dialog/wordcountdialog.cxx
index 5a43642..e2efe0a 100644
--- a/sw/source/ui/dialog/wordcountdialog.cxx
+++ b/sw/source/ui/dialog/wordcountdialog.cxx
@@ -32,7 +32,7 @@
 #include <vcl/msgbox.hxx>
 
 IMPL_LINK_NOARG(SwWordCountFloatDlg, CloseHdl)
-{   
+{
     SfxViewFrame* pVFrame = ::GetActiveView()->GetViewFrame();
     if (pVFrame != NULL)
     {
diff --git a/sw/source/ui/inc/view.hxx b/sw/source/ui/inc/view.hxx
index ff93acf..c1be2bb 100644
--- a/sw/source/ui/inc/view.hxx
+++ b/sw/source/ui/inc/view.hxx
@@ -355,7 +355,9 @@ class SW_DLLPUBLIC SwView: public SfxViewShell
 
     SW_DLLPRIVATE virtual void  Move();
 
-    SW_DLLPRIVATE sal_Bool          InsertGraphicDlg( SfxRequest& );
+    SW_DLLPRIVATE sal_Bool      InsertGraphicDlg( SfxRequest& );
+
+    SW_DLLPRIVATE void          DocumentStatsChanged();
 
 protected:
 
@@ -654,6 +656,8 @@ public:
 
     // exhibition hack (MA,MBA)
     void SelectShellForDrop();
+
+    void UpdateDocStats();
 };
 
 // ----------------- inline Methoden ----------------------
diff --git a/sw/source/ui/uiview/view.cxx b/sw/source/ui/uiview/view.cxx
index 44fa043..df77bd1 100644
--- a/sw/source/ui/uiview/view.cxx
+++ b/sw/source/ui/uiview/view.cxx
@@ -1048,6 +1048,7 @@ SwView::~SwView()
     bInDtor = sal_True;
     pEditWin->Hide(); // damit kein Paint Aerger machen kann!
     // An der SwDocShell den Pointer auf die View ruecksetzen
+
     SwDocShell* pDocSh = GetDocShell();
     if( pDocSh && pDocSh->GetView() == this )
         pDocSh->SetView( 0 );
diff --git a/sw/source/ui/uiview/view2.cxx b/sw/source/ui/uiview/view2.cxx
index a1ada624..61b9440 100644
--- a/sw/source/ui/uiview/view2.cxx
+++ b/sw/source/ui/uiview/view2.cxx
@@ -1203,6 +1203,13 @@ void SwView::UpdatePageNums(sal_uInt16 nPhyNum, sal_uInt16 nVirtNum, const Strin
     rBnd.Update( FN_STAT_PAGE );
 }
 
+void SwView::UpdateDocStats()
+{
+    SfxBindings &rBnd = GetViewFrame()->GetBindings();
+    rBnd.Invalidate( FN_STAT_WORDCOUNT );
+    rBnd.Update( FN_STAT_WORDCOUNT );
+}
+
 /*--------------------------------------------------------------------
     Beschreibung:   Status der Stauszeile
  --------------------------------------------------------------------*/
@@ -1241,7 +1248,7 @@ void SwView::StateStatusLine(SfxItemSet &rSet)
                 SwDocStat documentStats;
                 {
                     rShell.CountWords(selectionStats);
-                    documentStats = rShell.GetDoc()->GetUpdatedDocStat();
+                    documentStats = rShell.GetDoc()->GetUpdatedDocStat( true /* complete-async */ );
                 }
 
                 const sal_uInt32 stringId = selectionStats.nWord? STR_STATUSBAR_WORDCOUNT : STR_STATUSBAR_WORDCOUNT_NO_SELECTION;


More information about the Libreoffice-commits mailing list