/* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2005 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoDocument.h" #include "KoDocument_p.h" #include "KoDocumentIface.h" #include "KoDocumentChild.h" #include "KoView.h" #include "KoMainWindow.h" #include "KoFilterManager.h" #include "KoDocumentInfo.h" #include "KoOasisStyles.h" #include "KoOasisStore.h" #include "KoXmlNS.h" #include "KoOpenPane.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Define the protocol used here for embedded documents' URL // This used to "store" but KURL didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make KURL happy and still pass // some kind of relative path to KoDocumentChild #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cpp and koDocumentChild.cpp TQPtrList *KoDocument::s_documentList=0L; using namespace std; class KoViewWrapperWidget; /********************************************************** * * KoDocument * **********************************************************/ const int KoDocument::s_defaultAutoSave = 300; // 5 minutes class KoDocument::Private { public: Private() : m_dcopObject( 0L ), filterManager( 0L ), m_specialOutputFlag( 0 ), // default is native format m_isImporting( false ), m_isExporting( false ), m_numOperations( 0 ), modifiedAfterAutosave( false ), m_autosaving( false ), m_shouldCheckAutoSaveFile( true ), m_autoErrorHandlingEnabled( true ), m_backupFile( true ), m_backupPath( TQString() ), m_doNotSaveExtDoc( false ), m_current( false ), m_storeInternal( false ), m_bLoading( false ), m_startUpWidget( 0 ) { m_confirmNonNativeSave[0] = true; m_confirmNonNativeSave[1] = true; if ( TDEGlobal::locale()->measureSystem() == TDELocale::Imperial ) { m_unit = KoUnit::U_INCH; } else { m_unit = KoUnit::U_CM; } } TQPtrList m_views; TQPtrList m_children; TQPtrList m_shells; TQValueList m_viewBuildDocuments; KoViewWrapperWidget *m_wrapperWidget; KoDocumentIface * m_dcopObject; KoDocumentInfo *m_docInfo; KoView* hitTestView; KoUnit::Unit m_unit; KoFilterManager * filterManager; // The filter-manager to use when loading/saving [for the options] TQCString mimeType; // The actual mimetype of the document TQCString outputMimeType; // The mimetype to use when saving bool m_confirmNonNativeSave [2]; // used to pop up a dialog when saving for the // first time if the file is in a foreign format // (Save/Save As, Export) int m_specialOutputFlag; // See KoFileDialog in koMainWindow.cpp bool m_isImporting, m_isExporting; // File --> Import/Export vs File --> Open/Save TQTimer m_autoSaveTimer; TQString lastErrorMessage; // see openFile() int m_autoSaveDelay; // in seconds, 0 to disable. int m_numOperations; bool modifiedAfterAutosave; bool m_bSingleViewMode; bool m_autosaving; bool m_shouldCheckAutoSaveFile; // usually true bool m_autoErrorHandlingEnabled; // usually true bool m_backupFile; TQString m_backupPath; bool m_doNotSaveExtDoc; // makes it possible to save only internally stored child documents bool m_current; bool m_storeInternal; // Store this doc internally even if url is external bool m_bLoading; // True while loading (openURL is async) KoOpenPane* m_startUpWidget; TQString m_templateType; }; // Used in singleViewMode class KoViewWrapperWidget : public TQWidget { public: KoViewWrapperWidget( TQWidget *parent, const char *name ) : TQWidget( parent, name ) { TDEGlobal::locale()->insertCatalogue("koffice"); // Tell the iconloader about share/apps/koffice/icons TDEGlobal::iconLoader()->addAppDir("koffice"); m_view = 0L; // Avoid warning from KParts - we'll have the KoView as focus proxy anyway setFocusPolicy( TQWidget::ClickFocus ); } virtual ~KoViewWrapperWidget() { setFocusProxy( 0 ); // to prevent a crash due to clearFocus (#53466) } virtual void resizeEvent( TQResizeEvent * ) { TQObject *wid = child( 0, "TQWidget" ); if ( wid ) static_cast(wid)->setGeometry( 0, 0, width(), height() ); } virtual void childEvent( TQChildEvent *ev ) { if ( ev->type() == TQEvent::ChildInserted ) resizeEvent( 0L ); } // Called by openFile() void setKoView( KoView * view ) { m_view = view; setFocusProxy( m_view ); } KoView * koView() const { return m_view; } private: KoView* m_view; }; KoBrowserExtension::KoBrowserExtension( KoDocument * doc, const char * name ) : KParts::BrowserExtension( doc, name ) { emit enableAction( "print", true ); } void KoBrowserExtension::print() { KoDocument * doc = static_cast( parent() ); KoViewWrapperWidget * wrapper = static_cast( doc->widget() ); KoView * view = wrapper->koView(); // TODO remove code duplication (KoMainWindow), by moving this to KoView KPrinter printer; // ### TODO: apply global koffice settings here view->setupPrinter( printer ); if ( printer.setup( view ) ) view->print( printer ); } KoDocument::KoDocument( TQWidget * parentWidget, const char *widgetName, TQObject* parent, const char* name, bool singleViewMode ) : KParts::ReadWritePart( parent, name ) { if(s_documentList==0L) s_documentList=new TQPtrList; s_documentList->append(this); d = new Private; m_bEmpty = TRUE; connect( &d->m_autoSaveTimer, TQ_SIGNAL( timeout() ), this, TQ_SLOT( slotAutoSave() ) ); setAutoSave( s_defaultAutoSave ); d->m_bSingleViewMode = singleViewMode; // the parent setting *always* overrides! (Simon) if ( parent ) { if ( parent->inherits( "KoDocument" ) ) d->m_bSingleViewMode = ((KoDocument *)parent)->isSingleViewMode(); else if ( parent->inherits( "KParts::Part" ) ) d->m_bSingleViewMode = true; } if ( singleViewMode ) { d->m_wrapperWidget = new KoViewWrapperWidget( parentWidget, widgetName ); setWidget( d->m_wrapperWidget ); kdDebug(30003) << "creating KoBrowserExtension" << endl; (void) new KoBrowserExtension( this ); // ## only if embedded into a browser? } d->m_docInfo = new KoDocumentInfo( this, "document info" ); m_pageLayout.ptWidth = 0; m_pageLayout.ptHeight = 0; m_pageLayout.ptTop = 0; m_pageLayout.ptBottom = 0; m_pageLayout.ptLeft = 0; m_pageLayout.ptRight = 0; // A way to 'fix' the job's window, since we have no widget known to KParts if ( !singleViewMode ) connect( this, TQ_SIGNAL( started( TDEIO::Job* ) ), TQ_SLOT( slotStarted( TDEIO::Job* ) ) ); } KoDocument::~KoDocument() { if (!d) { return; } d->m_autoSaveTimer.stop(); TQPtrListIterator childIt( d->m_children ); for (; childIt.current(); ++childIt ) disconnect( childIt.current(), TQ_SIGNAL( destroyed() ), this, TQ_SLOT( slotChildDestroyed() ) ); // Tell our views that the document is already destroyed and // that they shouldn't try to access it. TQPtrListIterator vIt( d->m_views ); for (; vIt.current(); ++vIt ) vIt.current()->setDocumentDeleted(); delete d->m_startUpWidget; d->m_startUpWidget = 0; d->m_children.setAutoDelete( true ); d->m_children.clear(); d->m_shells.clear(); delete d->m_dcopObject; delete d->filterManager; delete d; d=0L; s_documentList->removeRef(this); // last one? if(s_documentList->isEmpty()) { delete s_documentList; s_documentList=0; } } bool KoDocument::isSingleViewMode() const { return d->m_bSingleViewMode; } bool KoDocument::isEmbedded() const { return dynamic_cast( parent() ) != 0; } KoView *KoDocument::createView( TQWidget *parent, const char *name ) { KoView *view=createViewInstance(parent, name); addView(view); return view; } bool KoDocument::exp0rt( const KURL & _url ) { bool ret; d->m_isExporting = true; // // Preserve a lot of state here because we need to restore it in order to // be able to fake a File --> Export. Can't do this in saveFile() because, // for a start, KParts has already set m_url and m_file and because we need // to restore the modified flag etc. and don't want to put a load on anyone // reimplementing saveFile() (Note: import() and export() will remain // non-virtual). // KURL oldURL = m_url; TQString oldFile = m_file; bool wasModified = isModified (); TQCString oldMimeType = mimeType (); // save... ret = saveAs( _url ); // // This is sooooo hacky :( // Hopefully we will restore enough state. // kdDebug(30003) << "Restoring KoDocument state to before export" << endl; // always restore m_url & m_file because KParts has changed them // (regardless of failure or success) m_url = oldURL; m_file = oldFile; // on successful export we need to restore modified etc. too // on failed export, mimetype/modified hasn't changed anyway if (ret) { setModified (wasModified); d->mimeType = oldMimeType; } d->m_isExporting = false; return ret; } bool KoDocument::saveFile() { kdDebug(30003) << "KoDocument::saveFile() doc='" << url().url() <<"'"<< endl; // set local again as it can happen that it gets resetted // so that all saved numbers have a . and not e.g a , as a // decimal seperator setlocale( LC_NUMERIC, "C" ); // Save it to be able to restore it after a failed save const bool wasModified = isModified (); // The output format is set by koMainWindow, and by openFile TQCString outputMimeType = d->outputMimeType; //Q_ASSERT( !outputMimeType.isEmpty() ); // happens when using the DCOP method saveAs if ( outputMimeType.isEmpty() ) outputMimeType = d->outputMimeType = nativeFormatMimeType(); TQApplication::setOverrideCursor( waitCursor ); if ( backupFile() ) { if ( url().isLocalFile() ) KSaveFile::backupFile( url().path(), d->m_backupPath ); else { TDEIO::UDSEntry entry; if ( TDEIO::NetAccess::stat( url(), entry, shells().current() ) ) { // this file exists => backup emit sigStatusBarMessage( i18n("Making backup...") ); KURL backup; if ( d->m_backupPath.isEmpty()) backup = url(); else backup = d->m_backupPath +"/"+url().fileName(); backup.setPath( backup.path() + TQString::fromLatin1("~") ); KFileItem item( entry, url() ); Q_ASSERT( item.name() == url().fileName() ); TDEIO::NetAccess::file_copy( url(), backup, item.permissions(), true /*overwrite*/, false /*resume*/, shells().current() ); } } } emit sigStatusBarMessage( i18n("Saving...") ); bool ret = false; bool suppressErrorDialog = false; if ( !isNativeFormat( outputMimeType ) ) { kdDebug(30003) << "Saving to format " << outputMimeType << " in " << m_file << endl; // Not native format : save using export filter if ( !d->filterManager ) d->filterManager = new KoFilterManager( this ); KoFilter::ConversionStatus status = d->filterManager->exp0rt( m_file, outputMimeType ); ret = status == KoFilter::OK; suppressErrorDialog = (status == KoFilter::UserCancelled || status == KoFilter::BadConversionGraph ); } else { // Native format => normal save Q_ASSERT( !m_file.isEmpty() ); ret = saveNativeFormat( m_file ); } if ( ret ) { removeAutoSaveFiles(); // Restart the autosave timer // (we don't want to autosave again 2 seconds after a real save) setAutoSave( d->m_autoSaveDelay ); } TQApplication::restoreOverrideCursor(); if ( !ret ) { if ( !suppressErrorDialog ) { showSavingErrorDialog(); } // couldn't save file so this new URL is invalid // FIXME: we should restore the current document's true URL instead of // setting it to nothing otherwise anything that depends on the URL // being correct will not work (i.e. the document will be called // "Untitled" which may not be true) // // Update: now the URL is restored in KoMainWindow but really, this // should still be fixed in KoDocument/KParts (ditto for m_file). // We still resetURL() here since we may or may not have been called // by KoMainWindow - Clarence resetURL(); // As we did not save, restore the "was modified" status setModified( wasModified ); } if ( ret ) { d->mimeType = outputMimeType; setConfirmNonNativeSave ( isExporting (), false ); } emit sigClearStatusBarMessage(); return ret; } TQCString KoDocument::mimeType() const { return d->mimeType; } void KoDocument::setMimeType( const TQCString & mimeType ) { d->mimeType = mimeType; } void KoDocument::setOutputMimeType( const TQCString & mimeType, int specialOutputFlag ) { d->outputMimeType = mimeType; d->m_specialOutputFlag = specialOutputFlag; } TQCString KoDocument::outputMimeType() const { return d->outputMimeType; } int KoDocument::specialOutputFlag() const { return d->m_specialOutputFlag; } bool KoDocument::confirmNonNativeSave( const bool exporting ) const { // "exporting ? 1 : 0" is different from "exporting" because a bool is // usually implemented like an "int", not "unsigned : 1" return d->m_confirmNonNativeSave [ exporting ? 1 : 0 ]; } void KoDocument::setConfirmNonNativeSave( const bool exporting, const bool on ) { d->m_confirmNonNativeSave [ exporting ? 1 : 0] = on; } bool KoDocument::wantExportConfirmation() const { return true; } bool KoDocument::isImporting() const { return d->m_isImporting; } bool KoDocument::isExporting() const { return d->m_isExporting; } void KoDocument::setCheckAutoSaveFile( bool b ) { d->m_shouldCheckAutoSaveFile = b; } void KoDocument::setAutoErrorHandlingEnabled( bool b ) { d->m_autoErrorHandlingEnabled = b; } bool KoDocument::isAutoErrorHandlingEnabled() const { return d->m_autoErrorHandlingEnabled; } void KoDocument::slotAutoSave() { if ( isModified() && d->modifiedAfterAutosave && !d->m_bLoading ) { connect( this, TQ_SIGNAL( sigProgress( int ) ), shells().current(), TQ_SLOT( slotProgress( int ) ) ); emit sigStatusBarMessage( i18n("Autosaving...") ); d->m_autosaving = true; bool ret = saveNativeFormat( autoSaveFile( m_file ) ); setModified( true ); if ( ret ) { d->modifiedAfterAutosave = false; d->m_autoSaveTimer.stop(); // until the next change } d->m_autosaving = false; emit sigClearStatusBarMessage(); disconnect( this, TQ_SIGNAL( sigProgress( int ) ), shells().current(), TQ_SLOT( slotProgress( int ) ) ); if ( !ret ) emit sigStatusBarMessage( i18n("Error during autosave! Partition full?") ); } } TDEAction *KoDocument::action( const TQDomElement &element ) const { // First look in the document itself TDEAction* act = KParts::ReadWritePart::action( element ); if ( act ) return act; Q_ASSERT( d->m_bSingleViewMode ); // Then look in the first view (this is for the single view mode) if ( !d->m_views.isEmpty() ) return d->m_views.getFirst()->action( element ); else return 0L; } TQDomDocument KoDocument::domDocument() const { // When embedded into e.g. konqueror, we want the view's GUI (hopefully a reduced one) // to be used. Q_ASSERT( d->m_bSingleViewMode ); if ( d->m_views.isEmpty() ) return TQDomDocument(); else return d->m_views.getFirst()->domDocument(); } void KoDocument::setManager( KParts::PartManager *manager ) { KParts::ReadWritePart::setManager( manager ); if ( d->m_bSingleViewMode && d->m_views.count() == 1 ) d->m_views.getFirst()->setPartManager( manager ); if ( manager ) { TQPtrListIterator it( d->m_children ); for (; it.current(); ++it ) if ( it.current()->document() ) manager->addPart( it.current()->document(), false ); } } void KoDocument::setReadWrite( bool readwrite ) { KParts::ReadWritePart::setReadWrite( readwrite ); TQPtrListIterator vIt( d->m_views ); for (; vIt.current(); ++vIt ) vIt.current()->updateReadWrite( readwrite ); TQPtrListIterator dIt( d->m_children ); for (; dIt.current(); ++dIt ) if ( dIt.current()->document() ) dIt.current()->document()->setReadWrite( readwrite ); setAutoSave( d->m_autoSaveDelay ); } void KoDocument::setAutoSave( int delay ) { d->m_autoSaveDelay = delay; if ( isReadWrite() && !isEmbedded() && d->m_autoSaveDelay > 0 ) d->m_autoSaveTimer.start( d->m_autoSaveDelay * 1000 ); else d->m_autoSaveTimer.stop(); } void KoDocument::addView( KoView *view ) { if ( !view ) return; d->m_views.append( view ); view->updateReadWrite( isReadWrite() ); } void KoDocument::removeView( KoView *view ) { d->m_views.removeRef( view ); } const TQPtrList& KoDocument::views() const { return d->m_views; } int KoDocument::viewCount() const { return d->m_views.count(); } void KoDocument::insertChild( KoDocumentChild *child ) { setModified( true ); d->m_children.append( child ); connect( child, TQ_SIGNAL( changed( KoChild * ) ), this, TQ_SLOT( slotChildChanged( KoChild * ) ) ); connect( child, TQ_SIGNAL( destroyed() ), this, TQ_SLOT( slotChildDestroyed() ) ); // It may be that insertChild is called without the KoDocumentChild // having a KoDocument attached, yet. This happens for example // when KPresenter loads a document with embedded objects. For those // KPresenterChild objects are allocated and insertChild is called. // Later in loadChildren() KPresenter iterates over the child list // and calls loadDocument for each child. That's exactly where we // will try to do what we cannot do now: Register the child document // at the partmanager (Simon) if ( manager() && !isSingleViewMode() && child->document() ) manager()->addPart( child->document(), false ); } void KoDocument::slotChildChanged( KoChild *c ) { assert( c->inherits( "KoDocumentChild" ) ); emit childChanged( static_cast( c ) ); } void KoDocument::slotChildDestroyed() { setModified( true ); const KoDocumentChild *child = static_cast( sender() ); d->m_children.removeRef( child ); } const TQPtrList& KoDocument::children() const { return d->m_children; } KParts::Part *KoDocument::hitTest( TQWidget *widget, const TQPoint &globalPos ) { TQPtrListIterator it( d->m_views ); for (; it.current(); ++it ) if ( static_cast(it.current()) == widget ) { KoView* view = it.current(); d->hitTestView = view; // koffice-1.x hack TQPoint canvasPos( view->canvas()->mapFromGlobal( globalPos ) ); canvasPos.rx() += view->canvasXOffset(); canvasPos.ry() += view->canvasYOffset(); KParts::Part *part = view->hitTest( canvasPos ); d->hitTestView = 0; if ( part ) return part; } return 0L; } KoView* KoDocument::hitTestView() { return d->hitTestView; } KoDocument* KoDocument::hitTest( const TQPoint &pos, const TQWMatrix &matrix ) { // Call KoDocumentChild::hitTest for any child document TQPtrListIterator it( d->m_children ); for (; it.current(); ++it ) { KoDocument *doc = it.current()->hitTest( pos, matrix ); if ( doc ) return doc; } // Unless we hit an embedded document, the hit is on this document itself. return this; } KoDocumentChild *KoDocument::child( KoDocument *doc ) { TQPtrListIterator it( d->m_children ); for (; it.current(); ++it ) if ( it.current()->document() == doc ) return it.current(); return 0L; } KoDocumentInfo *KoDocument::documentInfo() const { return d->m_docInfo; } void KoDocument::setViewBuildDocument( KoView *view, const TQDomDocument &doc ) { if ( d->m_views.find( view ) == -1 ) return; uint viewIdx = d->m_views.at(); if ( d->m_viewBuildDocuments.count() == viewIdx ) d->m_viewBuildDocuments.append( doc ); else if ( d->m_viewBuildDocuments.count() > viewIdx ) d->m_viewBuildDocuments[ viewIdx ] = doc; } TQDomDocument KoDocument::viewBuildDocument( KoView *view ) { TQDomDocument res; if ( d->m_views.find( view ) == -1 ) return res; uint viewIdx = d->m_views.at(); if ( viewIdx >= d->m_viewBuildDocuments.count() ) return res; res = d->m_viewBuildDocuments[ viewIdx ]; // make this entry empty. otherwise we get a segfault in TQMap ;-( d->m_viewBuildDocuments[ viewIdx ] = TQDomDocument(); return res; } void KoDocument::paintEverything( TQPainter &painter, const TQRect &rect, bool transparent, KoView *view, double zoomX, double zoomY ) { paintContent( painter, rect, transparent, zoomX, zoomY ); paintChildren( painter, rect, view, zoomX, zoomY ); } void KoDocument::paintChildren( TQPainter &painter, const TQRect &/*rect*/, KoView *view, double zoomX, double zoomY ) { TQPtrListIterator it( d->m_children ); for (; it.current(); ++it ) { // #### todo: paint only if child is visible inside rect painter.save(); paintChild( it.current(), painter, view, zoomX, zoomY ); painter.restore(); } } void KoDocument::paintChild( KoDocumentChild *child, TQPainter &painter, KoView *view, double zoomX, double zoomY ) { if ( child->isDeleted() ) return; // TQRegion rgn = painter.clipRegion(); child->transform( painter ); child->document()->paintEverything( painter, child->contentRect(), child->isTransparent(), view, zoomX, zoomY ); if ( view && view->partManager() ) { // ### do we need to apply zoomX and zoomY here ? KParts::PartManager *manager = view->partManager(); painter.scale( 1.0 / child->xScaling(), 1.0 / child->yScaling() ); int w = int( (double)child->contentRect().width() * child->xScaling() ); int h = int( (double)child->contentRect().height() * child->yScaling() ); if ( ( manager->selectedPart() == (KParts::Part *)child->document() && manager->selectedWidget() == (TQWidget *)view ) || ( manager->activePart() == (KParts::Part *)child->document() && manager->activeWidget() == (TQWidget *)view ) ) { // painter.setClipRegion( rgn ); painter.setClipping( FALSE ); painter.setPen( black ); painter.fillRect( -5, -5, w + 10, 5, white ); painter.fillRect( -5, h, w + 10, 5, white ); painter.fillRect( -5, -5, 5, h + 10, white ); painter.fillRect( w, -5, 5, h + 10, white ); painter.fillRect( -5, -5, w + 10, 5, BDiagPattern ); painter.fillRect( -5, h, w + 10, 5, BDiagPattern ); painter.fillRect( -5, -5, 5, h + 10, BDiagPattern ); painter.fillRect( w, -5, 5, h + 10, BDiagPattern ); if ( manager->selectedPart() == (KParts::Part *)child->document() && manager->selectedWidget() == (TQWidget *)view ) { TQColor color; if ( view->koDocument() == this ) color = black; else color = gray; painter.fillRect( -5, -5, 5, 5, color ); painter.fillRect( -5, h, 5, 5, color ); painter.fillRect( w, h, 5, 5, color ); painter.fillRect( w, -5, 5, 5, color ); painter.fillRect( w / 2 - 3, -5, 5, 5, color ); painter.fillRect( w / 2 - 3, h, 5, 5, color ); painter.fillRect( -5, h / 2 - 3, 5, 5, color ); painter.fillRect( w, h / 2 - 3, 5, 5, color ); } painter.setClipping( TRUE ); } } } bool KoDocument::isModified() const { if ( KParts::ReadWritePart::isModified() ) { //kdDebug(30003)< it( children() ); for( ; it.current(); ++it ) { KoDocument* childDoc = it.current()->document(); if (childDoc && !it.current()->isDeleted()) { if ( !childDoc->isStoredExtern() ) { //kdDebug(30003) << "KoDocument::saveChildren internal url: /" << i << endl; if ( !childDoc->saveToStore( _store, TQString::number( i++ ) ) ) return FALSE; if (!isExporting ()) childDoc->setModified( false ); } //else kdDebug(30003)<url().url()< it( children() ); for( ; it.current(); ++it ) { KoDocument* childDoc = it.current()->document(); if ( childDoc && !it.current()->isDeleted() ) { if ( !it.current()->saveOasis( store, manifestWriter ) ) return false; if ( !childDoc->isStoredExtern() && !isExporting () ) childDoc->setModified( false ); } } return true; } bool KoDocument::saveExternalChildren() { if ( d->m_doNotSaveExtDoc ) { //kdDebug(30003)<m_doNotSaveExtDoc = false; return true; } //kdDebug(30003)< it = children(); for (; (ch = it.current()); ++it ) { if ( !ch->isDeleted() ) { KoDocument* doc = ch->document(); if ( doc && doc->isStoredExtern() && doc->isModified() ) { kdDebug(30003)<<" save external doc='"<setDoNotSaveExtDoc(); // Only save doc + it's internal children if ( !doc->save() ) return false; // error } //kdDebug(30003)<saveExternalChildren() ) return false; } } return true; } bool KoDocument::saveNativeFormat( const TQString & file ) { d->lastErrorMessage = TQString(); //kdDebug(30003) << "Saving to store" << endl; KoStore::Backend backend = KoStore::Auto; #if 0 if ( d->m_specialOutputFlag == SaveAsKOffice1dot1 ) { kdDebug(30003) << "Saving as KOffice-1.1 format, using a tar.gz" << endl; backend = KoStore::Tar; // KOffice-1.0/1.1 used tar.gz for the native mimetype //// TODO more backwards compat stuff (embedded docs etc.) } else #endif if ( d->m_specialOutputFlag == SaveAsDirectoryStore ) { backend = KoStore::Directory; kdDebug(30003) << "Saving as uncompressed XML, using directory store." << endl; } else if ( d->m_specialOutputFlag == SaveAsFlatXML ) { kdDebug(30003) << "Saving as a flat XML file." << endl; TQFile f( file ); if ( f.open( IO_WriteOnly | IO_Translate ) ) { bool success = saveToStream( &f ); f.close(); return success; } else return false; } kdDebug(30003) << "KoDocument::saveNativeFormat nativeFormatMimeType=" << nativeFormatMimeType() << endl; // OLD: bool oasis = d->m_specialOutputFlag == SaveAsOASIS; // OLD: TQCString mimeType = oasis ? nativeOasisMimeType() : nativeFormatMimeType(); TQCString mimeType = d->outputMimeType; TQCString nativeOasisMime = nativeOasisMimeType(); bool oasis = !mimeType.isEmpty() && ( mimeType == nativeOasisMime || mimeType == nativeOasisMime + "-template" ); // TODO: use std::unique_ptr or create store on stack [needs API fixing], // to remove all the 'delete store' in all the branches KoStore* store = KoStore::createStore( file, KoStore::Write, mimeType, backend ); if ( store->bad() ) { d->lastErrorMessage = i18n( "Could not create the file for saving" ); // more details needed? delete store; return false; } if ( oasis ) { kdDebug(30003) << "Saving to OASIS format" << endl; // Tell KoStore not to touch the file names store->disallowNameExpansion(); KoOasisStore oasisStore( store ); KoXmlWriter* manifestWriter = oasisStore.manifestWriter( mimeType ); if ( !saveOasis( store, manifestWriter ) ) { kdDebug(30003) << "saveOasis failed" << endl; delete store; return false; } // Save embedded objects if ( !saveChildrenOasis( store, manifestWriter ) ) { kdDebug(30003) << "saveChildrenOasis failed" << endl; delete store; return false; } if ( store->open( "meta.xml" ) ) { if ( !d->m_docInfo->saveOasis( store ) || !store->close() ) { delete store; return false; } manifestWriter->addManifestEntry( "meta.xml", "text/xml" ); } else { d->lastErrorMessage = i18n( "Not able to write '%1'. Partition full?" ).arg( "meta.xml" ); delete store; return false; } if ( store->open( "Thumbnails/thumbnail.png" ) ) { if ( !saveOasisPreview( store, manifestWriter ) || !store->close() ) { d->lastErrorMessage = i18n( "Error while trying to write '%1'. Partition full?" ).arg( "Thumbnails/thumbnail.png" ); delete store; return false; } // No manifest entry! } else { d->lastErrorMessage = i18n( "Not able to write '%1'. Partition full?" ).arg( "Thumbnails/thumbnail.png" ); delete store; return false; } // Write out manifest file if ( !oasisStore.closeManifestWriter() ) { d->lastErrorMessage = i18n( "Error while trying to write '%1'. Partition full?" ).arg( "META-INF/manifest.xml" ); delete store; return false; } delete store; } else { // Save internal children first since they might get a new url if ( !saveChildren( store ) && !oasis ) { if ( d->lastErrorMessage.isEmpty() ) d->lastErrorMessage = i18n( "Error while saving embedded documents" ); // more details needed delete store; return false; } kdDebug(30003) << "Saving root" << endl; if ( store->open( "root" ) ) { KoStoreDevice dev( store ); if ( !saveToStream( &dev ) || !store->close() ) { kdDebug(30003) << "saveToStream failed" << endl; delete store; return false; } } else { d->lastErrorMessage = i18n( "Not able to write '%1'. Partition full?" ).arg( "maindoc.xml" ); delete store; return false; } if ( store->open( "documentinfo.xml" ) ) { TQDomDocument doc = d->m_docInfo->save(); KoStoreDevice dev( store ); TQCString s = doc.toCString(); // this is already Utf8! (void)dev.writeBlock( s.data(), s.size()-1 ); (void)store->close(); } if ( store->open( "preview.png" ) ) { // ### TODO: missing error checking (The partition could be full!) savePreview( store ); (void)store->close(); } if ( !completeSaving( store ) ) { delete store; return false; } kdDebug(30003) << "Saving done of url: " << url().url() << endl; delete store; } if ( !saveExternalChildren() ) { return false; } return true; } bool KoDocument::saveToStream( TQIODevice * dev ) { TQDomDocument doc = saveXML(); // Save to buffer TQCString s = doc.toCString(); // utf8 already // We use TQCString::size()-1 here, not the official TQCString::length // It works here, as we are not modifying TQCString as TQByteArray int nwritten = dev->writeBlock( s.data(), s.size()-1 ); if ( nwritten != (int)s.size()-1 ) kdWarning(30003) << "KoDocument::saveToStream wrote " << nwritten << " - expected " << s.size()-1 << endl; return nwritten == (int)s.size()-1; } // Called for embedded documents bool KoDocument::saveToStore( KoStore* _store, const TQString & _path ) { kdDebug(30003) << "Saving document to store " << _path << endl; // Use the path as the internal url if ( _path.startsWith( STORE_PROTOCOL ) ) m_url = KURL( _path ); else // ugly hack to pass a relative URI m_url = KURL( INTERNAL_PREFIX + _path ); // To make the children happy cd to the correct directory _store->pushDirectory(); _store->enterDirectory( _path ); // Save childen first since they might get a new url if ( !saveChildren( _store ) ) return false; // In the current directory we're the king :-) if ( _store->open( "root" ) ) { KoStoreDevice dev( _store ); if ( !saveToStream( &dev ) ) { _store->close(); return false; } if ( !_store->close() ) return false; } if ( !completeSaving( _store ) ) return false; // Now that we're done leave the directory again _store->popDirectory(); kdDebug(30003) << "Saved document to store" << endl; return true; } bool KoDocument::saveOasisPreview( KoStore* store, KoXmlWriter* manifestWriter ) { const TQPixmap pix = generatePreview( TQSize( 128, 128 ) ); TQImage preview ( pix.convertToImage().convertDepth( 32, TQt::ColorOnly ) ); if ( !preview.hasAlphaBuffer() ) { preview.setAlphaBuffer( true ); } // ### TODO: freedesktop.org Thumbnail specification (date...) KoStoreDevice io ( store ); if ( !io.open( IO_WriteOnly ) ) return false; if ( ! preview.save( &io, "PNG", 0 ) ) return false; io.close(); manifestWriter->addManifestEntry( "Thumbnails/", "" ); manifestWriter->addManifestEntry( "Thumbnails/thumbnail.png", "" ); return true; } bool KoDocument::savePreview( KoStore* store ) { TQPixmap pix = generatePreview(TQSize(256, 256)); // Reducing to 8bpp reduces file sizes quite a lot. const TQImage preview ( pix.convertToImage().convertDepth( 8, TQt::AvoidDither | TQt::DiffuseDither) ); KoStoreDevice io ( store ); if ( !io.open( IO_WriteOnly ) ) return false; if ( ! preview.save( &io, "PNG" ) ) // ### TODO What is -9 in quality terms? return false; io.close(); return true; } TQPixmap KoDocument::generatePreview( const TQSize& size ) { double docWidth, docHeight; int pixmapSize = TQMAX(size.width(), size.height()); if (m_pageLayout.ptWidth > 1.0) { docWidth = m_pageLayout.ptWidth / 72 * KoGlobal::dpiX(); docHeight = m_pageLayout.ptHeight / 72 * KoGlobal::dpiY(); } else { // If we don't have a page layout, just draw the top left hand corner docWidth = 500.0; docHeight = 500.0; } double ratio = docWidth / docHeight; TQPixmap pix; int previewWidth, previewHeight; if (ratio > 1.0) { previewWidth = (int) pixmapSize; previewHeight = (int) (pixmapSize / ratio); } else { previewWidth = (int) (pixmapSize * ratio); previewHeight = (int) pixmapSize; } pix.resize((int)docWidth, (int)docHeight); pix.fill( TQColor( 245, 245, 245 ) ); TQRect rc(0, 0, pix.width(), pix.height()); TQPainter p; p.begin(&pix); paintEverything(p, rc, false); p.end(); // ### TODO: why re-convert it to a TQPixmap, when mostly it will be re-converted back to a TQImage in the calling function pix.convertFromImage(pix.convertToImage().smoothScale(previewWidth, previewHeight)); return pix; } TQString KoDocument::autoSaveFile( const TQString & path ) const { // set local again as it can happen that it gets resetted // so that all saved numbers have a . and not e.g a , as a // decimal seperator setlocale( LC_NUMERIC, "C" ); // Using the extension allows to avoid relying on the mime magic when opening KMimeType::Ptr mime = KMimeType::mimeType( nativeFormatMimeType() ); TQString extension = mime->property( "X-TDE-NativeExtension" ).toString(); if ( path.isEmpty() ) { // Never saved? Use a temp file in $HOME then // Yes, two open unnamed docs will overwrite each other's autosave file, // but hmm, we can only do something if that's in the same process anyway... TQString ret = TQDir::homeDirPath() + "/." + TQString::fromLatin1(instance()->instanceName()) + ".autosave" + extension; return ret; } else { KURL url( path ); Q_ASSERT( url.isLocalFile() ); TQString dir = url.directory(false); TQString filename = url.fileName(); return dir + "." + filename + ".autosave" + extension; } } bool KoDocument::checkAutoSaveFile() { TQString asf = autoSaveFile( TQString() ); // the one in $HOME //kdDebug(30003) << "asf=" << asf << endl; if ( TQFile::exists( asf ) ) { TQDateTime date = TQFileInfo(asf).lastModified(); TQString dateStr = date.toString(TQt::LocalDate); int res = KMessageBox::warningYesNoCancel( 0, i18n( "An autosaved file for an unnamed document exists in %1.\nThis file is dated %2\nDo you want to open it?" ) .arg(asf, dateStr) ); switch(res) { case KMessageBox::Yes : { KURL url; url.setPath( asf ); bool ret = openURL( url ); if ( ret ) resetURL(); return ret; } case KMessageBox::No : TQFile::remove( asf ); return false; default: // Cancel return false; } } return false; } bool KoDocument::import( const KURL & _url ) { bool ret; kdDebug (30003) << "KoDocument::import url=" << _url.url() << endl; d->m_isImporting = true; // open... ret = openURL (_url); // reset m_url & m_file (kindly? set by KParts::openURL()) to simulate a // File --> Import if (ret) { kdDebug (30003) << "KoDocument::import success, resetting url" << endl; resetURL (); setTitleModified (); } d->m_isImporting = false; return ret; } bool KoDocument::openURL( const KURL & _url ) { kdDebug(30003) << "KoDocument::openURL url=" << _url.url() << endl; d->lastErrorMessage = TQString(); // Reimplemented, to add a check for autosave files and to improve error reporting if ( !_url.isValid() ) { d->lastErrorMessage = i18n( "Malformed URL\n%1" ).arg( _url.url() ); // ## used anywhere ? return false; } if ( !closeURL() ) return false; KURL url( _url ); bool autosaveOpened = false; d->m_bLoading = true; if ( url.isLocalFile() && d->m_shouldCheckAutoSaveFile ) { TQString file = url.path(); TQString asf = autoSaveFile( file ); if ( TQFile::exists( asf ) ) { //kdDebug(30003) << "KoDocument::openURL asf=" << asf << endl; // ## TODO compare timestamps ? int res = KMessageBox::warningYesNoCancel( 0, i18n( "An autosaved file exists for this document.\nDo you want to open it instead?" )); switch(res) { case KMessageBox::Yes : url.setPath( asf ); autosaveOpened = true; break; case KMessageBox::No : TQFile::remove( asf ); break; default: // Cancel d->m_bLoading = false; return false; } } } bool ret = KParts::ReadWritePart::openURL( url ); if ( autosaveOpened ) resetURL(); // Force save to act like 'Save As' else { // We have no koffice shell when we are being embedded as a readonly part. //if ( d->m_shells.isEmpty() ) // kdWarning(30003) << "KoDocument::openURL no shell yet !" << endl; // Add to recent actions list in our shells TQPtrListIterator it( d->m_shells ); for (; it.current(); ++it ) it.current()->addRecentURL( _url ); } return ret; } bool KoDocument::openFile() { //kdDebug(30003) << "KoDocument::openFile for " << m_file << endl; if ( !TQFile::exists(m_file) ) { TQApplication::restoreOverrideCursor(); if ( d->m_autoErrorHandlingEnabled ) // Maybe offer to create a new document with that name ? KMessageBox::error(0L, i18n("The file %1 does not exist.").arg(m_file) ); d->m_bLoading = false; return false; } TQApplication::setOverrideCursor( waitCursor ); d->m_specialOutputFlag = 0; TQCString _native_format = nativeFormatMimeType(); KURL u; u.setPath( m_file ); TQString typeName = KMimeType::findByURL( u, 0, true )->name(); // Allow to open backup files, don't keep the mimetype application/x-trash. if ( typeName == "application/x-trash" ) { TQString path = u.path(); TQStringList patterns = KMimeType::mimeType( typeName )->patterns(); // Find the extension that makes it a backup file, and remove it for( TQStringList::Iterator it = patterns.begin(); it != patterns.end(); ++it ) { TQString ext = *it; if ( !ext.isEmpty() && ext[0] == '*' ) { ext.remove(0, 1); if ( path.endsWith( ext ) ) { path.truncate( path.length() - ext.length() ); break; } } } typeName = KMimeType::findByPath( path, 0, true )->name(); } // Special case for flat XML files (e.g. using directory store) if ( u.fileName() == "maindoc.xml" || u.fileName() == "content.xml" || typeName == "inode/directory" ) { typeName = _native_format; // Hmm, what if it's from another app? ### Check mimetype d->m_specialOutputFlag = SaveAsDirectoryStore; kdDebug(30003) << "KoDocument::openFile loading " << u.fileName() << ", using directory store for " << m_file << "; typeName=" << typeName << endl; } kdDebug(30003) << "KoDocument::openFile " << m_file << " type:" << typeName << endl; TQString importedFile = m_file; if ( !isNativeFormat( typeName.latin1() ) ) { if ( !d->filterManager ) d->filterManager = new KoFilterManager( this ); KoFilter::ConversionStatus status; importedFile = d->filterManager->import( m_file, status ); if ( status != KoFilter::OK ) { TQApplication::restoreOverrideCursor(); TQString msg; switch( status ) { case KoFilter::OK: break; case KoFilter::CreationError: msg = i18n( "Creation error" ); break; case KoFilter::FileNotFound: msg = i18n( "File not found" ); break; case KoFilter::StorageCreationError: msg = i18n( "Cannot create storage" ); break; case KoFilter::BadMimeType: msg = i18n( "Bad MIME type" ); break; case KoFilter::EmbeddedDocError: msg = i18n( "Error in embedded document" ); break; case KoFilter::WrongFormat: msg = i18n( "Format not recognized" ); break; case KoFilter::NotImplemented: msg = i18n( "Not implemented" ); break; case KoFilter::ParsingError: msg = i18n( "Parsing error" ); break; case KoFilter::PasswordProtected: msg = i18n( "Document is password protected" ); break; case KoFilter::InternalError: case KoFilter::UnexpectedEOF: case KoFilter::UnexpectedOpcode: case KoFilter::StupidError: // ?? what is this ?? case KoFilter::UsageError: msg = i18n( "Internal error" ); break; case KoFilter::OutOfMemory: msg = i18n( "Out of memory" ); break; case KoFilter::UserCancelled: case KoFilter::BadConversionGraph: // intentionally we do not prompt the error message here break; default: msg = i18n( "Unknown error" ); break; } if( d->m_autoErrorHandlingEnabled && !msg.isEmpty()) { TQString errorMsg( i18n( "Could not open\n%2.\nReason: %1" ) ); TQString docUrl = url().prettyURL( 0, KURL::StripFileProtocol ); KMessageBox::error( 0L, errorMsg.arg(msg).arg(docUrl) ); } d->m_bLoading = false; return false; } kdDebug(30003) << "KoDocument::openFile - importedFile '" << importedFile << "', status: " << static_cast( status ) << endl; } TQApplication::restoreOverrideCursor(); bool ok = true; if (!importedFile.isEmpty()) // Something to load (tmp or native file) ? { // The filter, if any, has been applied. It's all native format now. if ( !loadNativeFormat( importedFile ) ) { ok = false; if ( d->m_autoErrorHandlingEnabled ) { showLoadingErrorDialog(); } } } if ( importedFile != m_file ) { // We opened a temporary file (result of an import filter) // Set document URL to empty - we don't want to save in /tmp ! // But only if in readwrite mode (no saving problem otherwise) // -- // But this isn't true at all. If this is the result of an // import, then importedFile=temporary_file.kwd and // m_file/m_url=foreignformat.ext so m_url is correct! // So don't resetURL() or else the caption won't be set when // foreign files are opened (an annoying bug). // - Clarence // #if 0 if ( isReadWrite() ) resetURL(); #endif // remove temp file - uncomment this to debug import filters if(!importedFile.isEmpty()) { TQFile::remove( importedFile ); } } if ( ok && d->m_bSingleViewMode ) { // See addClient below KXMLGUIFactory* guiFactory = factory(); if( guiFactory ) // 0L when splitting views in konq, for some reason guiFactory->removeClient( this ); if ( !d->m_views.isEmpty() ) { // We already had a view (this happens when doing reload in konqueror) KoView* v = d->m_views.first(); if( guiFactory ) guiFactory->removeClient( v ); removeView( v ); delete v; Q_ASSERT( d->m_views.isEmpty() ); } KoView *view = createView( d->m_wrapperWidget ); d->m_wrapperWidget->setKoView( view ); view->show(); // Ok, now we have a view, so action() and domDocument() will work as expected // -> rebuild GUI if ( guiFactory ) guiFactory->addClient( this ); } if ( ok ) { setMimeTypeAfterLoading( typeName ); } d->m_bLoading = false; return ok; } // shared between openFile and koMainWindow's "create new empty document" code void KoDocument::setMimeTypeAfterLoading( const TQString& mimeType ) { d->mimeType = mimeType.latin1(); d->outputMimeType = d->mimeType; const bool needConfirm = !isNativeFormat( d->mimeType ); setConfirmNonNativeSave( false, needConfirm ); setConfirmNonNativeSave( true, needConfirm ); } // The caller must call store->close() if loadAndParse returns true. bool KoDocument::oldLoadAndParse(KoStore* store, const TQString& filename, TQDomDocument& doc) { //kdDebug(30003) << "oldLoadAndParse: Trying to open " << filename << endl; if (!store->open(filename)) { kdWarning(30003) << "Entry " << filename << " not found!" << endl; d->lastErrorMessage = i18n( "Could not find %1" ).arg( filename ); return false; } // Error variables for TQDomDocument::setContent TQString errorMsg; int errorLine, errorColumn; bool ok = doc.setContent( store->device(), &errorMsg, &errorLine, &errorColumn ); if ( !ok ) { kdError(30003) << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; d->lastErrorMessage = i18n( "Parsing error in %1 at line %2, column %3\nError message: %4" ) .arg( filename ).arg( errorLine ).arg( errorColumn ) .arg( i18n ( "TQXml", errorMsg.utf8() ) ); store->close(); return false; } kdDebug(30003) << "File " << filename << " loaded and parsed" << endl; return true; } bool KoDocument::loadNativeFormat( const TQString & file ) { TQFileInfo fileInfo( file ); if ( !fileInfo.exists() ) // check duplicated from openURL, but this is useful for templates { d->lastErrorMessage = i18n("The file %1 does not exist.").arg(file); return false; } if ( !fileInfo.isFile() ) { d->lastErrorMessage = i18n( "%1 is not a file." ).arg(file); return false; } TQApplication::setOverrideCursor( waitCursor ); kdDebug(30003) << "KoDocument::loadNativeFormat( " << file << " )" << endl; TQFile in; bool isRawXML = false; if ( d->m_specialOutputFlag != SaveAsDirectoryStore ) // Don't try to open a directory ;) { in.setName(file); if ( !in.open( IO_ReadOnly ) ) { TQApplication::restoreOverrideCursor(); d->lastErrorMessage = i18n( "Could not open the file for reading (check read permissions)." ); return false; } // Try to find out whether it is a mime multi part file char buf[5]; if ( in.readBlock( buf, 4 ) < 4 ) { TQApplication::restoreOverrideCursor(); in.close(); d->lastErrorMessage = i18n( "Could not read the beginning of the file." ); return false; } // ### TODO: allow UTF-16 isRawXML = (strncasecmp( buf, "lastErrorMessage = i18n( "Could not read the beginning of the file." ); return false; } } while ( TQChar( buf[0] ).isSpace() ); if ( buf[0] == '<' ) { // First not-space character if ( in.readBlock( buf , 4 ) < 4 ) { TQApplication::restoreOverrideCursor(); in.close(); d->lastErrorMessage = i18n( "Could not read the beginning of the file." ); return false; } isRawXML = (strncasecmp( buf, "math", 4 ) == 0); // file begins with lastErrorMessage = i18n( "parsing error in the main document at line %1, column %2\nError message: %3" ) .arg( errorLine ).arg( errorColumn ).arg( i18n ( errorMsg.utf8() ) ); res=false; } TQApplication::restoreOverrideCursor(); in.close(); m_bEmpty = false; return res; } else { // It's a koffice store (tar.gz, zip, directory, etc.) in.close(); return loadNativeFormatFromStore( file ); } } bool KoDocument::loadNativeFormatFromStore( const TQString& file ) { KoStore::Backend backend = (d->m_specialOutputFlag == SaveAsDirectoryStore) ? KoStore::Directory : KoStore::Auto; KoStore * store = KoStore::createStore( file, KoStore::Read, "", backend ); if ( store->bad() ) { d->lastErrorMessage = i18n( "Not a valid KOffice file: %1" ).arg( file ); delete store; TQApplication::restoreOverrideCursor(); return false; } bool oasis = true; // OASIS/OOo file format? if ( store->hasFile( "content.xml" ) ) { store->disallowNameExpansion(); KoOasisStore oasisStore( store ); // We could check the 'mimetype' file, but let's skip that and be tolerant. if ( !loadOasisFromStore( store ) ) { delete store; TQApplication::restoreOverrideCursor(); return false; } } else if ( store->hasFile( "root" ) ) // Fallback to "old" file format (maindoc.xml) { oasis = false; TQDomDocument doc; bool ok = oldLoadAndParse( store, "root", doc ); if ( ok ) ok = loadXML( store->device(), doc ); if ( !ok ) { delete store; TQApplication::restoreOverrideCursor(); return false; } store->close(); if ( !loadChildren( store ) ) { kdError(30003) << "ERROR: Could not load children" << endl; // Don't abort, proceed nonetheless } } else { kdError(30003) << "ERROR: No maindoc.xml" << endl; d->lastErrorMessage = i18n( "Invalid document: no file 'maindoc.xml'." ); delete store; TQApplication::restoreOverrideCursor(); return false; } if ( oasis && store->hasFile( "meta.xml" ) ) { TQDomDocument metaDoc; KoOasisStore oasisStore( store ); if ( oasisStore.loadAndParse( "meta.xml", metaDoc, d->lastErrorMessage ) ) { d->m_docInfo->loadOasis( metaDoc ); } } else if ( !oasis && store->hasFile( "documentinfo.xml" ) ) { TQDomDocument doc; if ( oldLoadAndParse( store, "documentinfo.xml", doc ) ) { store->close(); d->m_docInfo->load( doc ); } } else { //kdDebug( 30003 ) << "cannot open document info" << endl; delete d->m_docInfo; d->m_docInfo = new KoDocumentInfo( this, "document info" ); } bool res = completeLoading( store ); delete store; TQApplication::restoreOverrideCursor(); m_bEmpty = false; return res; } // For embedded documents bool KoDocument::loadFromStore( KoStore* _store, const TQString& url ) { if ( _store->open( url ) ) { TQDomDocument doc; doc.setContent( _store->device() ); if ( !loadXML( _store->device(), doc ) ) { _store->close(); return false; } _store->close(); } else { kdWarning() << "couldn't open " << url << endl; } _store->pushDirectory(); // Store as document URL if ( url.startsWith( STORE_PROTOCOL ) ) { m_url = KURL( url ); } else { m_url = KURL( INTERNAL_PREFIX + url ); _store->enterDirectory( url ); } if ( !loadChildren( _store ) ) { kdError(30003) << "ERROR: Could not load children" << endl; #if 0 return false; #endif } bool result = completeLoading( _store ); // Restore the "old" path _store->popDirectory(); return result; } bool KoDocument::loadOasisFromStore( KoStore* store ) { KoOasisStyles oasisStyles; TQDomDocument contentDoc; TQDomDocument settingsDoc; KoOasisStore oasisStore( store ); bool ok = oasisStore.loadAndParse( "content.xml", contentDoc, d->lastErrorMessage ); if ( !ok ) return false; TQDomDocument stylesDoc; (void)oasisStore.loadAndParse( "styles.xml", stylesDoc, d->lastErrorMessage ); // Load styles from style.xml oasisStyles.createStyleMap( stylesDoc, true ); // Also load styles from content.xml oasisStyles.createStyleMap( contentDoc, false ); // TODO post 1.4, pass manifestDoc to the apps so that they don't have to do it themselves // (when calling KoDocumentChild::loadOasisDocument) //TQDomDocument manifestDoc; //KoOasisStore oasisStore( store ); //if ( !oasisStore.loadAndParse( "tar:/META-INF/manifest.xml", manifestDoc, d->lastErrorMessage ) ) // return false; if ( store->hasFile( "settings.xml" ) ) { (void)oasisStore.loadAndParse( "settings.xml", settingsDoc, d->lastErrorMessage ); } if ( !loadOasis( contentDoc, oasisStyles, settingsDoc, store ) ) return false; return true; } bool KoDocument::isInOperation() const { return d->m_numOperations > 0; } void KoDocument::emitBeginOperation() { /* if we're already in an operation, don't send the signal again */ if (!isInOperation()) emit sigBeginOperation(); d->m_numOperations++; } void KoDocument::emitEndOperation() { d->m_numOperations--; /* don't end the operation till we've cleared all the nested operations */ if (d->m_numOperations == 0) emit sigEndOperation(); else if (d->m_numOperations < 0) /* ignore 'end' calls with no matching 'begin' call */ d->m_numOperations = 0; } bool KoDocument::isStoredExtern() const { return !storeInternal() && hasExternURL(); } void KoDocument::setModified( bool mod ) { if ( isAutosaving() ) // ignore setModified calls due to autosaving return; //kdDebug(30003)<The document '%1' has been modified.

Do you want to save it?

" ).arg(name)); switch(res) { case KMessageBox::Yes : setDoNotSaveExtDoc(); // Let save() only save myself and my internal docs save(); // NOTE: External files always in native format. ###TODO: Handle non-native format setModified( false ); // Now when queryClose() is called by closeEvent it won't do anything. break; case KMessageBox::No : removeAutoSaveFiles(); setModified( false ); // Now when queryClose() is called by closeEvent it won't do anything. break; default : // case KMessageBox::Cancel : return res; // cancels the rest of the files } return res; } int KoDocument::queryCloseExternalChildren() { //kdDebug(30003)< it( children() ); for (; it.current(); ++it ) { if ( !it.current()->isDeleted() ) { KoDocument *doc = it.current()->document(); if ( doc ) { bool foo = doc->isStoredExtern(); kdDebug(36001) << "========== isStoredExtern() returned " << foo << " ==========" << endl; if ( foo ) //###TODO: Handle non-native mimetype docs { { kdDebug(30003)<url().url()<<" extern="<isStoredExtern()<queryCloseDia() == KMessageBox::Cancel ) return KMessageBox::Cancel; } } if ( doc->queryCloseExternalChildren() == KMessageBox::Cancel ) return KMessageBox::Cancel; } } } return KMessageBox::Ok; } void KoDocument::setTitleModified( const TQString caption, bool mod ) { //kdDebug(30003)<( parent() ); if ( doc ) { doc->setTitleModified( caption, mod ); return; } // we must be root doc so update caption in all related windows TQPtrListIterator it( d->m_shells ); for (; it.current(); ++it ) { it.current()->updateCaption(); it.current()->updateReloadFileAction(this); it.current()->updateVersionsFileAction(this); } } void KoDocument::setTitleModified() { //kdDebug(30003)<m_current<( parent() ); TQString caption; if ( (url().isEmpty() || isStoredExtern()) && d->m_current ) { // Get caption from document info (title(), in about page) if ( documentInfo() ) { KoDocumentInfoPage * page = documentInfo()->page( TQString::fromLatin1("about") ); if (page) caption = static_cast(page)->title(); } if ( caption.isEmpty() ) caption = url().prettyURL( 0, KURL::StripFileProtocol ); // Fall back to document URL //kdDebug(30003)<setTitleModified( caption, isModified() ); return; } else { // we must be root doc so update caption in all related windows setTitleModified( caption, isModified() ); return; } } if ( doc ) { // internal doc or not current doc, so pass on the buck doc->setTitleModified(); } } bool KoDocument::loadChildren( KoStore* ) { return true; } bool KoDocument::completeLoading( KoStore* ) { return true; } bool KoDocument::completeSaving( KoStore* ) { return true; } TQDomDocument KoDocument::createDomDocument( const TQString& tagName, const TQString& version ) const { return createDomDocument( instance()->instanceName(), tagName, version ); } //static TQDomDocument KoDocument::createDomDocument( const TQString& appName, const TQString& tagName, const TQString& version ) { TQDomImplementation impl; TQString url = TQString("http://www.koffice.org/DTD/%1-%1.dtd").arg(appName).arg(version); TQDomDocumentType dtype = impl.createDocumentType( tagName, TQString("-//KDE//DTD %1 %1//EN").arg(appName).arg(version), url ); // The namespace URN doesn't need to include the version number. TQString namespaceURN = TQString("http://www.koffice.org/DTD/%1").arg(appName); TQDomDocument doc = impl.createDocument( namespaceURN, tagName, dtype ); doc.insertBefore( doc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ), doc.documentElement() ); return doc; } KoXmlWriter* KoDocument::createOasisXmlWriter( TQIODevice* dev, const char* rootElementName ) { KoXmlWriter* writer = new KoXmlWriter( dev ); writer->startDocument( rootElementName ); writer->startElement( rootElementName ); writer->addAttribute( "xmlns:office", KoXmlNS::office ); writer->addAttribute( "xmlns:meta", KoXmlNS::meta ); if ( qstrcmp( rootElementName, "office:document-meta" ) != 0 ) { writer->addAttribute( "xmlns:config", KoXmlNS::config ); writer->addAttribute( "xmlns:text", KoXmlNS::text ); writer->addAttribute( "xmlns:table", KoXmlNS::table ); writer->addAttribute( "xmlns:draw", KoXmlNS::draw ); writer->addAttribute( "xmlns:presentation", KoXmlNS::presentation ); writer->addAttribute( "xmlns:dr3d", KoXmlNS::dr3d ); writer->addAttribute( "xmlns:chart", KoXmlNS::chart ); writer->addAttribute( "xmlns:form", KoXmlNS::form ); writer->addAttribute( "xmlns:script", KoXmlNS::script ); writer->addAttribute( "xmlns:style", KoXmlNS::style ); writer->addAttribute( "xmlns:number", KoXmlNS::number ); writer->addAttribute( "xmlns:math", KoXmlNS::math ); writer->addAttribute( "xmlns:svg", KoXmlNS::svg ); writer->addAttribute( "xmlns:fo", KoXmlNS::fo ); writer->addAttribute( "xmlns:koffice", KoXmlNS::koffice ); } // missing: office:version="1.0" writer->addAttribute( "xmlns:dc", KoXmlNS::dc ); writer->addAttribute( "xmlns:xlink", KoXmlNS::xlink ); return writer; } TQDomDocument KoDocument::saveXML() { kdError(30003) << "KoDocument::saveXML not implemented" << endl; d->lastErrorMessage = i18n( "Internal error: saveXML not implemented" ); return TQDomDocument(); } KService::Ptr KoDocument::nativeService() { if ( !m_nativeService ) m_nativeService = readNativeService( instance() ); return m_nativeService; } TQCString KoDocument::nativeFormatMimeType() const { KService::Ptr service = const_cast(this)->nativeService(); if ( !service ) return TQCString(); TQCString nativeMimeType = service->property( "X-TDE-NativeMimeType" ).toString().latin1(); if ( nativeMimeType.isEmpty() ) { // shouldn't happen, let's find out why it happened if ( !service->serviceTypes().contains( "KOfficePart" ) ) kdWarning(30003) << "Wrong desktop file, KOfficePart isn't mentionned" << endl; else if ( !KServiceType::serviceType( "KOfficePart" ) ) kdWarning(30003) << "The KOfficePart service type isn't installed!" << endl; } return nativeMimeType; } TQCString KoDocument::nativeOasisMimeType() const { KService::Ptr service = const_cast(this)->nativeService(); if ( !service ) return TQCString(); return service->property( "X-TDE-NativeOasisMimeType" ).toString().latin1(); } //static KService::Ptr KoDocument::readNativeService( TDEInstance *instance ) { TQString instname = instance ? instance->instanceName() : kapp->instanceName(); // The new way is: we look for a foopart.desktop in the kde_services dir. TQString servicepartname = instname + "part.desktop"; KService::Ptr service = KService::serviceByDesktopPath( servicepartname ); if ( service ) kdDebug(30003) << servicepartname << " found." << endl; if ( !service ) { // The old way is kept as fallback for compatibility, but in theory this is really never used anymore. // Try by path first, so that we find the global one (which has the native mimetype) // even if the user created a kword.desktop in ~/.trinity/share/applnk or any subdir of it. // If he created it under ~/.trinity/share/applnk/Office/ then no problem anyway. service = KService::serviceByDesktopPath( TQString::fromLatin1("Office/%1.desktop").arg(instname) ); } if ( !service ) service = KService::serviceByDesktopName( instname ); return service; } TQCString KoDocument::readNativeFormatMimeType( TDEInstance *instance ) //static { KService::Ptr service = readNativeService( instance ); if ( !service ) return TQCString(); if ( service->property( "X-TDE-NativeMimeType" ).toString().isEmpty() ) { // It may be that the servicetype "KOfficePart" is missing, which leads to this property not being known if ( KServiceType::serviceType( "KOfficePart" ) == 0L ) kdError(30003) << "The serviceType KOfficePart is missing. Check that you have a kofficepart.desktop file in the share/servicetypes directory." << endl; else { TQString instname = instance ? instance->instanceName() : kapp->instanceName(); if ( instname != "koshell" ) // hack for koshell kdWarning(30003) << service->desktopEntryPath() << ": no X-TDE-NativeMimeType entry!" << endl; } } return service->property( "X-TDE-NativeMimeType" ).toString().latin1(); } TQStringList KoDocument::readExtraNativeMimeTypes( TDEInstance *instance ) //static { KService::Ptr service = readNativeService( instance ); if ( !service ) return TQStringList(); return service->property( "X-TDE-ExtraNativeMimeTypes" ).toStringList(); } void KoDocument::setupXmlReader( TQXmlSimpleReader& reader, bool namespaceProcessing ) { if ( namespaceProcessing ) { reader.setFeature( "http://xml.org/sax/features/namespaces", TRUE ); reader.setFeature( "http://xml.org/sax/features/namespace-prefixes", FALSE ); } else { reader.setFeature( "http://xml.org/sax/features/namespaces", FALSE ); reader.setFeature( "http://xml.org/sax/features/namespace-prefixes", TRUE ); } reader.setFeature( "http://trolltech.com/xml/features/report-whitespace-only-CharData", TRUE ); } bool KoDocument::isNativeFormat( const TQCString& mimetype ) const { if ( mimetype == nativeFormatMimeType() ) return true; return extraNativeMimeTypes().contains( mimetype ); } TQStringList KoDocument::extraNativeMimeTypes() const { TQStringList lst; // This implementation is temporary while we treat both koffice-1.3 and OASIS formats as native. // But it's good to have this virtual method, in case some app want to // support more than one native format. KService::Ptr service = const_cast(this)->nativeService(); if ( !service ) // can't happen return lst; return service->property( "X-TDE-ExtraNativeMimeTypes" ).toStringList(); } int KoDocument::supportedSpecialFormats() const { // Apps which support special output flags can add reimplement and add to this. // E.g. this is how did "saving in the 1.1 format". // SaveAsDirectoryStore is a given since it's implemented by KoDocument itself. return SaveAsDirectoryStore; } void KoDocument::addShell( KoMainWindow *shell ) { if ( d->m_shells.findRef( shell ) == -1 ) { //kdDebug(30003) << "addShell: shell " << (void*)shell << " added to doc " << this << endl; d->m_shells.append( shell ); } } void KoDocument::removeShell( KoMainWindow *shell ) { //kdDebug(30003) << "removeShell: shell " << (void*)shell << " removed from doc " << this << endl; d->m_shells.removeRef( shell ); } const TQPtrList& KoDocument::shells() const { return d->m_shells; } int KoDocument::shellCount() const { return d->m_shells.count(); } DCOPObject * KoDocument::dcopObject() { if ( !d->m_dcopObject ) d->m_dcopObject = new KoDocumentIface( this ); return d->m_dcopObject; } TQCString KoDocument::dcopObjectId() const { return const_cast(this)->dcopObject()->objId(); } void KoDocument::setErrorMessage( const TQString& errMsg ) { d->lastErrorMessage = errMsg; } TQString KoDocument::errorMessage() const { return d->lastErrorMessage; } void KoDocument::showSavingErrorDialog() { if ( d->lastErrorMessage.isEmpty() ) { KMessageBox::error( 0L, i18n( "Could not save\n%1" ).arg( m_file ) ); } else if ( d->lastErrorMessage != "USER_CANCELED" ) { KMessageBox::error( 0L, i18n( "Could not save %1\nReason: %2" ).arg( m_file, d->lastErrorMessage ) ); } } void KoDocument::showLoadingErrorDialog() { if ( d->lastErrorMessage.isEmpty() ) { KMessageBox::error( 0L, i18n( "Could not open\n%1" ).arg( url().prettyURL( 0, KURL::StripFileProtocol ) ) ); } else if ( d->lastErrorMessage != "USER_CANCELED" ) { KMessageBox::error( 0L, i18n( "Could not open %1\nReason: %2" ).arg( url().prettyURL( 0, KURL::StripFileProtocol ), d->lastErrorMessage ) ); } } bool KoDocument::isAutosaving() const { return d->m_autosaving; } bool KoDocument::isLoading() const { return d->m_bLoading; } void KoDocument::removeAutoSaveFiles() { // Eliminate any auto-save file TQString asf = autoSaveFile( m_file ); // the one in the current dir if ( TQFile::exists( asf ) ) TQFile::remove( asf ); asf = autoSaveFile( TQString() ); // and the one in $HOME if ( TQFile::exists( asf ) ) TQFile::remove( asf ); } void KoDocument::setBackupFile( bool _b ) { d->m_backupFile = _b; } bool KoDocument::backupFile()const { return d->m_backupFile; } void KoDocument::setBackupPath( const TQString & _path) { d->m_backupPath = _path; } TQString KoDocument::backupPath()const { return d->m_backupPath; } void KoDocument::setCurrent( bool on ) { //kdDebug(30003)<( parent() ); if ( doc ) { if ( !isStoredExtern() ) { // internal doc so set next external to current (for safety) doc->setCurrent( true ); return; } // only externally stored docs shall have file name in title d->m_current = on; if ( !on ) { doc->setCurrent( true ); // let my next external parent take over return; } doc->forceCurrent( false ); // everybody else should keep off } else d->m_current = on; setTitleModified(); } void KoDocument::forceCurrent( bool on ) { //kdDebug(30003)<m_current = on; KoDocument *doc = dynamic_cast( parent() ); if ( doc ) { doc->forceCurrent( false ); } } bool KoDocument::isCurrent() const { return d->m_current; } bool KoDocument::storeInternal() const { return d->m_storeInternal; } void KoDocument::setStoreInternal( bool i ) { d->m_storeInternal = i; //kdDebug(30003)<m_storeInternal<<" doc: "<setWindow( d->m_shells.current() ); } } static const struct { const char* localName; const char* documentType; } TN2DTArray[] = { { "text", I18N_NOOP( "a word processing" ) }, { "spreadsheet", I18N_NOOP( "a spreadsheet" ) }, { "presentation", I18N_NOOP( "a presentation" ) }, { "chart", I18N_NOOP( "a chart" ) }, { "drawing", I18N_NOOP( "a drawing" ) } }; static const unsigned int numTN2DT = sizeof( TN2DTArray ) / sizeof( *TN2DTArray ); TQString KoDocument::tagNameToDocumentType( const TQString& localName ) { for ( unsigned int i = 0 ; i < numTN2DT ; ++i ) if ( localName == TN2DTArray[i].localName ) return i18n( TN2DTArray[i].documentType ); return localName; } TQValueList KoDocument::allTextDocuments() const { return TQValueList(); } KoPageLayout KoDocument::pageLayout(int /*pageNumber*/) const { return m_pageLayout; } KoUnit::Unit KoDocument::unit() const { return d->m_unit; } void KoDocument::setUnit( KoUnit::Unit unit ) { if ( d->m_unit != unit ) { d->m_unit = unit; emit unitChanged( unit ); } } TQString KoDocument::unitName() const { return KoUnit::unitName( unit() ); } void KoDocument::showStartUpWidget( KoMainWindow* parent, bool alwaysShow ) { if(!alwaysShow) { TDEConfigGroup cfgGrp( instance()->config(), "TemplateChooserDialog" ); TQString fullTemplateName = cfgGrp.readPathEntry( "AlwaysUseTemplate" ); if( !fullTemplateName.isEmpty() ) { openTemplate( fullTemplateName ); shells().getFirst()->setRootDocument( this ); return; } } if(d->m_startUpWidget){ d->m_startUpWidget->show(); } else { d->m_startUpWidget = createOpenPane( parent->centralWidget(), instance(), templateType() ); } parent->setDocToOpen( this ); parent->factory()->container("mainToolBar", parent)->hide(); } void KoDocument::openExistingFile( const TQString& file ) { KURL url( file ); bool ok = openURL( url ); setModified( false ); if( ok ) TQTimer::singleShot( 0, this, TQ_SLOT( deleteOpenPane() ) ); } void KoDocument::openTemplate( const TQString& file ) { bool ok = loadNativeFormat( file ); setModified( false ); if ( ok ) { deleteOpenPane(); resetURL(); setEmpty(); } else { showLoadingErrorDialog(); initEmpty(); } } void KoDocument::initEmpty() { setEmpty(); setModified(false); } void KoDocument::startCustomDocument() { deleteOpenPane(); } KoOpenPane* KoDocument::createOpenPane( TQWidget* parent, TDEInstance* instance, const TQString& templateType ) { KoOpenPane* openPane = new KoOpenPane( parent, instance, templateType ); TQWidget *customDoc = createCustomDocumentWidget(openPane); if(customDoc) { openPane->setCustomDocumentWidget( customDoc ); connect( customDoc, TQ_SIGNAL( documentSelected() ), this, TQ_SLOT( startCustomDocument() ) ); } openPane->show(); connect( openPane, TQ_SIGNAL( openExistingFile( const TQString& ) ), this, TQ_SLOT( openExistingFile( const TQString& ) ) ); connect( openPane, TQ_SIGNAL( openTemplate( const TQString& ) ), this, TQ_SLOT( openTemplate( const TQString& ) ) ); return openPane; } void KoDocument::setTemplateType( const TQString& _templateType ) { d->m_templateType = _templateType; } TQString KoDocument::templateType() const { return d->m_templateType; } void KoDocument::deleteOpenPane() { if( d->m_startUpWidget ) { d->m_startUpWidget->hide(); TQTimer::singleShot(1000, this, TQ_SLOT(deleteOpenPaneDelayed())); shells().getFirst()->factory()->container("mainToolBar", shells().getFirst())->show(); shells().getFirst()->setRootDocument( this ); } else { emit closeEmbedInitDialog(); } } void KoDocument::deleteOpenPaneDelayed() { delete d->m_startUpWidget; d->m_startUpWidget = 0; } TQWidget* KoDocument::createCustomDocumentWidget(TQWidget */*parent*/) { return 0; } bool KoDocument::showEmbedInitDialog(TQWidget* parent) { KDialogBase dlg(parent, "EmbedInitDialog", true, i18n("Embedding Object"), 0, KDialogBase::NoDefault); KoOpenPane* pane = createOpenPane(&dlg, instance(), templateType()); pane->layout()->setMargin(0); dlg.setMainWidget(pane); dlg.setInitialSize(dlg.configDialogSize("EmbedInitDialog")); connect(this, TQ_SIGNAL(closeEmbedInitDialog()), &dlg, TQ_SLOT(slotOk())); bool ok = dlg.exec() == TQDialog::Accepted; dlg.saveDialogSize("EmbedInitDialog"); return ok; } #include "KoDocument_p.moc" #include "KoDocument.moc"