lib Library API Documentation

koDocument.cc

00001 /* This file is part of the KDE project
00002    Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
00003    Copyright (C) 2000-2005 David Faure <faure@kde.org>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00018    Boston, MA 02111-1307, USA.
00019 */
00020 
00021 #include "koDocument.h"
00022 
00023 #include "koDocument_p.h"
00024 #include "KoDocumentIface.h"
00025 #include "koDocumentChild.h"
00026 #include "koView.h"
00027 #include "koMainWindow.h"
00028 #include "koFilterManager.h"
00029 #include "koDocumentInfo.h"
00030 #include "koOasisStyles.h"
00031 #include "koOasisStore.h"
00032 #include "koxmlns.h"
00033 
00034 #include <koStoreDevice.h>
00035 #include <koxmlwriter.h>
00036 
00037 #include <kapplication.h>
00038 #include <kdebug.h>
00039 #include <kdeversion.h>
00040 #include <kfileitem.h>
00041 #include <kiconloader.h>
00042 #include <kio/job.h>
00043 #include <kio/netaccess.h>
00044 #include <klocale.h>
00045 #include <kmessagebox.h>
00046 #include <kmimetype.h>
00047 #include <kparts/partmanager.h>
00048 #include <kprinter.h>
00049 #include <ksavefile.h>
00050 
00051 #include <qbuffer.h>
00052 #include <qcursor.h>
00053 #include <qdir.h>
00054 #include <qfile.h>
00055 #include <qfileinfo.h>
00056 #include <qimage.h>
00057 #include <qmap.h>
00058 #include <qpainter.h>
00059 #include <qtimer.h>
00060 #include <qxml.h>
00061 
00062 #include <config.h>
00063 #include <assert.h>
00064 
00065 
00066 // Define the protocol used here for embedded documents' URL
00067 // This used to "store" but KURL didn't like it,
00068 // so let's simply make it "tar" !
00069 #define STORE_PROTOCOL "tar"
00070 // The internal path is a hack to make KURL happy and still pass
00071 // some kind of relative path to KoDocumentChild
00072 #define INTERNAL_PROTOCOL "intern"
00073 #define INTERNAL_PREFIX "intern:/"
00074 // Warning, keep it sync in koStore.cc and koDocumentChild.cc
00075 
00076 QPtrList<KoDocument> *KoDocument::s_documentList=0L;
00077 
00078 using namespace std;
00079 class KoViewWrapperWidget;
00080 
00081 /**********************************************************
00082  *
00083  * KoDocument
00084  *
00085  **********************************************************/
00086 
00087 const int KoDocument::s_defaultAutoSave = 300; // 5 minutes
00088 
00089 class KoDocument::Private
00090 {
00091 public:
00092     Private() :
00093         m_dcopObject( 0L ),
00094         filterManager( 0L ),
00095         m_specialOutputFlag( 0 ), // default is native format
00096         m_isImporting( false ), m_isExporting( false ),
00097         m_numOperations( 0 ),
00098         modifiedAfterAutosave( false ),
00099         m_autosaving( false ),
00100         m_shouldCheckAutoSaveFile( true ),
00101         m_autoErrorHandlingEnabled( true ),
00102         m_backupFile( true ),
00103         m_backupPath( QString::null ),
00104         m_doNotSaveExtDoc( false ),
00105         m_current( false ),
00106         m_storeInternal( false ),
00107         m_bLoading( false )
00108     {
00109         m_confirmNonNativeSave[0] = true;
00110         m_confirmNonNativeSave[1] = true;
00111     }
00112 
00113     QPtrList<KoView> m_views;
00114     QPtrList<KoDocumentChild> m_children;
00115     QPtrList<KoMainWindow> m_shells;
00116     QValueList<QDomDocument> m_viewBuildDocuments;
00117 
00118     KoViewWrapperWidget *m_wrapperWidget;
00119     KoDocumentIface * m_dcopObject;
00120     KoDocumentInfo *m_docInfo;
00121 
00122     KoFilterManager * filterManager; // The filter-manager to use when loading/saving [for the options]
00123 
00124     QCString mimeType; // The actual mimetype of the document
00125     QCString outputMimeType; // The mimetype to use when saving
00126     bool m_confirmNonNativeSave [2]; // used to pop up a dialog when saving for the
00127                                      // first time if the file is in a foreign format
00128                                      // (Save/Save As, Export)
00129     int m_specialOutputFlag; // See KoFileDialog in koMainWindow.cc
00130     bool m_isImporting, m_isExporting; // File --> Import/Export vs File --> Open/Save
00131 
00132     QTimer m_autoSaveTimer;
00133     QString lastErrorMessage; // see openFile()
00134     int m_autoSaveDelay; // in seconds, 0 to disable.
00135     int m_numOperations;
00136     bool modifiedAfterAutosave;
00137     bool m_bSingleViewMode;
00138     bool m_autosaving;
00139     bool m_shouldCheckAutoSaveFile; // usually true
00140     bool m_autoErrorHandlingEnabled; // usually true
00141     bool m_backupFile;
00142     QString m_backupPath;
00143     bool m_doNotSaveExtDoc; // makes it possible to save only internally stored child documents
00144     bool m_current;
00145     bool m_storeInternal; // Store this doc internally even if url is external
00146     bool m_bLoading; // True while loading (openURL is async)
00147 };
00148 
00149 // Used in singleViewMode
00150 class KoViewWrapperWidget : public QWidget
00151 {
00152 public:
00153     KoViewWrapperWidget( QWidget *parent, const char *name )
00154         : QWidget( parent, name )
00155     {
00156         KGlobal::locale()->insertCatalogue("koffice");
00157         // Tell the iconloader about share/apps/koffice/icons
00158         KGlobal::iconLoader()->addAppDir("koffice");
00159         m_view = 0L;
00160         // Avoid warning from KParts - we'll have the KoView as focus proxy anyway
00161         setFocusPolicy( ClickFocus );
00162     }
00163 
00164     virtual ~KoViewWrapperWidget() {
00165         setFocusProxy( 0 ); // to prevent a crash due to clearFocus (#53466)
00166     }
00167 
00168     virtual void resizeEvent( QResizeEvent * )
00169     {
00170         QObject *wid = child( 0, "QWidget" );
00171         if ( wid )
00172             static_cast<QWidget *>(wid)->setGeometry( 0, 0, width(), height() );
00173     }
00174 
00175     virtual void childEvent( QChildEvent *ev )
00176     {
00177         if ( ev->type() == QEvent::ChildInserted )
00178             resizeEvent( 0L );
00179     }
00180 
00181     // Called by openFile()
00182     void setKoView( KoView * view ) {
00183         m_view = view;
00184         setFocusProxy( m_view );
00185     }
00186     KoView * koView() const { return m_view; }
00187 private:
00188     KoView* m_view;
00189 };
00190 
00191 KoBrowserExtension::KoBrowserExtension( KoDocument * doc, const char * name )
00192     : KParts::BrowserExtension( doc, name )
00193 {
00194     emit enableAction( "print", true );
00195 }
00196 
00197 void KoBrowserExtension::print()
00198 {
00199     KoDocument * doc = static_cast<KoDocument *>( parent() );
00200     KoViewWrapperWidget * wrapper = static_cast<KoViewWrapperWidget *>( doc->widget() );
00201     KoView * view = wrapper->koView();
00202     // TODO remove code duplication (KoMainWindow), by moving this to KoView
00203     KPrinter printer;
00204     // ### TODO: apply global koffice settings here
00205     view->setupPrinter( printer );
00206     if ( printer.setup( view ) )
00207         view->print( printer );
00208 }
00209 
00210 KoDocument::KoDocument( QWidget * parentWidget, const char *widgetName, QObject* parent, const char* name, bool singleViewMode )
00211     : KParts::ReadWritePart( parent, name )
00212 {
00213     if(s_documentList==0L)
00214         s_documentList=new QPtrList<KoDocument>;
00215     s_documentList->append(this);
00216 
00217     d = new Private;
00218     m_bEmpty = TRUE;
00219     connect( &d->m_autoSaveTimer, SIGNAL( timeout() ), this, SLOT( slotAutoSave() ) );
00220     setAutoSave( s_defaultAutoSave );
00221     d->m_bSingleViewMode = singleViewMode;
00222 
00223 
00224     // the parent setting *always* overrides! (Simon)
00225     if ( parent )
00226     {
00227         if ( parent->inherits( "KoDocument" ) )
00228             d->m_bSingleViewMode = ((KoDocument *)parent)->isSingleViewMode();
00229         else if ( parent->inherits( "KParts::Part" ) )
00230             d->m_bSingleViewMode = true;
00231     }
00232 
00233     if ( singleViewMode )
00234     {
00235         d->m_wrapperWidget = new KoViewWrapperWidget( parentWidget, widgetName );
00236         setWidget( d->m_wrapperWidget );
00237         kdDebug(30003) << "creating KoBrowserExtension" << endl;
00238         (void) new KoBrowserExtension( this ); // ## only if embedded into a browser?
00239     }
00240 
00241     d->m_docInfo = new KoDocumentInfo( this, "document info" );
00242 
00243     m_pageLayout.ptWidth = 0;
00244     m_pageLayout.ptHeight = 0;
00245     m_pageLayout.ptTop = 0;
00246     m_pageLayout.ptBottom = 0;
00247     m_pageLayout.ptLeft = 0;
00248     m_pageLayout.ptRight = 0;
00249 
00250     // A way to 'fix' the job's window, since we have no widget known to KParts
00251     if ( !singleViewMode )
00252         connect( this, SIGNAL( started( KIO::Job* ) ), SLOT( slotStarted( KIO::Job* ) ) );
00253 }
00254 
00255 KoDocument::~KoDocument()
00256 {
00257     d->m_autoSaveTimer.stop();
00258 
00259     QPtrListIterator<KoDocumentChild> childIt( d->m_children );
00260     for (; childIt.current(); ++childIt )
00261         disconnect( childIt.current(), SIGNAL( destroyed() ),
00262                     this, SLOT( slotChildDestroyed() ) );
00263 
00264     // Tell our views that the document is already destroyed and
00265     // that they shouldn't try to access it.
00266     QPtrListIterator<KoView> vIt( d->m_views );
00267     for (; vIt.current(); ++vIt )
00268         vIt.current()->setDocumentDeleted();
00269 
00270     d->m_children.setAutoDelete( true );
00271     d->m_children.clear();
00272 
00273     d->m_shells.setAutoDelete( true );
00274     d->m_shells.clear();
00275 
00276     delete d->m_dcopObject;
00277     delete d->filterManager;
00278     delete d;
00279     s_documentList->removeRef(this);
00280     // last one?
00281     if(s_documentList->isEmpty()) {
00282         delete s_documentList;
00283         s_documentList=0;
00284     }
00285 }
00286 
00287 bool KoDocument::isSingleViewMode() const
00288 {
00289     return d->m_bSingleViewMode;
00290 }
00291 
00292 bool KoDocument::isEmbedded() const
00293 {
00294     return dynamic_cast<KoDocument *>( parent() ) != 0;
00295 }
00296 
00297 KoView *KoDocument::createView( QWidget *parent, const char *name )
00298 {
00299     KoView *view=createViewInstance(parent, name);
00300     addView(view);
00301     return view;
00302 }
00303 
00304 bool KoDocument::exp0rt( const KURL & _url )
00305 {
00306     bool ret;
00307 
00308     d->m_isExporting = true;
00309 
00310     //
00311     // Preserve a lot of state here because we need to restore it in order to
00312     // be able to fake a File --> Export.  Can't do this in saveFile() because,
00313     // for a start, KParts has already set m_url and m_file and because we need
00314     // to restore the modified flag etc. and don't want to put a load on anyone
00315     // reimplementing saveFile() (Note: import() and export() will remain
00316     // non-virtual).
00317     //
00318     KURL oldURL = m_url;
00319     QString oldFile = m_file;
00320 
00321     bool wasModified = isModified ();
00322     QCString oldMimeType = mimeType ();
00323 
00324 
00325     // save...
00326     ret = saveAs( _url );
00327 
00328 
00329     //
00330     // This is sooooo hacky :(
00331     // Hopefully we will restore enough state.
00332     //
00333     kdDebug(30003) << "Restoring KoDocument state to before export" << endl;
00334 
00335     // always restore m_url & m_file because KParts has changed them
00336     // (regardless of failure or success)
00337     m_url = oldURL;
00338     m_file = oldFile;
00339 
00340     // on successful export we need to restore modified etc. too
00341     // on failed export, mimetype/modified hasn't changed anyway
00342     if (ret)
00343     {
00344         setModified (wasModified);
00345         d->mimeType = oldMimeType;
00346     }
00347 
00348 
00349     d->m_isExporting = false;
00350 
00351     return ret;
00352 }
00353 
00354 bool KoDocument::saveFile()
00355 {
00356     kdDebug(30003) << "KoDocument::saveFile() doc='" << url().url() <<"'"<< endl;
00357 
00358     // Save it to be able to restore it after a failed save
00359     const bool wasModified = isModified ();
00360 
00361     // The output format is set by koMainWindow, and by openFile
00362     QCString outputMimeType = d->outputMimeType;
00363     //Q_ASSERT( !outputMimeType.isEmpty() ); // happens when using the DCOP method saveAs
00364     if ( outputMimeType.isEmpty() )
00365         outputMimeType = d->outputMimeType = nativeFormatMimeType();
00366 
00367     QApplication::setOverrideCursor( waitCursor );
00368 
00369     if ( backupFile() ) {
00370         if ( url().isLocalFile() )
00371             KSaveFile::backupFile( url().path(), d->m_backupPath );
00372         else {
00373             KIO::UDSEntry entry;
00374             if ( KIO::NetAccess::stat( url(), entry, shells().current() ) ) { // this file exists => backup
00375                 emit sigStatusBarMessage( i18n("Making backup...") );
00376                 KURL backup;
00377                 if ( d->m_backupPath.isEmpty())
00378                     backup = url();
00379                 else
00380                     backup = d->m_backupPath +"/"+url().fileName();
00381                 backup.setPath( backup.path() + QString::fromLatin1("~") );
00382                 KFileItem item( entry, url() );
00383                 Q_ASSERT( item.name() == url().fileName() );
00384                 KIO::NetAccess::file_copy( url(), backup, item.permissions(), true /*overwrite*/, false /*resume*/, shells().current() );
00385             }
00386         }
00387     }
00388 
00389     emit sigStatusBarMessage( i18n("Saving...") );
00390     bool ret = false;
00391     bool suppressErrorDialog = false;
00392     if ( !isNativeFormat( outputMimeType ) ) {
00393         kdDebug(30003) << "Saving to format " << outputMimeType << " in " << m_file << endl;
00394         // Not native format : save using export filter
00395         if ( !d->filterManager )
00396             d->filterManager = new KoFilterManager( this );
00397 
00398         KoFilter::ConversionStatus status = d->filterManager->exp0rt( m_file, outputMimeType );
00399         ret = status == KoFilter::OK;
00400         suppressErrorDialog = (status == KoFilter::UserCancelled || status == KoFilter::BadConversionGraph );
00401     } else {
00402         // Native format => normal save
00403         Q_ASSERT( !m_file.isEmpty() );
00404         ret = saveNativeFormat( m_file );
00405     }
00406 
00407     if ( ret ) {
00408         removeAutoSaveFiles();
00409         // Restart the autosave timer
00410         // (we don't want to autosave again 2 seconds after a real save)
00411         setAutoSave( d->m_autoSaveDelay );
00412     }
00413 
00414     QApplication::restoreOverrideCursor();
00415     if ( !ret )
00416     {
00417         if ( !suppressErrorDialog )
00418         {
00419             showSavingErrorDialog();
00420         }
00421 
00422         // couldn't save file so this new URL is invalid
00423         // FIXME: we should restore the current document's true URL instead of
00424         // setting it to nothing otherwise anything that depends on the URL
00425         // being correct will not work (i.e. the document will be called
00426         // "Untitled" which may not be true)
00427         //
00428         // Update: now the URL is restored in KoMainWindow but really, this
00429         // should still be fixed in KoDocument/KParts (ditto for m_file).
00430         // We still resetURL() here since we may or may not have been called
00431         // by KoMainWindow - Clarence
00432         resetURL();
00433 
00434     // As we did not save, restore the "was modified" status
00435         setModified( wasModified );
00436     }
00437 
00438     if ( ret )
00439     {
00440         d->mimeType = outputMimeType;
00441         setConfirmNonNativeSave ( isExporting (), false );
00442     }
00443     emit sigClearStatusBarMessage();
00444 
00445     return ret;
00446 }
00447 
00448 QCString KoDocument::mimeType() const
00449 {
00450     return d->mimeType;
00451 }
00452 
00453 void KoDocument::setMimeType( const QCString & mimeType )
00454 {
00455     d->mimeType = mimeType;
00456 }
00457 
00458 void KoDocument::setOutputMimeType( const QCString & mimeType, int specialOutputFlag )
00459 {
00460     d->outputMimeType = mimeType;
00461     d->m_specialOutputFlag = specialOutputFlag;
00462 }
00463 
00464 QCString KoDocument::outputMimeType() const
00465 {
00466     return d->outputMimeType;
00467 }
00468 
00469 int KoDocument::specialOutputFlag() const
00470 {
00471     return d->m_specialOutputFlag;
00472 }
00473 
00474 bool KoDocument::confirmNonNativeSave( const bool exporting ) const
00475 {
00476     // "exporting ? 1 : 0" is different from "exporting" because a bool is
00477     // usually implemented like an "int", not "unsigned : 1"
00478     return d->m_confirmNonNativeSave [ exporting ? 1 : 0 ];
00479 }
00480 
00481 void KoDocument::setConfirmNonNativeSave( const bool exporting, const bool on )
00482 {
00483     d->m_confirmNonNativeSave [ exporting ? 1 : 0] = on;
00484 }
00485 
00486 bool KoDocument::isImporting() const
00487 {
00488     return d->m_isImporting;
00489 }
00490 
00491 bool KoDocument::isExporting() const
00492 {
00493     return d->m_isExporting;
00494 }
00495 
00496 void KoDocument::setCheckAutoSaveFile( bool b )
00497 {
00498     d->m_shouldCheckAutoSaveFile = b;
00499 }
00500 
00501 void KoDocument::setAutoErrorHandlingEnabled( bool b )
00502 {
00503     d->m_autoErrorHandlingEnabled = b;
00504 }
00505 
00506 bool KoDocument::isAutoErrorHandlingEnabled() const
00507 {
00508     return d->m_autoErrorHandlingEnabled;
00509 }
00510 
00511 void KoDocument::slotAutoSave()
00512 {
00513     if ( isModified() && d->modifiedAfterAutosave )
00514     {
00515         connect( this, SIGNAL( sigProgress( int ) ), shells().current(), SLOT( slotProgress( int ) ) );
00516         emit sigStatusBarMessage( i18n("Autosaving...") );
00517         d->m_autosaving = true;
00518         bool ret = saveNativeFormat( autoSaveFile( m_file ) );
00519         setModified( true );
00520         if ( ret ) {
00521             d->modifiedAfterAutosave = false;
00522             d->m_autoSaveTimer.stop(); // until the next change
00523         }
00524         d->m_autosaving = false;
00525         emit sigClearStatusBarMessage();
00526         disconnect( this, SIGNAL( sigProgress( int ) ), shells().current(), SLOT( slotProgress( int ) ) );
00527         if ( !ret )
00528             emit sigStatusBarMessage( i18n("Error during autosave! Partition full?") );
00529     }
00530 }
00531 
00532 KAction *KoDocument::action( const QDomElement &element ) const
00533 {
00534     // First look in the document itself
00535     KAction* act = KParts::ReadWritePart::action( element );
00536     if ( act )
00537         return act;
00538 
00539     Q_ASSERT( d->m_bSingleViewMode );
00540     // Then look in the first view (this is for the single view mode)
00541     if ( !d->m_views.isEmpty() )
00542         return d->m_views.getFirst()->action( element );
00543     else
00544         return 0L;
00545 }
00546 
00547 QDomDocument KoDocument::domDocument() const
00548 {
00549     // When embedded into e.g. konqueror, we want the view's GUI (hopefully a reduced one)
00550     // to be used.
00551     Q_ASSERT( d->m_bSingleViewMode );
00552     if ( d->m_views.isEmpty() )
00553         return QDomDocument();
00554     else
00555         return d->m_views.getFirst()->domDocument();
00556 }
00557 
00558 void KoDocument::setManager( KParts::PartManager *manager )
00559 {
00560     KParts::ReadWritePart::setManager( manager );
00561     if ( d->m_bSingleViewMode && d->m_views.count() == 1 )
00562         d->m_views.getFirst()->setPartManager( manager );
00563 
00564     if ( manager )
00565     {
00566         QPtrListIterator<KoDocumentChild> it( d->m_children );
00567         for (; it.current(); ++it )
00568             if ( it.current()->document() )
00569                 manager->addPart( it.current()->document(), false );
00570     }
00571 }
00572 
00573 void KoDocument::setReadWrite( bool readwrite )
00574 {
00575     KParts::ReadWritePart::setReadWrite( readwrite );
00576 
00577     QPtrListIterator<KoView> vIt( d->m_views );
00578     for (; vIt.current(); ++vIt )
00579         vIt.current()->updateReadWrite( readwrite );
00580 
00581     QPtrListIterator<KoDocumentChild> dIt( d->m_children );
00582     for (; dIt.current(); ++dIt )
00583         if ( dIt.current()->document() )
00584             dIt.current()->document()->setReadWrite( readwrite );
00585 
00586     setAutoSave( d->m_autoSaveDelay );
00587 }
00588 
00589 void KoDocument::setAutoSave( int delay )
00590 {
00591     d->m_autoSaveDelay = delay;
00592     if ( isReadWrite() && !isEmbedded() && d->m_autoSaveDelay > 0 )
00593         d->m_autoSaveTimer.start( d->m_autoSaveDelay * 1000 );
00594     else
00595         d->m_autoSaveTimer.stop();
00596 }
00597 
00598 void KoDocument::addView( KoView *view )
00599 {
00600     if ( !view )
00601         return;
00602 
00603     d->m_views.append( view );
00604     view->updateReadWrite( isReadWrite() );
00605 }
00606 
00607 void KoDocument::removeView( KoView *view )
00608 {
00609     d->m_views.removeRef( view );
00610 }
00611 
00612 const QPtrList<KoView>& KoDocument::views() const
00613 {
00614     return d->m_views;
00615 }
00616 
00617 int KoDocument::viewCount() const
00618 {
00619     return d->m_views.count();
00620 }
00621 
00622 void KoDocument::insertChild( KoDocumentChild *child )
00623 {
00624     setModified( true );
00625 
00626     d->m_children.append( child );
00627 
00628     connect( child, SIGNAL( changed( KoChild * ) ),
00629              this, SLOT( slotChildChanged( KoChild * ) ) );
00630     connect( child, SIGNAL( destroyed() ),
00631              this, SLOT( slotChildDestroyed() ) );
00632 
00633     // It may be that insertChild is called without the KoDocumentChild
00634     // having a KoDocument attached, yet. This happens for example
00635     // when KPresenter loads a document with embedded objects. For those
00636     // KPresenterChild objects are allocated and insertChild is called.
00637     // Later in loadChildren() KPresenter iterates over the child list
00638     // and calls loadDocument for each child. That's exactly where we
00639     // will try to do what we cannot do now: Register the child document
00640     // at the partmanager (Simon)
00641     if ( manager() && !isSingleViewMode() && child->document() )
00642         manager()->addPart( child->document(), false );
00643 }
00644 
00645 void KoDocument::slotChildChanged( KoChild *c )
00646 {
00647     assert( c->inherits( "KoDocumentChild" ) );
00648     emit childChanged( static_cast<KoDocumentChild *>( c ) );
00649 }
00650 
00651 void KoDocument::slotChildDestroyed()
00652 {
00653     setModified( true );
00654 
00655     const KoDocumentChild *child = static_cast<const KoDocumentChild *>( sender() );
00656     d->m_children.removeRef( child );
00657 }
00658 
00659 const QPtrList<KoDocumentChild>& KoDocument::children() const
00660 {
00661     return d->m_children;
00662 }
00663 
00664 KParts::Part *KoDocument::hitTest( QWidget *widget, const QPoint &globalPos )
00665 {
00666     QPtrListIterator<KoView> it( d->m_views );
00667     for (; it.current(); ++it )
00668         if ( (QWidget *)it.current() == widget )
00669         {
00670             QPoint canvasPos( it.current()->canvas()->mapFromGlobal( globalPos ) );
00671             canvasPos.rx() += it.current()->canvasXOffset();
00672             canvasPos.ry() += it.current()->canvasYOffset();
00673 
00674             KParts::Part *part = it.current()->hitTest( canvasPos );
00675             if ( part )
00676                 return part;
00677         }
00678 
00679     return 0L;
00680 }
00681 
00682 KoDocument *KoDocument::hitTest( const QPoint &pos, const QWMatrix &matrix )
00683 {
00684     QPtrListIterator<KoDocumentChild> it( d->m_children );
00685     for (; it.current(); ++it )
00686     {
00687         KoDocument *doc = it.current()->hitTest( pos, matrix );
00688         if ( doc )
00689             return doc;
00690     }
00691 
00692     return this;
00693 }
00694 
00695 KoDocumentChild *KoDocument::child( KoDocument *doc )
00696 {
00697     QPtrListIterator<KoDocumentChild> it( d->m_children );
00698     for (; it.current(); ++it )
00699         if ( it.current()->document() == doc )
00700             return it.current();
00701 
00702     return 0L;
00703 }
00704 
00705 KoDocumentInfo *KoDocument::documentInfo() const
00706 {
00707     return d->m_docInfo;
00708 }
00709 
00710 void KoDocument::setViewBuildDocument( KoView *view, const QDomDocument &doc )
00711 {
00712     if ( d->m_views.find( view ) == -1 )
00713         return;
00714 
00715     uint viewIdx = d->m_views.at();
00716 
00717     if ( d->m_viewBuildDocuments.count() == viewIdx )
00718         d->m_viewBuildDocuments.append( doc );
00719     else if ( d->m_viewBuildDocuments.count() > viewIdx )
00720         d->m_viewBuildDocuments[ viewIdx ] = doc;
00721 }
00722 
00723 QDomDocument KoDocument::viewBuildDocument( KoView *view )
00724 {
00725     QDomDocument res;
00726 
00727     if ( d->m_views.find( view ) == -1 )
00728         return res;
00729 
00730     uint viewIdx = d->m_views.at();
00731 
00732     if ( viewIdx >= d->m_viewBuildDocuments.count() )
00733         return res;
00734 
00735     res = d->m_viewBuildDocuments[ viewIdx ];
00736 
00737     // make this entry empty. otherwise we get a segfault in QMap ;-(
00738     d->m_viewBuildDocuments[ viewIdx ] = QDomDocument();
00739 
00740     return res;
00741 }
00742 
00743 void KoDocument::paintEverything( QPainter &painter, const QRect &rect, bool transparent, KoView *view, double zoomX, double zoomY )
00744 {
00745     paintContent( painter, rect, transparent, zoomX, zoomY );
00746     paintChildren( painter, rect, view, zoomX, zoomY );
00747 }
00748 
00749 void KoDocument::paintChildren( QPainter &painter, const QRect &/*rect*/, KoView *view, double zoomX, double zoomY )
00750 {
00751     QPtrListIterator<KoDocumentChild> it( d->m_children );
00752     for (; it.current(); ++it )
00753     {
00754         // #### todo: paint only if child is visible inside rect
00755         painter.save();
00756         paintChild( it.current(), painter, view, zoomX, zoomY );
00757         painter.restore();
00758     }
00759 }
00760 
00761 void KoDocument::paintChild( KoDocumentChild *child, QPainter &painter, KoView *view, double zoomX, double zoomY )
00762 {
00763     if ( child->isDeleted() )
00764         return;
00765 
00766     // QRegion rgn = painter.clipRegion();
00767 
00768     child->transform( painter );
00769     child->document()->paintEverything( painter, child->contentRect(), child->isTransparent(), view, zoomX, zoomY );
00770 
00771     if ( view && view->partManager() )
00772     {
00773         // ### do we need to apply zoomX and zoomY here ?
00774         KParts::PartManager *manager = view->partManager();
00775 
00776         painter.scale( 1.0 / child->xScaling(), 1.0 / child->yScaling() );
00777 
00778         int w = int( (double)child->contentRect().width() * child->xScaling() );
00779         int h = int( (double)child->contentRect().height() * child->yScaling() );
00780         if ( ( manager->selectedPart() == (KParts::Part *)child->document() &&
00781                manager->selectedWidget() == (QWidget *)view ) ||
00782              ( manager->activePart() == (KParts::Part *)child->document() &&
00783                manager->activeWidget() == (QWidget *)view ) )
00784         {
00785             // painter.setClipRegion( rgn );
00786             painter.setClipping( FALSE );
00787 
00788             painter.setPen( black );
00789             painter.fillRect( -5, -5, w + 10, 5, white );
00790             painter.fillRect( -5, h, w + 10, 5, white );
00791             painter.fillRect( -5, -5, 5, h + 10, white );
00792             painter.fillRect( w, -5, 5, h + 10, white );
00793             painter.fillRect( -5, -5, w + 10, 5, BDiagPattern );
00794             painter.fillRect( -5, h, w + 10, 5, BDiagPattern );
00795             painter.fillRect( -5, -5, 5, h + 10, BDiagPattern );
00796             painter.fillRect( w, -5, 5, h + 10, BDiagPattern );
00797 
00798             if ( manager->selectedPart() == (KParts::Part *)child->document() &&
00799                  manager->selectedWidget() == (QWidget *)view )
00800             {
00801                 QColor color;
00802                 if ( view->koDocument() == this )
00803                     color = black;
00804                 else
00805                     color = gray;
00806                 painter.fillRect( -5, -5, 5, 5, color );
00807                 painter.fillRect( -5, h, 5, 5, color );
00808                 painter.fillRect( w, h, 5, 5, color );
00809                 painter.fillRect( w, -5, 5, 5, color );
00810                 painter.fillRect( w / 2 - 3, -5, 5, 5, color );
00811                 painter.fillRect( w / 2 - 3, h, 5, 5, color );
00812                 painter.fillRect( -5, h / 2 - 3, 5, 5, color );
00813                 painter.fillRect( w, h / 2 - 3, 5, 5, color );
00814             }
00815 
00816             painter.setClipping( TRUE );
00817         }
00818     }
00819 }
00820 
00821 bool KoDocument::isModified() const
00822 {
00823     if ( KParts::ReadWritePart::isModified() )
00824     {
00825         //kdDebug(30003)<<k_funcinfo<<" Modified doc='"<<url().url()<<"' extern="<<isStoredExtern()<<endl;
00826         return true;
00827     }
00828     // Then go through internally stored children (considered to be part of this doc)
00829     QPtrListIterator<KoDocumentChild> it = children();
00830     for (; it.current(); ++it )
00831     {
00832         KoDocument *doc = it.current()->document();
00833         if ( doc && !it.current()->isStoredExtern() && !it.current()->isDeleted() && doc->isModified() )
00834             return true;
00835     }
00836     return false;
00837 }
00838 
00839 bool KoDocument::saveChildren( KoStore* _store )
00840 {
00841     //kdDebug(30003)<<k_funcinfo<<" checking children of doc='"<<url().url()<<"'"<<endl;
00842     int i = 0;
00843     QPtrListIterator<KoDocumentChild> it( children() );
00844     for( ; it.current(); ++it ) {
00845         KoDocument* childDoc = it.current()->document();
00846         if (childDoc && !it.current()->isDeleted())
00847         {
00848             if ( !childDoc->isStoredExtern() )
00849             {
00850                 //kdDebug(30003) << "KoDocument::saveChildren internal url: /" << i << endl;
00851                 if ( !childDoc->saveToStore( _store, QString::number( i++ ) ) )
00852                     return FALSE;
00853 
00854                 if (!isExporting ())
00855                     childDoc->setModified( false );
00856             }
00857             //else kdDebug(30003)<<k_funcinfo<<" external (don't save) url:" << childDoc->url().url()<<endl;
00858         }
00859     }
00860     return true;
00861 }
00862 
00863 // TODO delegate most of the work to a method which takes a list of children to save.
00864 // Useful for e.g. KWDocument::saveSelectedFrames
00865 bool KoDocument::saveChildrenOasis( KoStore* store, KoXmlWriter* manifestWriter )
00866 {
00867     //kdDebug(30003)<<k_funcinfo<<" checking children of doc='"<<url().url()<<"'"<<endl;
00868     QPtrListIterator<KoDocumentChild> it( children() );
00869     for( ; it.current(); ++it ) {
00870         KoDocument* childDoc = it.current()->document();
00871         if (childDoc && !it.current()->isDeleted())
00872         {
00873             QString path;
00874             if ( !childDoc->isStoredExtern() )
00875             {
00876                 if ( !it.current()->saveOasisToStore( store, manifestWriter ) )
00877                     return false;
00878                 if (!isExporting ())
00879                     childDoc->setModified( false );
00880 
00881                 // see KoDocumentChild
00882                 assert( childDoc->url().protocol() == INTERNAL_PROTOCOL );
00883                 path = store->currentDirectory();
00884                 if ( !path.isEmpty() )
00885                     path += '/';
00886                 path += childDoc->url().path();
00887                 if ( path.startsWith( "/" ) )
00888                     path = path.mid( 1 ); // remove leading '/', no wanted in manifest
00889             }
00890             else
00891             {
00892                 kdDebug(30003)<<k_funcinfo<<" external (don't save) url:" << childDoc->url().url()<<endl;
00893                 path = childDoc->url().url();
00894             }
00895             // OOo uses a trailing slash for the path to embedded objects (== directories)
00896             if ( !path.endsWith( "/" ) )
00897                 path += '/';
00898             QCString mimetype = childDoc->nativeOasisMimeType();
00899             if ( mimetype.isEmpty() )
00900                 mimetype = childDoc->nativeFormatMimeType();
00901             manifestWriter->addManifestEntry( path, mimetype );
00902         }
00903     }
00904     return true;
00905 }
00906 
00907 bool KoDocument::saveExternalChildren()
00908 {
00909     if ( d->m_doNotSaveExtDoc )
00910     {
00911         //kdDebug(30003)<<k_funcinfo<<" Don't save external docs in doc='"<<url().url()<<"'"<<endl;
00912         d->m_doNotSaveExtDoc = false;
00913         return true;
00914     }
00915 
00916     //kdDebug(30003)<<k_funcinfo<<" checking children of doc='"<<url().url()<<"'"<<endl;
00917     KoDocument *doc;
00918     KoDocumentChild *ch;
00919     QPtrListIterator<KoDocumentChild> it = children();
00920     for (; (ch = it.current()); ++it )
00921     {
00922         if ( !ch->isDeleted() )
00923         {
00924             doc = ch->document();
00925             if ( doc && doc->isStoredExtern() && doc->isModified() )
00926             {
00927                 kdDebug(30003)<<" save external doc='"<<url().url()<<"'"<<endl;
00928                 doc->setDoNotSaveExtDoc(); // Only save doc + it's internal children
00929                 if ( !doc->save() )
00930                     return false; // error
00931             }
00932             //kdDebug(30003)<<k_funcinfo<<" not modified doc='"<<url().url()<<"'"<<endl;
00933             // save possible external docs inside doc
00934             if ( !doc->saveExternalChildren() )
00935                 return false;
00936         }
00937     }
00938     return true;
00939 }
00940 
00941 bool KoDocument::saveNativeFormat( const QString & file )
00942 {
00943     d->lastErrorMessage = QString::null;
00944     //kdDebug(30003) << "Saving to store" << endl;
00945 
00946     KoStore::Backend backend = KoStore::Auto;
00947     if ( d->m_specialOutputFlag == SaveAsKOffice1dot1 )
00948     {
00949         kdDebug(30003) << "Saving as KOffice-1.1 format, using a tar.gz" << endl;
00950         backend = KoStore::Tar; // KOffice-1.0/1.1 used tar.gz for the native mimetype
00952     }
00953     else if ( d->m_specialOutputFlag == SaveAsDirectoryStore )
00954     {
00955         backend = KoStore::Directory;
00956         kdDebug(30003) << "Saving as uncompressed XML, using directory store." << endl;
00957     }
00958 
00959     kdDebug(30003) << "KoDocument::saveNativeFormat nativeFormatMimeType=" << nativeFormatMimeType() << endl;
00960     // OLD: bool oasis = d->m_specialOutputFlag == SaveAsOASIS;
00961     // OLD: QCString mimeType = oasis ? nativeOasisMimeType() : nativeFormatMimeType();
00962     QCString mimeType = d->outputMimeType;
00963     QCString nativeOasisMime = nativeOasisMimeType();
00964     bool oasis = !mimeType.isEmpty() && ( mimeType == nativeOasisMime || mimeType == nativeOasisMime + "-template" );
00965     // TODO: use std::auto_ptr or create store on stack [needs API fixing],
00966     // to remove all the 'delete store' in all the branches
00967     KoStore* store = KoStore::createStore( file, KoStore::Write, mimeType, backend );
00968     if ( store->bad() )
00969     {
00970         d->lastErrorMessage = i18n( "Could not create the file for saving" ); // more details needed?
00971         delete store;
00972         return false;
00973     }
00974 
00975     if ( oasis )
00976     {
00977         kdDebug(30003) << "Saving to OASIS format" << endl;
00978         // Tell KoStore not to touch the file names
00979         store->disallowNameExpansion();
00980         KoOasisStore oasisStore( store );
00981         KoXmlWriter* manifestWriter = oasisStore.manifestWriter( mimeType );
00982 
00983         if ( !saveOasis( store, manifestWriter ) )
00984         {
00985             kdDebug(30003) << "saveOasis failed" << endl;
00986             delete store;
00987             return false;
00988         }
00989 
00990         // Save embedded objects
00991         if ( !saveChildrenOasis( store, manifestWriter ) )
00992         {
00993             kdDebug(30003) << "saveChildrenOasis failed" << endl;
00994             delete store;
00995             return false;
00996         }
00997 
00998         if ( store->open( "meta.xml" ) )
00999         {
01000             if ( !d->m_docInfo->saveOasis( store ) || !store->close() ) {
01001                 delete store;
01002                 return false;
01003             }
01004             manifestWriter->addManifestEntry( "meta.xml", "text/xml" );
01005         }
01006         else
01007         {
01008             d->lastErrorMessage = i18n( "Not able to write '%1'. Partition full?" ).arg( "meta.xml" );
01009             delete store;
01010             return false;
01011         }
01012 
01013         if ( store->open( "Thumbnails/thumbnail.png" ) )
01014         {
01015             if ( !saveOasisPreview( store, manifestWriter ) || !store->close() ) {
01016                 d->lastErrorMessage = i18n( "Error while trying to write '%1'. Partition full?" ).arg( "Thumbnails/thumbnail.png" );
01017                 delete store;
01018                 return false;
01019             }
01020             // No manifest entry!
01021         }
01022         else
01023         {
01024             d->lastErrorMessage = i18n( "Not able to write '%1'. Partition full?" ).arg( "Thumbnails/thumbnail.png" );
01025             delete store;
01026             return false;
01027         }
01028 
01029         // Write out manifest file
01030         if ( !oasisStore.closeManifestWriter() )
01031         {
01032             d->lastErrorMessage = i18n( "Error while trying to write '%1'. Partition full?" ).arg( "META-INF/manifest.xml" );
01033             delete store;
01034             return false;
01035         }
01036 
01037         delete store;
01038     }
01039     else
01040     {
01041         // Save internal children first since they might get a new url
01042         if ( !saveChildren( store ) && !oasis )
01043         {
01044             if ( d->lastErrorMessage.isEmpty() )
01045                 d->lastErrorMessage = i18n( "Error while saving embedded documents" ); // more details needed
01046             delete store;
01047             return false;
01048         }
01049 
01050         kdDebug(30003) << "Saving root" << endl;
01051         if ( store->open( "root" ) )
01052         {
01053             KoStoreDevice dev( store );
01054             if ( !saveToStream( &dev ) || !store->close() )
01055             {
01056                 kdDebug(30003) << "saveToStream failed" << endl;
01057                 delete store;
01058                 return false;
01059             }
01060         }
01061         else
01062         {
01063             d->lastErrorMessage = i18n( "Not able to write '%1'. Partition full?" ).arg( "maindoc.xml" );
01064             delete store;
01065             return false;
01066         }
01067         if ( store->open( "documentinfo.xml" ) )
01068         {
01069             QDomDocument doc = d->m_docInfo->save();
01070             KoStoreDevice dev( store );
01071 
01072             QCString s = doc.toCString(); // this is already Utf8!
01073             (void)dev.writeBlock( s.data(), s.size()-1 );
01074             (void)store->close();
01075         }
01076 
01077         if ( store->open( "preview.png" ) )
01078         {
01079             // ### TODO: missing error checking (The partition could be full!)
01080             savePreview( store );
01081             (void)store->close();
01082         }
01083 
01084         if ( !completeSaving( store ) )
01085         {
01086             delete store;
01087             return false;
01088         }
01089         kdDebug(30003) << "Saving done of url: " << url().url() << endl;
01090         delete store;
01091     }
01092     if ( !saveExternalChildren() )
01093     {
01094         return false;
01095     }
01096     return true;
01097 }
01098 
01099 bool KoDocument::saveToStream( QIODevice * dev )
01100 {
01101     QDomDocument doc = saveXML();
01102     // Save to buffer
01103     QCString s = doc.toCString(); // utf8 already
01104     // We use QCString::size()-1 here, not the official QCString::length
01105     // It works here, as we are not modifying QCString as QByteArray
01106     int nwritten = dev->writeBlock( s.data(), s.size()-1 );
01107     if ( nwritten != (int)s.size()-1 )
01108         kdWarning(30003) << "KoDocument::saveToStream wrote " << nwritten << "   - expected " << s.size()-1 << endl;
01109     return nwritten == (int)s.size()-1;
01110 }
01111 
01112 // Called for embedded documents
01113 bool KoDocument::saveToStore( KoStore* _store, const QString & _path )
01114 {
01115     kdDebug(30003) << "Saving document to store " << _path << endl;
01116 
01117     // Use the path as the internal url
01118     if ( _path.startsWith( STORE_PROTOCOL ) )
01119         m_url = KURL( _path );
01120     else // ugly hack to pass a relative URI
01121         m_url = KURL( INTERNAL_PREFIX +  _path );
01122 
01123     // To make the children happy cd to the correct directory
01124     _store->pushDirectory();
01125     _store->enterDirectory( _path );
01126 
01127     // Save childen first since they might get a new url
01128     if ( !saveChildren( _store ) )
01129         return false;
01130 
01131     // In the current directory we're the king :-)
01132     if ( _store->open( "root" ) )
01133     {
01134         KoStoreDevice dev( _store );
01135         if ( !saveToStream( &dev ) )
01136         {
01137             _store->close();
01138             return false;
01139         }
01140         if ( !_store->close() )
01141             return false;
01142     }
01143 
01144     if ( !completeSaving( _store ) )
01145         return false;
01146 
01147     // Now that we're done leave the directory again
01148     _store->popDirectory();
01149 
01150     kdDebug(30003) << "Saved document to store" << endl;
01151 
01152     return true;
01153 }
01154 
01155 bool KoDocument::saveOasisPreview( KoStore* store, KoXmlWriter* manifestWriter )
01156 {
01157     const QPixmap pix = generatePreview( QSize( 128, 128 ) );
01158     QImage preview ( pix.convertToImage().convertDepth( 32, Qt::ColorOnly ) );
01159     if ( !preview.hasAlphaBuffer() )
01160     {
01161         preview.setAlphaBuffer( true );
01162     }
01163     // ### TODO: freedesktop.org Thumbnail specification (date...)
01164     KoStoreDevice io ( store );
01165     if ( !io.open( IO_WriteOnly ) )
01166         return false;
01167     if ( ! preview.save( &io, "PNG", 0 ) )
01168         return false;
01169     io.close();
01170     manifestWriter->addManifestEntry( "Thumbnails/", "" );
01171     manifestWriter->addManifestEntry( "Thumbnails/thumbnail.png", "" );
01172     return true;
01173 }
01174 
01175 bool KoDocument::savePreview( KoStore* store )
01176 {
01177     QPixmap pix = generatePreview(QSize(256, 256));
01178     // Reducing to 8bpp reduces file sizes quite a lot.
01179     const QImage preview ( pix.convertToImage().convertDepth( 8, Qt::AvoidDither | Qt::DiffuseDither) );
01180     KoStoreDevice io ( store );
01181     if ( !io.open( IO_WriteOnly ) )
01182         return false;
01183     if ( ! preview.save( &io, "PNG" ) ) // ### TODO What is -9 in quality terms?
01184         return false;
01185     io.close();
01186     return true;
01187 }
01188 
01189 QPixmap KoDocument::generatePreview( const QSize& size )
01190 {
01191     double docWidth, docHeight;
01192     int pixmapSize = QMAX(size.width(), size.height());
01193 
01194     if (m_pageLayout.ptWidth > 1.0) {
01195         docWidth = m_pageLayout.ptWidth / 72 * KoGlobal::dpiX();
01196         docHeight = m_pageLayout.ptHeight / 72 * KoGlobal::dpiY();
01197 
01198     } else {
01199         // If we don't have a page layout, just draw the top left hand corner
01200         docWidth = 500.0;
01201         docHeight = 500.0;
01202     }
01203 
01204     double ratio = docWidth / docHeight;
01205 
01206     QPixmap pix;
01207     int previewWidth, previewHeight;
01208     if (ratio > 1.0)
01209     {
01210         previewWidth = (int) pixmapSize;
01211         previewHeight = (int) (pixmapSize / ratio);
01212     }
01213     else
01214     {
01215         previewWidth = (int) (pixmapSize * ratio);
01216         previewHeight = (int) pixmapSize;
01217     }
01218 
01219     pix.resize((int)docWidth, (int)docHeight);
01220 
01221     pix.fill( QColor( 245, 245, 245 ) );
01222 
01223     QRect rc(0, 0, pix.width(), pix.height());
01224 
01225     QPainter p;
01226     p.begin(&pix);
01227     paintEverything(p, rc, false);
01228     p.end();
01229 
01230     // ### TODO: why re-convert it to a QPixmap, when mostly it will be re-converted back to a QImage in the calling function
01231     pix.convertFromImage(pix.convertToImage().smoothScale(previewWidth, previewHeight));
01232 
01233     return pix;
01234 }
01235 
01236 QString KoDocument::autoSaveFile( const QString & path ) const
01237 {
01238     // Using the extension allows to avoid relying on the mime magic when opening
01239     KMimeType::Ptr mime = KMimeType::mimeType( nativeFormatMimeType() );
01240     QString extension = mime->property( "X-KDE-NativeExtension" ).toString();
01241     if ( path.isEmpty() )
01242     {
01243         // Never saved? Use a temp file in $HOME then
01244         // Yes, two open unnamed docs will overwrite each other's autosave file,
01245         // but hmm, we can only do something if that's in the same process anyway...
01246         QString ret = QDir::homeDirPath() + "/." + QString::fromLatin1(instance()->instanceName()) + ".autosave" + extension;
01247         return ret;
01248     }
01249     else
01250     {
01251         KURL url( path );
01252         Q_ASSERT( url.isLocalFile() );
01253         QString dir = url.directory(false);
01254         QString filename = url.fileName();
01255         return dir + "." + filename + ".autosave" + extension;
01256     }
01257 }
01258 
01259 bool KoDocument::checkAutoSaveFile()
01260 {
01261     QString asf = autoSaveFile( QString::null ); // the one in $HOME
01262     //kdDebug(30003) << "asf=" << asf << endl;
01263     if ( QFile::exists( asf ) )
01264     {
01265         QDateTime date = QFileInfo(asf).lastModified();
01266         QString dateStr = date.toString(Qt::LocalDate);
01267         int res = KMessageBox::warningYesNoCancel(
01268             0, i18n( "An autosaved file for an unnamed document exists in %1.\nThis file is dated %2\nDo you want to open it?" )
01269             .arg(asf, dateStr) );
01270         switch(res) {
01271         case KMessageBox::Yes : {
01272             KURL url;
01273             url.setPath( asf );
01274             bool ret = openURL( url );
01275             if ( ret )
01276                 resetURL();
01277             return ret;
01278         }
01279         case KMessageBox::No :
01280             QFile::remove( asf );
01281             return false;
01282         default: // Cancel
01283             return false;
01284         }
01285     }
01286     return false;
01287 }
01288 
01289 bool KoDocument::import( const KURL & _url )
01290 {
01291     bool ret;
01292 
01293     kdDebug (30003) << "KoDocument::import url=" << _url.url() << endl;
01294     d->m_isImporting = true;
01295 
01296     // open...
01297     ret = openURL (_url);
01298 
01299     // reset m_url & m_file (kindly? set by KParts::openURL()) to simulate a
01300     // File --> Import
01301     if (ret)
01302     {
01303         kdDebug (30003) << "KoDocument::import success, resetting url" << endl;
01304         resetURL ();
01305         setTitleModified ();
01306     }
01307 
01308     d->m_isImporting = false;
01309 
01310     return ret;
01311 }
01312 
01313 bool KoDocument::openURL( const KURL & _url )
01314 {
01315     kdDebug(30003) << "KoDocument::openURL url=" << _url.url() << endl;
01316     d->lastErrorMessage = QString::null;
01317 
01318     // Reimplemented, to add a check for autosave files and to improve error reporting
01319     if ( !_url.isValid() )
01320     {
01321         d->lastErrorMessage = i18n( "Malformed URL\n%1" ).arg( _url.url() ); // ## used anywhere ?
01322         return false;
01323     }
01324     if ( !closeURL() )
01325         return false;
01326 
01327     KURL url( _url );
01328     bool autosaveOpened = false;
01329     d->m_bLoading = true;
01330     if ( url.isLocalFile() && d->m_shouldCheckAutoSaveFile )
01331     {
01332         QString file = url.path();
01333         QString asf = autoSaveFile( file );
01334         if ( QFile::exists( asf ) )
01335         {
01336             //kdDebug(30003) << "KoDocument::openURL asf=" << asf << endl;
01337             // ## TODO compare timestamps ?
01338             int res = KMessageBox::warningYesNoCancel( 0,
01339                                                        i18n( "An autosaved file exists for this document.\nDo you want to open it instead?" ));
01340             switch(res) {
01341                 case KMessageBox::Yes :
01342                     url.setPath( asf );
01343                     autosaveOpened = true;
01344                     break;
01345                 case KMessageBox::No :
01346                     QFile::remove( asf );
01347                     break;
01348                 default: // Cancel
01349                     d->m_bLoading = false;
01350                     return false;
01351             }
01352         }
01353     }
01354 
01355     bool ret = KParts::ReadWritePart::openURL( url );
01356 
01357     if ( autosaveOpened )
01358         resetURL(); // Force save to act like 'Save As'
01359     else
01360     {
01361         // We have no koffice shell when we are being embedded as a readonly part.
01362         //if ( d->m_shells.isEmpty() )
01363         //    kdWarning(30003) << "KoDocument::openURL no shell yet !" << endl;
01364         // Add to recent actions list in our shells
01365         QPtrListIterator<KoMainWindow> it( d->m_shells );
01366         for (; it.current(); ++it )
01367             it.current()->addRecentURL( _url );
01368     }
01369     return ret;
01370 }
01371 
01372 bool KoDocument::openFile()
01373 {
01374     //kdDebug(30003) << "KoDocument::openFile for " << m_file << endl;
01375     if ( !QFile::exists(m_file) )
01376     {
01377         QApplication::restoreOverrideCursor();
01378         if ( d->m_autoErrorHandlingEnabled )
01379             // Maybe offer to create a new document with that name ?
01380             KMessageBox::error(0L, i18n("The file %1 does not exist.").arg(m_file) );
01381         d->m_bLoading = false;
01382         return false;
01383     }
01384 
01385     QApplication::setOverrideCursor( waitCursor );
01386 
01387     if ( d->m_bSingleViewMode && !d->m_views.isEmpty() )
01388     {
01389         // We already had a view (this happens when doing reload in konqueror)
01390         KoView* v = d->m_views.first();
01391         removeView( v );
01392         delete v;
01393         Q_ASSERT( d->m_views.isEmpty() );
01394     }
01395 
01396     d->m_specialOutputFlag = 0;
01397     QCString _native_format = nativeFormatMimeType();
01398 
01399     KURL u;
01400     u.setPath( m_file );
01401     QString typeName = KMimeType::findByURL( u, 0, true )->name();
01402 
01403     // Allow to open backup files, don't keep the mimetype application/x-trash.
01404     if ( typeName == "application/x-trash" )
01405     {
01406         QString path = u.path();
01407         QStringList patterns = KMimeType::mimeType( typeName )->patterns();
01408         // Find the extension that makes it a backup file, and remove it
01409         for( QStringList::Iterator it = patterns.begin(); it != patterns.end(); ++it ) {
01410             QString ext = *it;
01411             if ( !ext.isEmpty() && ext[0] == '*' )
01412             {
01413                 ext.remove(0, 1);
01414                 if ( path.endsWith( ext ) ) {
01415                     path.truncate( path.length() - ext.length() );
01416                     break;
01417                 }
01418             }
01419         }
01420         typeName = KMimeType::findByPath( path, 0, true )->name();
01421     }
01422 
01423     // Special case for flat XML files (e.g. using directory store)
01424     if ( u.fileName() == "maindoc.xml" || typeName == "inode/directory" )
01425     {
01426         typeName = _native_format; // Hmm, what if it's from another app? ### Check mimetype
01427         d->m_specialOutputFlag = SaveAsDirectoryStore;
01428         kdDebug(30003) << "KoDocument::openFile loading maindoc.xml, using directory store for " << m_file << endl;
01429     }
01430     kdDebug(30003) << "KoDocument::openFile " << m_file << " type:" << typeName << endl;
01431 
01432     QString importedFile = m_file;
01433 
01434     if ( typeName == KMimeType::defaultMimeType() ) {
01435         kdError(30003) << "No mimetype found for " << m_file << endl;
01436         QApplication::restoreOverrideCursor();
01437         if ( d->m_autoErrorHandlingEnabled )
01438             KMessageBox::error( 0L, i18n( "Could not open\n%1" ).arg( url().prettyURL( 0, KURL::StripFileProtocol ) ) );
01439         d->m_bLoading = false;
01440         return false;
01441     }
01442 
01443     if ( !isNativeFormat( typeName.latin1() ) ) {
01444         if ( !d->filterManager )
01445             d->filterManager = new KoFilterManager( this );
01446         KoFilter::ConversionStatus status;
01447         importedFile = d->filterManager->import( m_file, status );
01448         if ( status != KoFilter::OK )
01449         {
01450             QApplication::restoreOverrideCursor();
01451             if ( status != KoFilter::UserCancelled &&
01452                  status != KoFilter::BadConversionGraph &&
01453                  d->m_autoErrorHandlingEnabled )
01454                 // ### TODO Any way of passing a better error message from the filter?
01455                 KMessageBox::error( 0L, i18n( "Could not open\n%1" ).arg( url().prettyURL( 0, KURL::StripFileProtocol ) ) );
01456 
01457             d->m_bLoading = false;
01458             return false;
01459         }
01460         kdDebug(30003) << "KoDocument::openFile - importedFile '" << importedFile
01461                        << "', status: " << static_cast<int>( status ) << endl;
01462     }
01463 
01464     QApplication::restoreOverrideCursor();
01465 
01466     bool ok = true;
01467 
01468     if (!importedFile.isEmpty()) // Something to load (tmp or native file) ?
01469     {
01470         // The filter, if any, has been applied. It's all native format now.
01471         if ( !loadNativeFormat( importedFile ) )
01472         {
01473             ok = false;
01474             if ( d->m_autoErrorHandlingEnabled )
01475             {
01476                 showLoadingErrorDialog();
01477             }
01478         }
01479     }
01480 
01481     if ( importedFile != m_file )
01482     {
01483         // We opened a temporary file (result of an import filter)
01484         // Set document URL to empty - we don't want to save in /tmp !
01485         // But only if in readwrite mode (no saving problem otherwise)
01486         // --
01487         // But this isn't true at all.  If this is the result of an
01488         // import, then importedFile=temporary_file.kwd and
01489         // m_file/m_url=foreignformat.ext so m_url is correct!
01490         // So don't resetURL() or else the caption won't be set when
01491         // foreign files are opened (an annoying bug).
01492         // - Clarence
01493         //
01494 #if 0
01495         if ( isReadWrite() )
01496             resetURL();
01497 #endif
01498 
01499         // remove temp file - uncomment this to debug import filters
01500         if(!importedFile.isEmpty()) {
01501             QFile::remove( importedFile );
01502     }
01503     }
01504 
01505     if ( ok && d->m_bSingleViewMode )
01506     {
01507         // See addClient below
01508         KXMLGUIFactory* guiFactory = factory();
01509         if( guiFactory ) // 0L when splitting views in konq, for some reason
01510             guiFactory->removeClient( this );
01511 
01512         KoView *view = createView( d->m_wrapperWidget );
01513         d->m_wrapperWidget->setKoView( view );
01514         view->show();
01515 
01516         // Ok, now we have a view, so action() and domDocument() will work as expected
01517         // -> rebuild GUI
01518         if ( guiFactory )
01519             guiFactory->addClient( this );
01520     }
01521 
01522     if ( ok )
01523     {
01524         setMimeTypeAfterLoading( typeName );
01525     }
01526     d->m_bLoading = false;
01527     return ok;
01528 }
01529 
01530 // shared between openFile and koMainWindow's "create new empty document" code
01531 void KoDocument::setMimeTypeAfterLoading( const QString& mimeType )
01532 {
01533     d->mimeType = mimeType.latin1();
01534 
01535     d->outputMimeType = d->mimeType;
01536 
01537     const bool needConfirm = !isNativeFormat( d->mimeType );
01538     setConfirmNonNativeSave( false, needConfirm  );
01539     setConfirmNonNativeSave( true, needConfirm );
01540 }
01541 
01542 // The caller must call store->close() if loadAndParse returns true.
01543 bool KoDocument::oldLoadAndParse(KoStore* store, const QString& filename, QDomDocument& doc)
01544 {
01545     //kdDebug(30003) << "oldLoadAndParse: Trying to open " << filename << endl;
01546 
01547     if (!store->open(filename))
01548     {
01549         kdWarning(30003) << "Entry " << filename << " not found!" << endl;
01550         d->lastErrorMessage = i18n( "Could not find %1" ).arg( filename );
01551         return false;
01552     }
01553     // Error variables for QDomDocument::setContent
01554     QString errorMsg;
01555     int errorLine, errorColumn;
01556     bool ok = doc.setContent( store->device(), &errorMsg, &errorLine, &errorColumn );
01557     if ( !ok )
01558     {
01559         kdError(30003) << "Parsing error in " << filename << "! Aborting!" << endl
01560             << " In line: " << errorLine << ", column: " << errorColumn << endl
01561             << " Error message: " << errorMsg << endl;
01562         d->lastErrorMessage = i18n( "Parsing error in %1 at line %2, column %3\nError message: %4" )
01563                               .arg( filename ).arg( errorLine ).arg( errorColumn )
01564                               .arg( i18n ( "QXml", errorMsg.utf8() ) );
01565         store->close();
01566         return false;
01567     }
01568     kdDebug(30003) << "File " << filename << " loaded and parsed" << endl;
01569     return true;
01570 }
01571 
01572 bool KoDocument::loadNativeFormat( const QString & file )
01573 {
01574     QFileInfo fileInfo( file );
01575     if ( !fileInfo.exists() ) // check duplicated from openURL, but this is useful for templates
01576     {
01577         d->lastErrorMessage = i18n("The file %1 does not exist.").arg(file);
01578         return false;
01579     }
01580     if ( !fileInfo.isFile() )
01581     {
01582         d->lastErrorMessage = i18n( "%1 is not a file." ).arg(file);
01583         return false;
01584     }
01585 
01586     QApplication::setOverrideCursor( waitCursor );
01587 
01588     kdDebug(30003) << "KoDocument::loadNativeFormat( " << file << " )" << endl;
01589 
01590     QFile in;
01591     bool isRawXML = false;
01592     if ( d->m_specialOutputFlag != SaveAsDirectoryStore ) // Don't try to open a directory ;)
01593     {
01594         in.setName(file);
01595         if ( !in.open( IO_ReadOnly ) )
01596         {
01597             QApplication::restoreOverrideCursor();
01598             d->lastErrorMessage = i18n( "Could not open the file for reading (check read permissions)." );
01599             return false;
01600         }
01601 
01602         // Try to find out whether it is a mime multi part file
01603         char buf[5];
01604         if ( in.readBlock( buf, 4 ) < 4 )
01605         {
01606             QApplication::restoreOverrideCursor();
01607             in.close();
01608             d->lastErrorMessage = i18n( "Could not read the beginning of the file." );
01609             return false;
01610         }
01611         // ### TODO: allow UTF-16
01612         isRawXML = (strncasecmp( buf, "<?xm", 4 ) == 0);
01613         //kdDebug(30003) << "PATTERN=" << buf << endl;
01614     }
01615     // Is it plain XML?
01616     if ( isRawXML )
01617     {
01618         in.at(0);
01619         QString errorMsg;
01620         int errorLine;
01621         int errorColumn;
01622         QDomDocument doc;
01623         bool res;
01624         if ( doc.setContent( &in, &errorMsg, &errorLine, &errorColumn ) )
01625         {
01626             res = loadXML( &in, doc );
01627             if ( res )
01628                 res = completeLoading( 0L );
01629         }
01630         else
01631         {
01632             kdError (30003) << "Parsing Error! Aborting! (in KoDocument::loadNativeFormat (QFile))" << endl
01633                             << "  Line: " << errorLine << " Column: " << errorColumn << endl
01634                             << "  Message: " << errorMsg << endl;
01635             d->lastErrorMessage = i18n( "parsing error in the main document at line %1, column %2\nError message: %3" )
01636                                   .arg( errorLine ).arg( errorColumn ).arg( i18n ( errorMsg.utf8() ) );
01637             res=false;
01638         }
01639 
01640         QApplication::restoreOverrideCursor();
01641         in.close();
01642         m_bEmpty = false;
01643         return res;
01644     } else
01645     { // It's a koffice store (tar.gz, zip, directory, etc.)
01646         in.close();
01647 
01648         return loadNativeFormatFromStore( file );
01649     }
01650 }
01651 
01652 bool KoDocument::loadNativeFormatFromStore( const QString& file )
01653 {
01654     KoStore::Backend backend = (d->m_specialOutputFlag == SaveAsDirectoryStore) ? KoStore::Directory : KoStore::Auto;
01655     KoStore * store = KoStore::createStore( file, KoStore::Read, "", backend );
01656 
01657     if ( store->bad() )
01658     {
01659         d->lastErrorMessage = i18n( "Not a valid KOffice file: %1" ).arg( file );
01660         delete store;
01661         QApplication::restoreOverrideCursor();
01662         return false;
01663     }
01664 
01665     bool oasis = true;
01666     // OASIS/OOo file format?
01667     if ( store->hasFile( "content.xml" ) )
01668     {
01669         store->disallowNameExpansion();
01670 
01671         KoOasisStore oasisStore( store );
01672         // We could check the 'mimetype' file, but let's skip that and be tolerant.
01673 
01674         if ( !loadOasisFromStore( store ) ) {
01675             delete store;
01676             QApplication::restoreOverrideCursor();
01677             return false;
01678         }
01679 
01680     } else if ( store->hasFile( "root" ) ) // Fallback to "old" file format (maindoc.xml)
01681     {
01682         oasis = false;
01683 
01684         QDomDocument doc;
01685         bool ok = oldLoadAndParse( store, "root", doc );
01686         if ( ok )
01687             ok = loadXML( store->device(), doc );
01688         if ( !ok )
01689         {
01690             delete store;
01691             QApplication::restoreOverrideCursor();
01692             return false;
01693         }
01694         store->close();
01695 
01696         if ( !loadChildren( store ) )
01697         {
01698             kdError(30003) << "ERROR: Could not load children" << endl;
01699             // Don't abort, proceed nonetheless
01700         }
01701 
01702     } else
01703     {
01704         kdError(30003) << "ERROR: No maindoc.xml" << endl;
01705         d->lastErrorMessage = i18n( "Invalid document: no file 'maindoc.xml'." );
01706         delete store;
01707         QApplication::restoreOverrideCursor();
01708         return false;
01709     }
01710 
01711     if ( oasis && store->hasFile( "meta.xml" ) ) {
01712         QDomDocument metaDoc;
01713         KoOasisStore oasisStore( store );
01714         if ( oasisStore.loadAndParse( "meta.xml", metaDoc, d->lastErrorMessage ) ) {
01715             d->m_docInfo->loadOasis( metaDoc );
01716         }
01717     }
01718     else if ( !oasis && store->hasFile( "documentinfo.xml" ) )
01719     {
01720         QDomDocument doc;
01721         if ( oldLoadAndParse( store, "documentinfo.xml", doc ) ) {
01722             store->close();
01723             d->m_docInfo->load( doc );
01724         }
01725     }
01726     else
01727     {
01728         //kdDebug( 30003 ) << "cannot open document info" << endl;
01729         delete d->m_docInfo;
01730         d->m_docInfo = new KoDocumentInfo( this, "document info" );
01731     }
01732 
01733     bool res = completeLoading( store );
01734     delete store;
01735     QApplication::restoreOverrideCursor();
01736     m_bEmpty = false;
01737     return res;
01738 }
01739 
01740 // For embedded documents
01741 bool KoDocument::loadFromStore( KoStore* _store, const QString& url )
01742 {
01743     if ( _store->open( url ) )
01744     {
01745         QDomDocument doc;
01746         doc.setContent( _store->device() );
01747         if ( !loadXML( _store->device(), doc ) )
01748         {
01749             _store->close();
01750             return false;
01751         }
01752         _store->close();
01753     } else {
01754         kdWarning() << "couldn't open " << url << endl;
01755     }
01756 
01757     _store->pushDirectory();
01758     // Store as document URL
01759     if ( url.startsWith( STORE_PROTOCOL ) ) {
01760         m_url = KURL( url );
01761     } else {
01762         m_url = KURL( INTERNAL_PREFIX + url );
01763         _store->enterDirectory( url );
01764     }
01765 
01766     if ( !loadChildren( _store ) )
01767     {
01768         kdError(30003) << "ERROR: Could not load children" << endl;
01769 #if 0
01770         return false;
01771 #endif
01772     }
01773 
01774     bool result = completeLoading( _store );
01775 
01776     // Restore the "old" path
01777     _store->popDirectory();
01778 
01779     return result;
01780 }
01781 
01782 bool KoDocument::loadOasisFromStore( KoStore* store )
01783 {
01784     KoOasisStyles oasisStyles;
01785     QDomDocument contentDoc;
01786     QDomDocument settingsDoc;
01787     KoOasisStore oasisStore( store );
01788     bool ok = oasisStore.loadAndParse( "content.xml", contentDoc, d->lastErrorMessage );
01789     if ( !ok )
01790         return false;
01791 
01792     QDomDocument stylesDoc;
01793     (void)oasisStore.loadAndParse( "styles.xml", stylesDoc, d->lastErrorMessage );
01794     // Load styles from style.xml
01795     oasisStyles.createStyleMap( stylesDoc );
01796     // Also load styles from content.xml
01797     oasisStyles.createStyleMap( contentDoc );
01798 
01799     // TODO post 1.4, pass manifestDoc to the apps so that they don't have to do it themselves
01800     // (when calling KoDocumentChild::loadOasisDocument)
01801     //QDomDocument manifestDoc;
01802     //KoOasisStore oasisStore( store );
01803     //if ( !oasisStore.loadAndParse( "tar:/META-INF/manifest.xml", manifestDoc, d->lastErrorMessage ) )
01804     //    return false;
01805 
01806     if ( store->hasFile( "settings.xml" ) ) {
01807         (void)oasisStore.loadAndParse( "settings.xml", settingsDoc, d->lastErrorMessage );
01808     }
01809     if ( !loadOasis( contentDoc, oasisStyles, settingsDoc, store ) )
01810         return false;
01811 
01812     return true;
01813 }
01814 
01815 bool KoDocument::isInOperation() const
01816 {
01817     return d->m_numOperations > 0;
01818 }
01819 
01820 void KoDocument::emitBeginOperation()
01821 {
01822 
01823     /* if we're already in an operation, don't send the signal again */
01824     if (!isInOperation())
01825         emit sigBeginOperation();
01826     d->m_numOperations++;
01827 }
01828 
01829 void KoDocument::emitEndOperation()
01830 {
01831     d->m_numOperations--;
01832 
01833     /* don't end the operation till we've cleared all the nested operations */
01834     if (d->m_numOperations == 0)
01835         emit sigEndOperation();
01836     else if (d->m_numOperations < 0)
01837         /* ignore 'end' calls with no matching 'begin' call */
01838         d->m_numOperations = 0;
01839 }
01840 
01841 
01842 bool KoDocument::isStoredExtern() const
01843 {
01844     return !storeInternal() && hasExternURL();
01845 }
01846 
01847 void KoDocument::setModified( bool mod )
01848 {
01849     if ( isAutosaving() ) // ignore setModified calls due to autosaving
01850         return;
01851 
01852     //kdDebug(30003)<<k_funcinfo<<" url:" << m_url.path() << endl;
01853     //kdDebug(30003)<<k_funcinfo<<" mod="<<mod<<" MParts mod="<<KParts::ReadWritePart::isModified()<<" isModified="<<isModified()<<endl;
01854 
01855     if ( mod && !d->modifiedAfterAutosave ) {
01856         // First change since last autosave -> start the autosave timer
01857         setAutoSave( d->m_autoSaveDelay );
01858     }
01859     d->modifiedAfterAutosave = mod;
01860 
01861     if ( mod == isModified() )
01862         return;
01863 
01864     KParts::ReadWritePart::setModified( mod );
01865 
01866     if ( mod ) {
01867         m_bEmpty = FALSE;
01868     } else {
01869         // When saving this document, all non-external child documents get saved too.
01870         QPtrListIterator<KoDocumentChild> it = children();
01871         for (; it.current(); ++it )
01872         {
01873             KoDocument *doc = it.current()->document();
01874             if ( doc && !it.current()->isStoredExtern() && !it.current()->isDeleted() && doc->isModified() )
01875                 doc->setModified( false );
01876         }
01877     }
01878 
01879     // This influences the title
01880     setTitleModified();
01881 }
01882 
01883 void KoDocument::setDoNotSaveExtDoc( bool on )
01884 {
01885     d->m_doNotSaveExtDoc = on;
01886 }
01887 
01888 int KoDocument::queryCloseDia()
01889 {
01890     //kdDebug(30003)<<k_funcinfo<<endl;
01891 
01892     QString name;
01893     if ( documentInfo() )
01894     {
01895         name = documentInfo()->title();
01896     }
01897     if ( name.isEmpty() )
01898         name = url().fileName();
01899 
01900     if ( name.isEmpty() )
01901         name = i18n( "Untitled" );
01902 
01903     int res = KMessageBox::warningYesNoCancel( 0L,
01904                     i18n( "<p>The document <b>'%1'</b> has been modified.</p><p>Do you want to save it?</p>" ).arg(name));
01905 
01906     switch(res)
01907     {
01908         case KMessageBox::Yes :
01909             setDoNotSaveExtDoc(); // Let save() only save myself and my internal docs
01910             save(); // NOTE: External files always in native format. ###TODO: Handle non-native format
01911             setModified( false ); // Now when queryClose() is called by closeEvent it won't do anything.
01912             break;
01913         case KMessageBox::No :
01914             removeAutoSaveFiles();
01915             setModified( false ); // Now when queryClose() is called by closeEvent it won't do anything.
01916             break;
01917         default : // case KMessageBox::Cancel :
01918             return res; // cancels the rest of the files
01919     }
01920     return res;
01921 }
01922 
01923 int KoDocument::queryCloseExternalChildren()
01924 {
01925     //kdDebug(30003)<<k_funcinfo<<" checking for children in: "<<url().url()<<endl;
01926     setDoNotSaveExtDoc(false);
01927     QPtrListIterator<KoDocumentChild> it( children() );
01928     for (; it.current(); ++it )
01929     {
01930         if ( !it.current()->isDeleted() )
01931         {
01932             KoDocument *doc = it.current()->document();
01933             if ( doc )
01934             {
01935         bool foo = doc->isStoredExtern();
01936         kdDebug(36001) << "========== isStoredExtern() returned "
01937                    << foo << " ==========" << endl;
01938 
01939                 if ( foo ) //###TODO: Handle non-native mimetype docs
01940                 {
01941                     {
01942                         kdDebug(30003)<<k_funcinfo<<" found modified child: "<<doc->url().url()<<" extern="<<doc->isStoredExtern()<<endl;
01943                         if ( doc->queryCloseDia() == KMessageBox::Cancel )
01944                             return  KMessageBox::Cancel;
01945                     }
01946                 }
01947                 if ( doc->queryCloseExternalChildren() == KMessageBox::Cancel )
01948                     return KMessageBox::Cancel;
01949             }
01950         }
01951     }
01952     return KMessageBox::Ok;
01953 }
01954 
01955 void KoDocument::setTitleModified( const QString caption, bool mod )
01956 {
01957     //kdDebug(30003)<<k_funcinfo<<" url: "<<url().url()<<" caption: "<<caption<<" mod: "<<mod<<endl;
01958     KoDocument *doc = dynamic_cast<KoDocument *>( parent() );
01959     if ( doc )
01960     {
01961         doc->setTitleModified( caption, mod );
01962         return;
01963     }
01964     // we must be root doc so update caption in all related windows
01965     QPtrListIterator<KoMainWindow> it( d->m_shells );
01966     for (; it.current(); ++it )
01967     {
01968         it.current()->updateCaption();
01969         it.current()->updateReloadFileAction(this);
01970         it.current()->updateVersionsFileAction(this);
01971     }
01972 }
01973 
01974 void KoDocument::setTitleModified()
01975 {
01976     //kdDebug(30003)<<k_funcinfo<<" url: "<<url().url()<<" extern: "<<isStoredExtern()<<" current: "<<d->m_current<<endl;
01977     KoDocument *doc = dynamic_cast<KoDocument *>( parent() );
01978     QString caption;
01979     if ( (url().isEmpty() || isStoredExtern()) && d->m_current )
01980     {
01981         // Get caption from document info (title(), in about page)
01982         if ( documentInfo() )
01983         {
01984             KoDocumentInfoPage * page = documentInfo()->page( QString::fromLatin1("about") );
01985             if (page)
01986                 caption = static_cast<KoDocumentInfoAbout *>(page)->title();
01987         }
01988         if ( caption.isEmpty() )
01989             caption = url().prettyURL( 0, KURL::StripFileProtocol );             // Fall back to document URL
01990 
01991         //kdDebug(30003)<<k_funcinfo<<" url: "<<url().url()<<" caption: "<<caption<<endl;
01992         if ( doc )
01993         {
01994             doc->setTitleModified( caption, isModified() );
01995             return;
01996         }
01997         else
01998         {
01999             // we must be root doc so update caption in all related windows
02000             setTitleModified( caption, isModified() );
02001             return;
02002         }
02003     }
02004     if ( doc )
02005     {
02006         // internal doc or not current doc, so pass on the buck
02007         doc->setTitleModified();
02008     }
02009 }
02010 
02011 bool KoDocument::loadChildren( KoStore* )
02012 {
02013     return true;
02014 }
02015 
02016 bool KoDocument::completeLoading( KoStore* )
02017 {
02018     return true;
02019 }
02020 
02021 bool KoDocument::completeSaving( KoStore* )
02022 {
02023     return true;
02024 }
02025 
02026 QDomDocument KoDocument::createDomDocument( const QString& tagName, const QString& version ) const
02027 {
02028     return createDomDocument( instance()->instanceName(), tagName, version );
02029 }
02030 
02031 //static
02032 QDomDocument KoDocument::createDomDocument( const QString& appName, const QString& tagName, const QString& version )
02033 {
02034     QDomImplementation impl;
02035     QString url = QString("http://www.koffice.org/DTD/%1-%1.dtd").arg(appName).arg(version);
02036     QDomDocumentType dtype = impl.createDocumentType( tagName,
02037                                                       QString("-//KDE//DTD %1 %1//EN").arg(appName).arg(version),
02038                                                       url );
02039     // The namespace URN doesn't need to include the version number.
02040     QString namespaceURN = QString("http://www.koffice.org/DTD/%1").arg(appName);
02041     QDomDocument doc = impl.createDocument( namespaceURN, tagName, dtype );
02042     doc.insertBefore( doc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ), doc.documentElement() );
02043     return doc;
02044 }
02045 
02046 KoXmlWriter* KoDocument::createOasisXmlWriter( QIODevice* dev, const char* rootElementName )
02047 {
02048     KoXmlWriter* writer = new KoXmlWriter( dev );
02049     writer->startDocument( rootElementName );
02050     writer->startElement( rootElementName );
02051     writer->addAttribute( "xmlns:office", KoXmlNS::office );
02052     writer->addAttribute( "xmlns:meta", KoXmlNS::meta );
02053 
02054     if ( qstrcmp( rootElementName, "office:document-meta" ) != 0 ) {
02055         writer->addAttribute( "xmlns:config", KoXmlNS::config );
02056         writer->addAttribute( "xmlns:text", KoXmlNS::text );
02057         writer->addAttribute( "xmlns:table", KoXmlNS::table );
02058         writer->addAttribute( "xmlns:draw", KoXmlNS::draw );
02059         writer->addAttribute( "xmlns:presentation", KoXmlNS::presentation );
02060         writer->addAttribute( "xmlns:dr3d", KoXmlNS::dr3d );
02061         writer->addAttribute( "xmlns:chart", KoXmlNS::chart );
02062         writer->addAttribute( "xmlns:form", KoXmlNS::form );
02063         writer->addAttribute( "xmlns:script", KoXmlNS::script );
02064         writer->addAttribute( "xmlns:style", KoXmlNS::style );
02065         writer->addAttribute( "xmlns:number", KoXmlNS::number );
02066         writer->addAttribute( "xmlns:math", KoXmlNS::math );
02067         writer->addAttribute( "xmlns:svg", KoXmlNS::svg );
02068         writer->addAttribute( "xmlns:fo", KoXmlNS::fo );
02069         writer->addAttribute( "xmlns:koffice", KoXmlNS::koffice );
02070     }
02071     // missing: office:version="1.0"
02072 
02073     writer->addAttribute( "xmlns:dc", KoXmlNS::dc );
02074     writer->addAttribute( "xmlns:xlink", KoXmlNS::xlink );
02075     return writer;
02076 }
02077 
02078 QDomDocument KoDocument::saveXML()
02079 {
02080     kdError(30003) << "KoDocument::saveXML not implemented" << endl;
02081     d->lastErrorMessage = i18n( "Internal error: saveXML not implemented" );
02082     return QDomDocument();
02083 }
02084 
02085 KService::Ptr KoDocument::nativeService()
02086 {
02087     if ( !m_nativeService )
02088         m_nativeService = readNativeService( instance() );
02089 
02090     return m_nativeService;
02091 }
02092 
02093 QCString KoDocument::nativeFormatMimeType() const
02094 {
02095     KService::Ptr service = const_cast<KoDocument *>(this)->nativeService();
02096     if ( !service )
02097         return QCString();
02098     return service->property( "X-KDE-NativeMimeType" ).toString().latin1();
02099 }
02100 
02101 QCString KoDocument::nativeOasisMimeType() const
02102 {
02103     KService::Ptr service = const_cast<KoDocument *>(this)->nativeService();
02104     if ( !service )
02105         return QCString();
02106     return service->property( "X-KDE-NativeOasisMimeType" ).toString().latin1();
02107 }
02108 
02109 
02110 //static
02111 KService::Ptr KoDocument::readNativeService( KInstance *instance )
02112 {
02113     QString instname = instance ? instance->instanceName() : kapp->instanceName();
02114 
02115     // The new way is: we look for a foopart.desktop in the kde_services dir.
02116     QString servicepartname = instname + "part.desktop";
02117     KService::Ptr service = KService::serviceByDesktopPath( servicepartname );
02118     if ( service )
02119         kdDebug(30003) << servicepartname << " found." << endl;
02120     if ( !service )
02121     {
02122         // The old way is kept as fallback for compatibility, but in theory this is really never used anymore.
02123 
02124         // Try by path first, so that we find the global one (which has the native mimetype)
02125         // even if the user created a kword.desktop in ~/.kde/share/applnk or any subdir of it.
02126         // If he created it under ~/.kde/share/applnk/Office/ then no problem anyway.
02127         service = KService::serviceByDesktopPath( QString::fromLatin1("Office/%1.desktop").arg(instname) );
02128     }
02129     if ( !service )
02130         service = KService::serviceByDesktopName( instname );
02131 
02132     return service;
02133 }
02134 
02135 QCString KoDocument::readNativeFormatMimeType( KInstance *instance ) //static
02136 {
02137     KService::Ptr service = readNativeService( instance );
02138     if ( !service )
02139         return QCString();
02140 
02141     if ( service->property( "X-KDE-NativeMimeType" ).toString().isEmpty() )
02142     {
02143         // It may be that the servicetype "KOfficePart" is missing, which leads to this property not being known
02144         if ( KServiceType::serviceType( "KOfficePart" ) == 0L )
02145             kdError(30003) << "The serviceType KOfficePart is missing. Check that you have a kofficepart.desktop file in the share/servicetypes directory." << endl;
02146         else {
02147             QString instname = instance ? instance->instanceName() : kapp->instanceName();
02148             if ( instname != "koshell" ) // hack for koshell
02149                 kdWarning(30003) << service->desktopEntryPath() << ": no X-KDE-NativeMimeType entry!" << endl;
02150         }
02151     }
02152 
02153     return service->property( "X-KDE-NativeMimeType" ).toString().latin1();
02154 }
02155 
02156 QStringList KoDocument::readExtraNativeMimeTypes( KInstance *instance ) //static
02157 {
02158     KService::Ptr service = readNativeService( instance );
02159     if ( !service )
02160         return QStringList();
02161     return service->property( "X-KDE-ExtraNativeMimeTypes" ).toStringList();
02162 }
02163 
02164 void KoDocument::setupXmlReader( QXmlSimpleReader& reader, bool namespaceProcessing )
02165 {
02166     if ( namespaceProcessing )
02167     {
02168         reader.setFeature( "http://xml.org/sax/features/namespaces", TRUE );
02169         reader.setFeature( "http://xml.org/sax/features/namespace-prefixes", FALSE );
02170     }
02171     else
02172     {
02173         reader.setFeature( "http://xml.org/sax/features/namespaces", FALSE );
02174         reader.setFeature( "http://xml.org/sax/features/namespace-prefixes", TRUE );
02175     }
02176     reader.setFeature( "http://trolltech.com/xml/features/report-whitespace-only-CharData", TRUE );
02177 }
02178 
02179 
02180 bool KoDocument::isNativeFormat( const QCString& mimetype ) const
02181 {
02182     if ( mimetype == nativeFormatMimeType() )
02183         return true;
02184     return extraNativeMimeTypes().contains( mimetype );
02185 }
02186 
02187 QStringList KoDocument::extraNativeMimeTypes() const
02188 {
02189     QStringList lst;
02190     // This implementation is temporary while we treat both koffice-1.3 and OASIS formats as native.
02191     // But it's good to have this virtual method, in case some app want to
02192     // support more than one native format.
02193     KService::Ptr service = const_cast<KoDocument *>(this)->nativeService();
02194     if ( !service ) // can't happen
02195         return lst;
02196     return service->property( "X-KDE-ExtraNativeMimeTypes" ).toStringList();
02197 }
02198 
02199 int KoDocument::supportedSpecialFormats() const
02200 {
02201     // Apps which support saving in the 1.1 or in the 1.3 format can add SaveAsKOffice1dot1/3 here.
02202     // SaveAsDirectoryStore is a given since it's implemented by KoDocument itself.
02203     return SaveAsDirectoryStore;
02204 }
02205 
02206 void KoDocument::addShell( KoMainWindow *shell )
02207 {
02208     if ( d->m_shells.findRef( shell ) == -1 )
02209     {
02210         //kdDebug(30003) << "addShell: shell " << (void*)shell << " added to doc " << this << endl;
02211         d->m_shells.append( shell );
02212     }
02213 }
02214 
02215 void KoDocument::removeShell( KoMainWindow *shell )
02216 {
02217     //kdDebug(30003) << "removeShell: shell " << (void*)shell << " removed from doc " << this << endl;
02218     d->m_shells.removeRef( shell );
02219 }
02220 
02221 const QPtrList<KoMainWindow>& KoDocument::shells() const
02222 {
02223     return d->m_shells;
02224 }
02225 
02226 int KoDocument::shellCount() const
02227 {
02228     return d->m_shells.count();
02229 }
02230 
02231 DCOPObject * KoDocument::dcopObject()
02232 {
02233     if ( !d->m_dcopObject )
02234         d->m_dcopObject = new KoDocumentIface( this );
02235     return d->m_dcopObject;
02236 }
02237 
02238 QCString KoDocument::dcopObjectId() const
02239 {
02240     return const_cast<KoDocument *>(this)->dcopObject()->objId();
02241 }
02242 
02243 void KoDocument::setErrorMessage( const QString& errMsg )
02244 {
02245     d->lastErrorMessage = errMsg;
02246 }
02247 
02248 QString KoDocument::errorMessage() const
02249 {
02250     return d->lastErrorMessage;
02251 }
02252 
02253 void KoDocument::showSavingErrorDialog()
02254 {
02255     if ( d->lastErrorMessage.isEmpty() )
02256     {
02257         KMessageBox::error( 0L, i18n( "Could not save\n%1" ).arg( m_file ) );
02258     }
02259     else if ( d->lastErrorMessage != "USER_CANCELED" )
02260     {
02261         KMessageBox::error( 0L, i18n( "Could not save %1\nReason: %2" ).arg( m_file, d->lastErrorMessage ) );
02262     }
02263 }
02264 
02265 void KoDocument::showLoadingErrorDialog()
02266 {
02267     if ( d->lastErrorMessage.isEmpty() )
02268     {
02269         KMessageBox::error( 0L, i18n( "Could not open\n%1" ).arg( url().prettyURL( 0, KURL::StripFileProtocol ) ) );
02270     }
02271     else if ( d->lastErrorMessage != "USER_CANCELED" )
02272     {
02273         KMessageBox::error( 0L, i18n( "Could not open %1\nReason: %2" ).arg( url().prettyURL( 0, KURL::StripFileProtocol ), d->lastErrorMessage ) );
02274     }
02275 }
02276 
02277 bool KoDocument::isAutosaving() const
02278 {
02279     return d->m_autosaving;
02280 }
02281 
02282 bool KoDocument::isLoading() const
02283 {
02284     return d->m_bLoading;
02285 }
02286 
02287 void KoDocument::removeAutoSaveFiles()
02288 {
02289         // Eliminate any auto-save file
02290         QString asf = autoSaveFile( m_file ); // the one in the current dir
02291         if ( QFile::exists( asf ) )
02292             QFile::remove( asf );
02293         asf = autoSaveFile( QString::null ); // and the one in $HOME
02294         if ( QFile::exists( asf ) )
02295             QFile::remove( asf );
02296 }
02297 
02298 void KoDocument::setBackupFile( bool _b )
02299 {
02300     d->m_backupFile = _b;
02301 }
02302 
02303 bool KoDocument::backupFile()const
02304 {
02305     return d->m_backupFile;
02306 }
02307 
02308 
02309 void KoDocument::setBackupPath( const QString & _path)
02310 {
02311     d->m_backupPath = _path;
02312 }
02313 
02314 QString KoDocument::backupPath()const
02315 {
02316     return d->m_backupPath;
02317 }
02318 
02319 void KoDocument::setCurrent( bool on )
02320 {
02321     //kdDebug(30003)<<k_funcinfo<<" url: "<<url().url()<<" set to: "<<on<<endl;
02322     KoDocument *doc = dynamic_cast<KoDocument *>( parent() );
02323     if ( doc )
02324     {
02325         if ( !isStoredExtern() )
02326         {
02327             // internal doc so set next external to current (for safety)
02328             doc->setCurrent( true );
02329             return;
02330         }
02331         // only externally stored docs shall have file name in title
02332         d->m_current = on;
02333         if ( !on )
02334         {
02335             doc->setCurrent( true );    // let my next external parent take over
02336             return;
02337         }
02338         doc->forceCurrent( false ); // everybody else should keep off
02339     }
02340     else
02341         d->m_current = on;
02342 
02343     setTitleModified();
02344 }
02345 
02346 void KoDocument::forceCurrent( bool on )
02347 {
02348     //kdDebug(30003)<<k_funcinfo<<" url: "<<url().url()<<" force to: "<<on<<endl;
02349     d->m_current = on;
02350     KoDocument *doc = dynamic_cast<KoDocument *>( parent() );
02351     if ( doc )
02352     {
02353         doc->forceCurrent( false );
02354     }
02355 }
02356 
02357 bool KoDocument::isCurrent() const
02358 {
02359     return d->m_current;
02360 }
02361 
02362 bool KoDocument::storeInternal() const
02363 {
02364     return d->m_storeInternal;
02365 }
02366 
02367 void KoDocument::setStoreInternal( bool i )
02368 {
02369     d->m_storeInternal = i;
02370     //kdDebug(30003)<<k_funcinfo<<"="<<d->m_storeInternal<<" doc: "<<url().url()<<endl;
02371 }
02372 
02373 bool KoDocument::hasExternURL() const
02374 {
02375     return !url().protocol().isEmpty() && url().protocol() != STORE_PROTOCOL && url().protocol() != INTERNAL_PROTOCOL;
02376 }
02377 
02378 void KoDocument::slotStarted( KIO::Job* job )
02379 {
02380     if ( job )
02381     {
02382         job->setWindow( d->m_shells.current() );
02383     }
02384 }
02385 
02386 #include "koDocument_p.moc"
02387 #include "koDocument.moc"
KDE Logo
This file is part of the documentation for lib Library Version 1.4.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Mon Feb 13 09:40:00 2006 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003