/* 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.cc and koDocumentChild.cc
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 ( KGlobal::locale()->measureSystem() == KLocale::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.cc
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 )
{
KGlobal::locale()->insertCatalogue("koffice");
// Tell the iconloader about share/apps/koffice/icons
KGlobal::iconLoader()->addAppDir("koffice");
m_view = 0L;
// Avoid warning from KParts - we'll have the KoView as focus proxy anyway
setFocusPolicy( TQ_ClickFocus );
}
virtual ~KoViewWrapperWidget() {
setFocusProxy( 0 ); // to prevent a crash due to clearFocus (#53466)
}
virtual void resizeEvent( TQResizeEvent * )
{
TQObject *wid = child( 0, TQWIDGET_OBJECT_NAME_STRING );
if ( wid )
TQT_TQWIDGET(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, TQT_SIGNAL( timeout() ), this, TQT_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, TQT_SIGNAL( started( KIO::Job* ) ), TQT_SLOT( slotStarted( KIO::Job* ) ) );
}
KoDocument::~KoDocument()
{
d->m_autoSaveTimer.stop();
TQPtrListIterator childIt( d->m_children );
for (; childIt.current(); ++childIt )
disconnect( childIt.current(), TQT_SIGNAL( destroyed() ),
this, TQT_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.setAutoDelete( true );
d->m_shells.clear();
delete d->m_dcopObject;
delete d->filterManager;
delete d;
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 {
KIO::UDSEntry entry;
if ( KIO::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() );
KIO::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, TQT_SIGNAL( sigProgress( int ) ), shells().current(), TQT_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, TQT_SIGNAL( sigProgress( int ) ), shells().current(), TQT_SLOT( slotProgress( int ) ) );
if ( !ret )
emit sigStatusBarMessage( i18n("Error during autosave! Partition full?") );
}
}
KAction *KoDocument::action( const TQDomElement &element ) const
{
// First look in the document itself
KAction* 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, TQT_SIGNAL( changed( KoChild * ) ),
this, TQT_SLOT( slotChildChanged( KoChild * ) ) );
connect( child, TQT_SIGNAL( destroyed() ),
this, TQT_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( TQT_TQIODEVICE(&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::auto_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, Qt::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, Qt::AvoidDither | Qt::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 tqlayout, 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-KDE-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(Qt::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
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-KDE-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-KDE-NativeOasisMimeType" ).toString().latin1();
}
//static
KService::Ptr KoDocument::readNativeService( KInstance *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 ~/.kde/share/applnk or any subdir of it.
// If he created it under ~/.kde/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( KInstance *instance ) //static
{
KService::Ptr service = readNativeService( instance );
if ( !service )
return TQCString();
if ( service->property( "X-KDE-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-KDE-NativeMimeType entry!" << endl;
}
}
return service->property( "X-KDE-NativeMimeType" ).toString().latin1();
}
TQStringList KoDocument::readExtraNativeMimeTypes( KInstance *instance ) //static
{
KService::Ptr service = readNativeService( instance );
if ( !service )
return TQStringList();
return service->property( "X-KDE-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-KDE-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) {
KConfigGroup 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, TQT_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, KInstance* instance,
const TQString& templateType )
{
KoOpenPane* openPane = new KoOpenPane( parent, instance, templateType );
TQWidget *customDoc = createCustomDocumentWidget(openPane);
if(customDoc) {
openPane->setCustomDocumentWidget( customDoc );
connect( customDoc, TQT_SIGNAL( documentSelected() ), this, TQT_SLOT( startCustomDocument() ) );
}
openPane->show();
connect( openPane, TQT_SIGNAL( openExistingFile( const TQString& ) ),
this, TQT_SLOT( openExistingFile( const TQString& ) ) );
connect( openPane, TQT_SIGNAL( openTemplate( const TQString& ) ),
this, TQT_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, TQT_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->tqlayout()->setMargin(0);
dlg.setMainWidget(pane);
dlg.setInitialSize(dlg.configDialogSize("EmbedInitDialog"));
connect(this, TQT_SIGNAL(closeEmbedInitDialog()), &dlg, TQT_SLOT(slotOk()));
bool ok = dlg.exec() == TQDialog::Accepted;
dlg.saveDialogSize("EmbedInitDialog");
return ok;
}
#include "KoDocument_p.moc"
#include "KoDocument.moc"