diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 114a878c64ce6f8223cfd22d76a20eb16d177e5e (patch) | |
tree | acaf47eb0fa12142d3896416a69e74cbf5a72242 /src/partcontroller.cpp | |
download | tdevelop-114a878c64ce6f8223cfd22d76a20eb16d177e5e.tar.gz tdevelop-114a878c64ce6f8223cfd22d76a20eb16d177e5e.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdevelop@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/partcontroller.cpp')
-rw-r--r-- | src/partcontroller.cpp | 1867 |
1 files changed, 1867 insertions, 0 deletions
diff --git a/src/partcontroller.cpp b/src/partcontroller.cpp new file mode 100644 index 00000000..1fb15445 --- /dev/null +++ b/src/partcontroller.cpp @@ -0,0 +1,1867 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <qpopupmenu.h> +#include <qfile.h> +#include <qlayout.h> +#include <qmap.h> +#include <qlabel.h> +#include <qradiobutton.h> +#include <qcheckbox.h> +#include <qdom.h> + +#include <kmimetype.h> +#include <kservice.h> +#include <ktrader.h> +#include <kapplication.h> +#include <krun.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kparts/part.h> +#include <kparts/factory.h> +#include <kparts/partmanager.h> +#include <kparts/browserextension.h> +#include <kfiledialog.h> +#include <kmainwindow.h> +#include <kaction.h> +#include <kmessagebox.h> +#include <kstatusbar.h> +#include <khtml_part.h> +#include <kpopupmenu.h> +#include <kio/netaccess.h> +#include <kdialogbase.h> +#include <klineedit.h> +#include <kshortcut.h> +#include <kcompletion.h> +#include <kdirwatch.h> +#include <kdeversion.h> +#include <kiconloader.h> +#include <kuserprofile.h> +#include <kencodingfiledialog.h> +#include <ksqueezedtextlabel.h> + +#include <ktexteditor/view.h> +#include <ktexteditor/document.h> +#include <ktexteditor/viewcursorinterface.h> +#include <ktexteditor/encodinginterface.h> + +#include "toplevel.h" +#include "api.h" +#include "core.h" +#include "editorproxy.h" +#include "documentationpart.h" +#include "ksavealldialog.h" + +#include "kdevproject.h" +#include "urlutil.h" +#include "mimewarningdialog.h" +#include "domutil.h" +#include "kdevjobtimer.h" + +#include "designer.h" +#include "kdevlanguagesupport.h" + +#include "multibuffer.h" +#include "partcontroller.h" + + +class QDomDocument; + +PartController *PartController::s_instance = 0; + +using namespace MainWindowUtils; + +struct HistoryEntry +{ + KURL url; + QString context; + + HistoryEntry( const KURL& u, const QString& c ): url( u ), context( c ) {} +}; + +struct ModificationData +{ + KTextEditor::Document * doc; + bool isModified; + unsigned char reason; +}; + + +PartController::PartController(QWidget *parent) + : KDevPartController(parent), _editorFactory(0L), m_currentActivePart(0), m_removingActivePart(false) +{ + connect(this, SIGNAL(partRemoved(KParts::Part*)), this, SLOT(slotPartRemoved(KParts::Part* )) ); + connect(this, SIGNAL(partAdded(KParts::Part*)), this, SLOT(slotPartAdded(KParts::Part* )) ); + connect(this, SIGNAL(activePartChanged(KParts::Part*)), this, SLOT(slotActivePartChanged(KParts::Part*))); + + setupActions(); + + m_isJumping = false; + + m_openNextAsText = false; +} + + +PartController::~PartController() +{ +} + + +void PartController::createInstance(QWidget *parent) +{ + if (!s_instance) + s_instance = new PartController(parent); +} + + +PartController *PartController::getInstance() +{ + return s_instance; +} + + +void PartController::setupActions() +{ + KActionCollection *ac = TopLevel::getInstance()->main()->actionCollection(); + + KAction* newAction = KStdAction::open(this, SLOT(slotOpenFile()), ac, "file_open"); + newAction->setToolTip( i18n("Open file") ); + newAction->setWhatsThis( i18n("<b>Open file</b><p>Opens an existing file without adding it to the project.</p>") ); + + m_openRecentAction = KStdAction::openRecent( this, SLOT(slotOpenRecent(const KURL&) ), ac, "file_open_recent" ); + m_openRecentAction->setWhatsThis(QString("<b>%1</b><p>%2").arg(beautifyToolTip(m_openRecentAction->text())).arg(i18n("Opens recently opened file."))); + m_openRecentAction->loadEntries( kapp->config(), "RecentFiles" ); + + m_saveAllFilesAction = new KAction(i18n("Save Al&l"), 0, this, SLOT(slotSaveAllFiles()), ac, "file_save_all"); + m_saveAllFilesAction->setToolTip( i18n("Save all modified files") ); + m_saveAllFilesAction->setWhatsThis(i18n("<b>Save all</b><p>Saves all modified files.")); + m_saveAllFilesAction->setEnabled(false); + + m_revertAllFilesAction = new KAction(i18n("Rever&t All"), 0, this, SLOT(slotRevertAllFiles()), ac, "file_revert_all"); + m_revertAllFilesAction->setToolTip(i18n("Revert all changes")); + m_revertAllFilesAction->setWhatsThis(i18n("<b>Revert all</b><p>Reverts all changes in opened files. Prompts to save changes so the reversion can be canceled for each modified file.")); + m_revertAllFilesAction->setEnabled(false); + + m_closeWindowAction = KStdAction::close(this, SLOT(slotCloseWindow()), ac, "file_close"); + m_closeWindowAction->setToolTip( i18n("Close current file") ); + m_closeWindowAction->setWhatsThis(QString("<b>%1</b><p>%2").arg(beautifyToolTip(m_closeWindowAction->text())).arg(i18n("Closes current file."))); + m_closeWindowAction->setEnabled(false); + + m_closeAllWindowsAction = new KAction(i18n("Close All"), 0, this, SLOT(slotCloseAllWindows()), ac, "file_close_all"); + m_closeAllWindowsAction->setToolTip( i18n("Close all files") ); + m_closeAllWindowsAction->setWhatsThis(i18n("<b>Close all</b><p>Close all opened files.")); + m_closeAllWindowsAction->setEnabled(false); + + m_closeOtherWindowsAction = new KAction(i18n("Close All Others"), 0, this, SLOT(slotCloseOtherWindows()), ac, "file_closeother"); + m_closeOtherWindowsAction->setToolTip( i18n("Close other files") ); + m_closeOtherWindowsAction->setWhatsThis(i18n("<b>Close all others</b><p>Close all opened files except current.")); + m_closeOtherWindowsAction->setEnabled(false); + + new KActionSeparator(ac, "dummy_separator"); + + m_backAction = new KToolBarPopupAction(i18n("Back"), "back", 0, this, SLOT(slotBack()), ac, "history_back"); + m_backAction->setEnabled( false ); + m_backAction->setToolTip(i18n("Back")); + m_backAction->setWhatsThis(i18n("<b>Back</b><p>Moves backwards one step in the navigation history.")); + connect(m_backAction->popupMenu(), SIGNAL(aboutToShow()), this, SLOT(slotBackAboutToShow())); + connect(m_backAction->popupMenu(), SIGNAL(activated(int)), this, SLOT(slotBackPopupActivated(int))); + + m_forwardAction = new KToolBarPopupAction(i18n("Forward"), "forward", 0, this, SLOT(slotForward()), ac, "history_forward"); + m_forwardAction->setEnabled( false ); + m_forwardAction->setToolTip(i18n("Forward")); + m_forwardAction->setWhatsThis(i18n("<b>Forward</b><p>Moves forward one step in the navigation history.")); + connect(m_forwardAction->popupMenu(), SIGNAL(aboutToShow()), this, SLOT(slotForwardAboutToShow())); + connect(m_forwardAction->popupMenu(), SIGNAL(activated(int)), this, SLOT(slotForwardPopupActivated(int))); + + m_gotoLastEditPosAction = new KAction( i18n("Goto Last Edit Position"), "bottom", 0, this, SLOT(gotoLastEditPos()), ac, "goto_last_edit_pos" ); + m_gotoLastEditPosAction->setEnabled( false ); + m_gotoLastEditPosAction->setToolTip( i18n("Goto Last Edit Position") ); + m_gotoLastEditPosAction->setWhatsThis( i18n("<b>Goto Last Edit Position</b><p>Open the last edited file and position cursor at the point of edit") ); +} + +void PartController::setEncoding(const QString &encoding) +{ + m_presetEncoding = encoding; +} + +KParts::Part* PartController::findOpenDocument(const KURL& url) +{ + // if we find it this way, all is well + KParts::Part * part = partForURL( url ); + if ( part ) + { + return part; + } + + // ok, let's see if we can try harder + if ( API::getInstance()->project() ) + { + KURL partURL = findURLInProject( url ); + partURL.cleanPath(); + return partForURL( partURL ); + } + + return 0L; +} + +KURL PartController::findURLInProject(const KURL& url) +{ + QStringList fileList = API::getInstance()->project()->allFiles(); + + bool filenameOnly = (url.url().find('/') == -1); + QString filename = filenameOnly ? "/" : ""; + filename += url.url(); + + for (QStringList::Iterator it = fileList.begin(); it != fileList.end(); ++it) { + if ((*it).endsWith(filename)) { + // Match! The first one is as good as any one, I guess... + return KURL( API::getInstance()->project()->projectDirectory() + "/" + *it ); + } + } + + return url; +} + +void PartController::editDocument(const KURL &inputUrl, int lineNum, int col) +{ + editDocumentInternal(inputUrl, lineNum, col); +} + +void PartController::splitCurrentDocument(const KURL &inputUrl, + int lineNum, int col) +{ + editDocumentInternal(inputUrl, lineNum, col, true, true); +} + +void PartController::scrollToLineColumn(const KURL &inputUrl, + int lineNum, int col, bool storeHistory ) +{ + if ( KParts::ReadOnlyPart *existingPart = partForURL( inputUrl ) ) + { + if( storeHistory ) addHistoryEntry( existingPart ); + EditorProxy::getInstance()->setLineNumber( existingPart, lineNum, col ); + return; + } +} + +void PartController::editDocumentInternal( const KURL & inputUrl, int lineNum, + int col, bool activate, + bool addToCurrentBuffer ) +{ + kdDebug(9000) << k_funcinfo << "\n " << inputUrl.prettyURL() + << " linenum " << lineNum << " activate? " << activate + << " addToCurrentBuffer? " << addToCurrentBuffer << endl; + + KURL url = inputUrl; + + // is it already open? + // (Try this once before verifying the URL, we could be dealing with a file that no longer exists on disc) + if ( KParts::Part *existingPart = partForURL( url ) ) + { + // if we've been asked to OpenAs an open file with a specific encoding, assume the user wants to change encoding + if ( !m_presetEncoding.isNull() ) + { + if ( KTextEditor::EncodingInterface * ei = dynamic_cast<KTextEditor::EncodingInterface*>( existingPart ) ) + { + ei->setEncoding( m_presetEncoding ); + } + m_presetEncoding = QString::null; + } + + addHistoryEntry(); + activatePart( existingPart ); + EditorProxy::getInstance()->setLineNumber( existingPart, lineNum, col ); + return; + } + + // Make sure the URL exists + if ( !url.isValid() || !KIO::NetAccess::exists(url, false, 0) ) + { + bool done = false; + + // Try to find this file in the current project's list instead + if ( API::getInstance()->project() ) + { + if (url.isRelativeURL(url.url())) { + KURL relURL(API::getInstance()->project()->projectDirectory()); + relURL.addPath( url.url() ); + + kdDebug() << k_funcinfo << "Looking for file in project dir: " << API::getInstance()->project()->projectDirectory() << " url " << url.url() << " transformed to " << relURL.url() << ": " << done << endl; + if (relURL.isValid() && KIO::NetAccess::exists(relURL, false, 0)) { + url = relURL; + done = true; + } + else { + KURL relURL(API::getInstance()->project()->buildDirectory()); + relURL.addPath( url.url() ); + kdDebug() << k_funcinfo << "Looking for file in build dir: " << API::getInstance()->project()->buildDirectory() << " url " << url.url() << " transformed to " << relURL.url() << ": " << done << endl; + if (relURL.isValid() && KIO::NetAccess::exists(relURL, false, 0)) { + url = relURL; + done = true; + } + } + } + + if (!done) { + url = findURLInProject(url); + + if ( !url.isValid() || !KIO::NetAccess::exists(url, false, 0) ) + // See if this url is relative to the current project's directory + url = API::getInstance()->project()->projectDirectory() + "/" + url.path(); + + else + done = true; + } + } + + if ( !done && ( !url.isValid() || !KIO::NetAccess::exists(url, false, 0) )) + { + // Not found - prompt the user to find it? + kdDebug(9000) << "cannot find URL: " << url.url() << endl; + return; + } + } + + // We now have a url that exists ;) + + // clean it and resolve possible symlink + url.cleanPath(true); + if (url.isLocalFile()) + { + QString path = url.path(); + path = URLUtil::canonicalPath(path); + if ( !path.isEmpty() ) + url.setPath(path); + } + + // is it already open? + KParts::Part *existingPart = partForURL(url); + if (existingPart) + { + addHistoryEntry(); + activatePart(existingPart); + EditorProxy::getInstance()->setLineNumber(existingPart, lineNum, col); + return; + } + + if ( !addToCurrentBuffer ) + { + if ( KDevLanguageSupport *lang = + API::getInstance()->languageSupport() ) + { + // Let the language part override the addToCurrentBuffer flag + // if it decides to... + addToCurrentBuffer = lang->shouldSplitDocument( inputUrl ); + + if ( addToCurrentBuffer ) + { + kdDebug(9000) << + "languagePart() insists addToCurrentBuffer = true" << endl; + // Set activate = true, otherwise we have hard to fix + // multi-buffer delayed activation. + // I'll re-look at this later... + activate = true; + } + } + } + + KMimeType::Ptr MimeType = KMimeType::findByURL( url ); + + kdDebug(9000) << "mimeType = " << MimeType->name() << endl; + + // is the URL pointing to a directory? + if ( MimeType->is( "inode/directory" ) ) + { + return; + } + + if ( !m_presetEncoding.isNull() ) + { + m_openNextAsText = true; + } + + KConfig *config = kapp->config(); + config->setGroup("General Options"); + + // we don't trust KDE with designer files, let's handle it ourselves + if ( !m_openNextAsText && MimeType->is( "application/x-designer" ) ) + { + QString DesignerSetting = config->readEntry( "DesignerSetting", "ExternalDesigner" ); + QString designerExec = "designer"; + QStringList designerPluginPaths; + QDomDocument* dom = API::getInstance()->projectDom(); + if ( dom != 0 ) + { + // The global option specifies a fallback if the project + // has no setting or no project is open. However for Qt4 + // projects we want to use ExternalDesigner in any case. + if ( DomUtil::readIntEntry( *dom, "/kdevcppsupport/qt/version", 3 ) == 4 ) + { + designerPluginPaths = DomUtil::readListEntry(*dom, "/kdevcppsupport/qt/designerpluginpaths", "path" ); + DesignerSetting = "ExternalDesigner"; + } + + DesignerSetting = DomUtil::readEntry(*dom, "/kdevcppsupport/qt/designerintegration", DesignerSetting ); + designerExec = DomUtil::readEntry(*dom, "/kdevcppsupport/qt/designer", designerExec ); + } + if ( DesignerSetting == "ExternalKDevDesigner" ) + { + designerExec = "kdevdesigner"; + } + else if ( DesignerSetting == "EmbeddedKDevDesigner" ) + { + if ( KParts::ReadOnlyPart *designerPart = qtDesignerPart() ) + { + addHistoryEntry(); + activatePart(designerPart); + designerPart->openURL(url); + return; + } + else if ( KParts::Factory * KDevDesignerFactory = static_cast<KParts::Factory*>( KLibLoader::self()->factory( QFile::encodeName( "libkdevdesignerpart" ) ) ) ) + { + KParts::ReadWritePart * kdevpart = static_cast<KParts::ReadWritePart*>( KDevDesignerFactory->createPart( TopLevel::getInstance()->main(), 0, 0, 0, "KParts::ReadWritePart" ) ); + kdevpart->openURL( url ); + addHistoryEntry(); + integratePart( kdevpart, url ); + m_openRecentAction->addURL( url ); + m_openRecentAction->saveEntries( kapp->config(), "RecentFiles" ); + return; + } + } + + if( designerPluginPaths.isEmpty() ) + KRun::runCommand( designerExec+" "+url.pathOrURL() ); + else + KRun::runCommand( "QT_PLUGIN_PATH=\""+designerPluginPaths.join(":")+"\" "+designerExec+" "+url.pathOrURL() ); + + return; + } + + config->setGroup("General"); + + QStringList texttypeslist = config->readListEntry( "TextTypes" ); + if ( texttypeslist.contains( MimeType->name() ) ) + { + m_openNextAsText = true; + } + + bool isText = false; + QVariant v = MimeType->property("X-KDE-text"); + if (v.isValid()) + isText = v.toBool(); + // is this regular text - open in editor + if ( m_openNextAsText || isText || MimeType->is( "application/x-zerosize" ) ) + { + KTextEditor::Editor *editorpart = + createEditorPart( activate, addToCurrentBuffer, url ); + if ( editorpart ) + { + if ( m_presetEncoding.isNull() && API::getInstance()->projectDom() ) + { + QDomDocument * projectDom = API::getInstance()->projectDom(); + m_presetEncoding = DomUtil::readEntry( *projectDom, "/general/defaultencoding", QString::null ); + } + + if ( !m_presetEncoding.isNull() ) + { + if ( KTextEditor::EncodingInterface * ei = dynamic_cast<KTextEditor::EncodingInterface*>( editorpart ) ) + { + ei->setEncoding( m_presetEncoding ); + } + m_presetEncoding = QString::null; + } + + addHistoryEntry(); + + QWidget * widget = EditorProxy::getInstance()->topWidgetForPart( editorpart ); + + integratePart(editorpart, url, widget, true, activate, addToCurrentBuffer); + EditorProxy::getInstance()->setLineNumber(editorpart, lineNum, col); + + m_openNextAsText = false; + + m_openRecentAction->addURL( url ); + m_openRecentAction->saveEntries( kapp->config(), "RecentFiles" ); + + return; + } + } + + // OK, it's not text and it's not a designer file.. let's see what else we can come up with.. + + KParts::Factory *factory = 0; + QString className; + + QString services[] = { "KParts/ReadWritePart", "KParts/ReadOnlyPart" }; + QString classnames[] = { "KParts::ReadWritePart", "KParts::ReadOnlyPart" }; + for (uint i=0; i<2; ++i) + { + factory = findPartFactory( MimeType->name(), services[i] ); + if (factory) + { + className = classnames[i]; + break; + } + } + + kdDebug(9000) << "factory = " << factory << endl; + + if (factory) + { + // create the object of the desired class + KParts::ReadOnlyPart *part = static_cast<KParts::ReadOnlyPart*>( factory->createPart( TopLevel::getInstance()->main(), 0, 0, 0, className.latin1() ) ); + if ( part ) + { + part->openURL( url ); + addHistoryEntry(); + + if ( dynamic_cast<KTextEditor::Editor*>( part ) ) // we can have ended up with a texteditor, in which case need to treat it as such + { + integratePart(part, url, part->widget(), true, activate); + EditorProxy::getInstance()->setLineNumber(part, lineNum, col); + } + else + { + integratePart( part, url ); + } + + m_openRecentAction->addURL( url ); + m_openRecentAction->saveEntries( kapp->config(), "RecentFiles" ); + } + } + else + { + MimeWarningDialog dlg; + dlg.text2->setText( QString( "<qt><b>%1</b></qt>" ).arg(url.path())); + dlg.text3->setText( dlg.text3->text().arg(MimeType->name()) ); + + if ( dlg.exec() == QDialog::Accepted ) + { + if ( dlg.open_with_kde->isChecked() ) + { + KRun::runURL(url, MimeType->name() ); + } + else + { + if ( dlg.always_open_as_text->isChecked() ) + { + KConfig *config = kapp->config(); + config->setGroup("General"); + QStringList texttypeslist = config->readListEntry( "TextTypes" ); + texttypeslist << MimeType->name(); + config->writeEntry( "TextTypes", texttypeslist ); + } + m_openNextAsText = true; + editDocument( url, lineNum, col ); + } + } + } +} + + +void PartController::showDocument(const KURL &url, bool newWin) +{ + QString fixedPath = HTMLDocumentationPart::resolveEnvVarsInURL(url.url()); // possibly could env vars + KURL docUrl(fixedPath); + kdDebug(9000) << "SHOW: " << docUrl.url() << endl; + + if ( docUrl.isLocalFile() && KMimeType::findByURL(docUrl)->name() != "text/html" ) { + // a link in a html-file pointed to a local text file - display + // it in the editor instead of a html-view to avoid uglyness + editDocument( docUrl ); + return; + } + + addHistoryEntry(); + + HTMLDocumentationPart *part = dynamic_cast<HTMLDocumentationPart*>(activePart()); + if (!part || newWin) + { + part = new HTMLDocumentationPart; + integratePart(part,docUrl); + connect(part, SIGNAL(fileNameChanged(KParts::ReadOnlyPart* )), + this, SIGNAL(partURLChanged(KParts::ReadOnlyPart* ))); + } + else + { + activatePart(part); + } + part->openURL(docUrl); +} + +KParts::Factory *PartController::findPartFactory(const QString &mimeType, const QString &partType, const QString &preferredName) +{ + KTrader::OfferList offers = KTrader::self()->query(mimeType, QString("'%1' in ServiceTypes").arg(partType)); + + if (offers.count() > 0) + { + KService::Ptr ptr = 0; + // if there is a preferred plugin we'll take it + if ( !preferredName.isEmpty() ) { + KTrader::OfferList::Iterator it; + for (it = offers.begin(); it != offers.end(); ++it) { + if ((*it)->desktopEntryName() == preferredName) { + ptr = (*it); + } + } + } + // else we just take the first in the list + if ( !ptr ) { + ptr = offers.first(); + } + return static_cast<KParts::Factory*>(KLibLoader::self()->factory(QFile::encodeName(ptr->library()))); + } + + return 0; +} + +KTextEditor::Editor * PartController::createEditorPart( bool activate, + bool addToCurrentBuffer, + const KURL &url ) +{ + MultiBuffer *multiBuffer = 0; + if ( addToCurrentBuffer ) + { + multiBuffer = + dynamic_cast<MultiBuffer*>( + EditorProxy::getInstance()->topWidgetForPart( activePart() ) + ); + } + if ( !multiBuffer ) + { + kdDebug(9000) << "Creating a new MultiBuffer for " + << url.fileName() << endl; + multiBuffer = new MultiBuffer( TopLevel::getInstance()->main() ); + } + + static bool alwaysActivate = true; + + kapp->config()->setGroup("Editor"); + QString preferred = kapp->config()->readPathEntry("EmbeddedKTextEditor"); + // If we are not using kyzis... + // Don't create non-wrapped views for now, + // avoid two paths (== two chances for bad bugs) + if ( preferred != "kyzispart" ) + alwaysActivate = false; + + KTextEditor::Editor *editorpart = + dynamic_cast<KTextEditor::Editor*>(multiBuffer->createPart( "text/plain", + "KTextEditor/Document", + alwaysActivate | activate ? + "KTextEditor::Editor" : "KTextEditor::Document", + preferred + )); + + if ( url.isValid() ) + editorpart->openURL( url ); + + multiBuffer->registerURL( url, editorpart ); + multiBuffer->setDelayedActivation( !activate ); + return editorpart; +} + +void PartController::integratePart(KParts::Part *part, const KURL &url, + QWidget* widget, bool isTextEditor, + bool activate, bool addToCurrentBuffer ) +{ + if (!widget) widget = part->widget(); + + if (!widget) { + /// @todo error handling + kdDebug(9000) << "no widget for this part!!" << endl; + return; // to avoid later crash + } + + if ( !addToCurrentBuffer ) + TopLevel::getInstance()->embedPartView(widget, url.fileName(), url.url()); + + addPart(part, activate); + + // tell the parts we loaded a document + KParts::ReadOnlyPart *ro_part = dynamic_cast<KParts::ReadOnlyPart*>(part); + if ( !ro_part ) return; + + emit loadedFile( ro_part->url() ); + + connect( part, SIGNAL(modifiedOnDisc(Kate::Document*, bool, unsigned char)), this, SLOT(slotDocumentDirty(Kate::Document*, bool, unsigned char)) ); + + // let's get notified when a document has been changed + connect(part, SIGNAL(completed()), this, SLOT(slotUploadFinished())); + + // yes, we're cheating again. this signal exists for katepart's + // Document object and our HTMLDocumentationPart +// connect(part, SIGNAL(fileNameChanged()), this, SLOT(slotFileNameChanged())); + + // Connect to the document's views newStatus() signal in order to keep track of the + // modified-status of the document. + + if (isTextEditor) + integrateTextEditorPart(static_cast<KTextEditor::Document*>(part)); + + KInterfaceDesigner::Designer *designerPart = dynamic_cast<KInterfaceDesigner::Designer *>(part); + if (designerPart && API::getInstance()->languageSupport()) + { + kdDebug() << "integrating designer part with language support" << endl; + connect(designerPart, SIGNAL(addedFunction(DesignerType, const QString&, Function )), + API::getInstance()->languageSupport(), + SLOT(addFunction(DesignerType, const QString&, Function ))); + connect(designerPart, SIGNAL(editedFunction(DesignerType, const QString&, Function, Function )), API::getInstance()->languageSupport(), + SLOT(editFunction(DesignerType, const QString&, Function, Function ))); + connect(designerPart, SIGNAL(removedFunction(DesignerType, const QString&, Function )), + API::getInstance()->languageSupport(), + SLOT(removeFunction(DesignerType, const QString&, Function ))); + connect(designerPart, SIGNAL(editFunction(DesignerType, const QString&, const QString& )), + API::getInstance()->languageSupport(), + SLOT(openFunction(DesignerType, const QString&, const QString& ))); + connect(designerPart, SIGNAL(editSource(DesignerType, const QString& )), + API::getInstance()->languageSupport(), + SLOT(openSource(DesignerType, const QString& ))); + connect(designerPart, SIGNAL(newStatus(const QString &, int)), + this, SLOT(slotNewDesignerStatus(const QString &, int))); + } +} + +void PartController::integrateTextEditorPart(KTextEditor::Document* doc) +{ + // There's shortcut conflict between Kate and the debugger, resolve + // it here. Ideally, the should be some standard mechanism, configurable + // by config files. + // However, it does not exists and situation here some rare commands + // like "Dynamic word wrap" or "Show line numbers" from Kate take + // all possible shortcuts, leaving us with IDE that has no shortcuts for + // debugger, is very bad. + // + // We could try to handle this in debugger, but that would require + // the debugger to intercept all new KTextEditor::View creations, which is + // not possible. + + + if ( !doc ) return; + + connect( doc, SIGNAL(textChanged()), this, SLOT(textChanged()) ); + connect( doc, SIGNAL(fileNameChanged()), + this, SLOT(slotDocumentUrlChanged())); + + if( doc->widget() ) + { + connect( doc->widget(), SIGNAL(dropEventPass(QDropEvent *)), + TopLevel::getInstance()->main(), SLOT(slotDropEvent(QDropEvent *)) ); + } + + if ( KTextEditor::View * view = dynamic_cast<KTextEditor::View*>( doc->widget() ) ) + { + KActionCollection* c = view->actionCollection(); + // Be extra carefull, in case the part either don't provide those + // action, or uses different shortcuts. + + if (KAction* a = c->action("view_folding_markers")) + { + if (a->shortcut() == KShortcut(Key_F9)) + a->setShortcut(KShortcut()); + } + + if (KAction* a = c->action("view_dynamic_word_wrap")) + { + if (a->shortcut() == KShortcut(Key_F10)) + a->setShortcut(KShortcut()); + } + + if (KAction* a = c->action("view_line_numbers")) + { + if (a->shortcut() == KShortcut(Key_F11)) + a->setShortcut(KShortcut()); + } + } + + //EditorProxy::getInstance()->installPopup(doc, contextPopupMenu()); + + // What's potentially problematic is that this signal isn't officially part of the + // KTextEditor::View interface. It is nevertheless there, and used in kate and kwrite. + // There doesn't seem to be any othere way of making this work with katepart, and since + // signals are dynamic, if we try to connect to an editorpart that lacks this signal, + // all we get is a runtime warning. At this point in time we are only really supported + // by katepart anyway so IMHO this hack is justified. //teatime + QPtrList<KTextEditor::View> list = doc->views(); + QPtrListIterator<KTextEditor::View> it( list ); + while ( it.current() ) + { + connect( it, SIGNAL( newStatus() ), this, SLOT( slotNewStatus() ) ); + ++it; + } +} + +void PartController::slotPartAdded( KParts::Part * part ) +{ + kdDebug(9000) << k_funcinfo << endl; + + if ( KParts::ReadOnlyPart * ro_part = dynamic_cast<KParts::ReadOnlyPart*>( part ) ) + { + updatePartURL( ro_part ); + } + + updateMenuItems(); +} + +void PartController::slotPartRemoved( KParts::Part * part ) +{ + kdDebug(9000) << k_funcinfo << endl; + + _partURLMap.remove( static_cast<KParts::ReadOnlyPart*>(part) ); + + if ( part == m_currentActivePart ) + { + m_removingActivePart = true; + } + + updateMenuItems(); +} + +void PartController::updatePartURL( KParts::ReadOnlyPart * ro_part ) +{ + if ( ro_part->url().isEmpty() ) + { + kdDebug(9000) << "updatePartURL() called with empty URL for part: " << ro_part << endl; + return; + } + _partURLMap[ ro_part ] = ro_part->url(); +} + +bool PartController::partURLHasChanged( KParts::ReadOnlyPart * ro_part ) +{ + if ( _partURLMap.contains( ro_part ) && !ro_part->url().isEmpty() ) + { + if ( _partURLMap[ ro_part ] != ro_part->url() ) + { + return true; + } + } + return false; +} + +KURL PartController::storedURLForPart( KParts::ReadOnlyPart * ro_part ) +{ + if ( _partURLMap.contains( ro_part ) ) + { + return _partURLMap[ ro_part ]; + } + return KURL(); +} + + +void PartController::slotUploadFinished() +{ + KParts::ReadOnlyPart *ro_part = const_cast<KParts::ReadOnlyPart*>( dynamic_cast<const KParts::ReadOnlyPart*>(sender()) ); + if ( !ro_part ) return; + + if ( partURLHasChanged( ro_part ) ) + { + emit partURLChanged( ro_part ); + updatePartURL( ro_part ); + } +} + +KParts::ReadOnlyPart *PartController::partForURL(const KURL &url) +{ + QPtrListIterator<KParts::Part> it(*parts()); + for ( ; it.current(); ++it) + { + KParts::ReadOnlyPart *ro_part = dynamic_cast<KParts::ReadOnlyPart*>(it.current()); + if (ro_part && url.path() == ro_part->url().path()) + return ro_part; + } + return 0; +} + +KParts::Part * PartController::partForWidget( const QWidget * widget ) +{ + QPtrListIterator<KParts::Part> it(*parts()); + for ( ; it.current(); ++it) + { + if ( it.current()->widget() == widget ) + { + return *it; + } + } + return 0; +} + + +void PartController::activatePart(KParts::Part *part) +{ + if ( !part ) return; + + QWidget * widget = EditorProxy::getInstance()->widgetForPart( part ); + if (widget) + { + TopLevel::getInstance()->raiseView( widget ); + widget->show(); + widget->setFocus(); + } + + setActivePart(part); + + QWidget* w2 = EditorProxy::getInstance()->widgetForPart( part ); + if (w2 != widget) + w2->setFocus(); +} + +bool PartController::closePart(KParts::Part *part) +{ + KParts::ReadOnlyPart * ro_part = + dynamic_cast<KParts::ReadOnlyPart*>( part ); + + if ( !ro_part ) return true; + + KURL url = ro_part->url(); + + if (QWidget* w = EditorProxy::getInstance()->topWidgetForPart( part ) ) + { + if ( MultiBuffer *multiBuffer = dynamic_cast<MultiBuffer*>( w ) ) + { + kdDebug(9000) << "About to delete MultiBuffered document..." + << " numberOfBuffers: " << multiBuffer->numberOfBuffers() + << " isActivated: " << multiBuffer->isActivated() + << endl; + if ( !multiBuffer->closeURL( url ) ) + return false; + + if ( multiBuffer->numberOfBuffers() == 0 + || !multiBuffer->isActivated() ) + { + TopLevel::getInstance()->removeView( w ); + _dirtyDocuments.remove( static_cast<KParts::ReadWritePart*>( ro_part ) ); + emit closedFile( url ); +/* kdDebug(9000) << "Deleting MultiBuffer Part" << endl;*/ + TopLevel::getInstance()->main()->guiFactory()->removeClient( part ); + delete part; +/* kdDebug(9000) << "DeleteLater Actual MultiBuffer" << endl;*/ + multiBuffer->deleteLater(); + return true; + } + else + { +/* kdDebug(9000) << "Deleting MultiBuffer Part" << endl;*/ + _dirtyDocuments.remove( static_cast<KParts::ReadWritePart*>( ro_part ) ); + TopLevel::getInstance()->main()->guiFactory()->removeClient( part ); + emit closedFile( url ); + delete part; + // Switch to a remaining buffer + setActivePart( multiBuffer->activeBuffer() ); + return true; + } + } + else if ( !ro_part->closeURL() ) + return false; + TopLevel::getInstance()->removeView( w ); + } + else if ( !ro_part->closeURL() ) + return false; + TopLevel::getInstance()->main()->guiFactory()->removeClient( part ); + + _dirtyDocuments.remove( static_cast<KParts::ReadWritePart*>( ro_part ) ); + emit closedFile( url ); + +/* kdDebug(9000) << "Deleting Regular Part" << endl;*/ + delete part; + return true; +} + + +void PartController::updateMenuItems() +{ + bool hasWriteParts = false; + bool hasReadOnlyParts = false; + + QPtrListIterator<KParts::Part> it(*parts()); + for ( ; it.current(); ++it) + { + if (it.current()->inherits("KParts::ReadWritePart")) + hasWriteParts = true; + if (it.current()->inherits("KParts::ReadOnlyPart")) + hasReadOnlyParts = true; + } + + m_saveAllFilesAction->setEnabled(hasWriteParts); + m_revertAllFilesAction->setEnabled(hasWriteParts); + m_closeWindowAction->setEnabled(hasReadOnlyParts); + m_closeAllWindowsAction->setEnabled(hasReadOnlyParts); + m_closeOtherWindowsAction->setEnabled(hasReadOnlyParts); + + m_backAction->setEnabled( !m_backHistory.isEmpty() ); +} + +void PartController::slotRevertAllFiles() +{ + revertAllFiles(); +} + +void PartController::reloadFile( const KURL & url ) +{ + KParts::ReadWritePart * part = dynamic_cast<KParts::ReadWritePart*>( partForURL( url ) ); + if ( part ) + { + if ( part->isModified() ) + { + if ( KMessageBox::warningYesNo( TopLevel::getInstance()->main(), + i18n( "The file \"%1\" is modified in memory. Are you sure you want to reload it? (Local changes will be lost.)" ).arg( url.path() ), + i18n( "File is Modified" ), i18n("Reload"), i18n("Do Not Reload") ) == KMessageBox::Yes ) + { + part->setModified( false ); + } + else + { + return; + } + } + + unsigned int line = 0; unsigned int col = 0; + KTextEditor::ViewCursorInterface * iface = dynamic_cast<KTextEditor::ViewCursorInterface*>( part->widget() ); + if (iface) + { + iface->cursorPositionReal( &line, &col ); + } + + part->openURL( url ); + + _dirtyDocuments.remove( part ); + emit documentChangedState( url, Clean ); + + if ( iface ) + { + iface->setCursorPositionReal( line, col ); + } + } +} + +void PartController::revertFiles( const KURL::List & list ) +{ + KURL::List::ConstIterator it = list.begin(); + while ( it != list.end() ) + { + reloadFile( *it ); + ++it; + } +} + +void PartController::revertAllFiles() +{ + revertFiles( openURLs() ); +} + +void PartController::slotCloseWindow() +{ + closePart( activePart() ); +} + +KURL::List PartController::modifiedDocuments() +{ + KURL::List modFiles; + + QPtrListIterator<KParts::Part> it( *parts() ); + while( it.current() ) + { + KParts::ReadWritePart *rw_part = dynamic_cast<KParts::ReadWritePart*>(it.current()); + if ( rw_part && rw_part->isModified() ) + { + modFiles << rw_part->url(); + } + ++it; + } + return modFiles; +} + +void PartController::slotSave() +{ + kdDebug(9000) << k_funcinfo << endl; + + if ( KParts::ReadWritePart * part = dynamic_cast<KParts::ReadWritePart*>( activePart() ) ) + { + saveFile( part->url() ); + } +} + +void PartController::slotReload() +{ + kdDebug(9000) << k_funcinfo << endl; + + if ( KParts::ReadWritePart * part = dynamic_cast<KParts::ReadWritePart*>( activePart() ) ) + { + reloadFile( part->url() ); + } +} + +void PartController::slotSaveAllFiles() +{ + saveAllFiles(); +} + +bool PartController::saveFile( const KURL & url, bool force ) +{ + KParts::ReadWritePart * part = dynamic_cast<KParts::ReadWritePart*>( partForURL( url ) ); + if ( !part ) return true; + + switch( documentState( url ) ) + { + case Clean: + if ( !force ) + { + return true; + } + kdDebug(9000) << "Forced save" << endl; + break; + + case Modified: + kdDebug(9000) << "Normal save" << endl; + break; + + case Dirty: + case DirtyAndModified: + { + int code = KMessageBox::warningYesNoCancel( TopLevel::getInstance()->main(), + i18n("The file \"%1\" is modified on disk.\n\nAre you sure you want to overwrite it? (External changes will be lost.)").arg( url.path() ), + i18n("File Externally Modified"), i18n("Overwrite"), i18n("Do Not Overwrite") ); + if ( code == KMessageBox::Yes ) + { + kdDebug(9000) << "Dirty save!!" << endl; + } + else if ( code == KMessageBox::No ) + { + return true; + } + else + { + return false; // a 'false' return means to interrupt the process that caused the save + } + } + break; + + default: + ; + } + + if ( part->save() ) + { + _dirtyDocuments.remove( part ); + emit documentChangedState( url, Clean ); + emit savedFile( url ); + } + + return true; +} + +bool PartController::saveAllFiles() +{ + return saveFiles( openURLs() ); +} + +bool PartController::saveFiles( KURL::List const & filelist ) +{ + KURL::List::ConstIterator it = filelist.begin(); + while ( it != filelist.end() ) + { + if (saveFile( *it )==false) + return false; //user cancelled + ++it; + } + return true; +} + +bool PartController::querySaveFiles() +{ + return saveFilesDialog( KURL::List() ); +} + +void PartController::clearModified( KURL::List const & filelist ) +{ + KURL::List::ConstIterator it = filelist.begin(); + while ( it != filelist.end() ) + { + KParts::ReadWritePart * rw_part = dynamic_cast<KParts::ReadWritePart*>( partForURL( *it ) ); + if ( rw_part ) + { + rw_part->setModified( false ); + } + ++it; + } +} + +bool PartController::saveFilesDialog( KURL::List const & ignoreList ) +{ + KURL::List modList = modifiedDocuments(); + + if ( modList.count() > 0 && modList != ignoreList ) + { + KSaveSelectDialog dlg( modList, ignoreList, TopLevel::getInstance()->main() ); + if ( dlg.exec() == QDialog::Accepted ) + { + saveFiles( dlg.filesToSave() ); + clearModified( dlg.filesNotToSave() ); + } + else + { + return false; + } + } + return true; +} + +bool PartController::closeFilesDialog( KURL::List const & ignoreList ) +{ + if ( !saveFilesDialog( ignoreList ) ) return false; + + QPtrList<KParts::Part> partList( *parts() ); + QPtrListIterator<KParts::Part> it( partList ); + while ( KParts::Part* part = it.current() ) + { + KParts::ReadOnlyPart * ro_part = dynamic_cast<KParts::ReadOnlyPart*>( part ); + if ( ro_part && !ignoreList.contains( ro_part->url() ) || !ro_part ) + { + closePart( part ); + } + ++it; + } + return true; +} + +bool PartController::closeFiles( const KURL::List & list ) +{ + KURL::List::ConstIterator it = list.begin(); + while ( it != list.end() ) + { + if ( !closePart( partForURL( *it ) ) ) + { + return false; + } + ++it; + } + return true; +} + +bool PartController::closeFile( const KURL & url ) +{ + return closePart( partForURL( url ) ); +} + +bool PartController::closeAllFiles() +{ + return closeFilesDialog( KURL::List() ); +} + +void PartController::slotCloseAllWindows() +{ + closeAllFiles(); +} + +bool PartController::closeAllOthers( const KURL & url ) +{ + KURL::List ignoreList; + ignoreList.append( url ); + + return closeFilesDialog( ignoreList ); +} + + +void PartController::slotCloseOtherWindows() +{ + if ( KParts::ReadOnlyPart * active = dynamic_cast<KParts::ReadOnlyPart*>( activePart() ) ) + { + closeAllOthers( active->url() ); + } +} + +void PartController::slotOpenFile() +{ + QString DefaultEncoding; + if ( QDomDocument * projectDom = API::getInstance()->projectDom() ) + { + DefaultEncoding = DomUtil::readEntry( *projectDom, "/general/defaultencoding", QString::null ); + } + + if ( DefaultEncoding.isEmpty() ) + { + // have a peek at katepart's settings: + KConfig * config = kapp->config(); + config->setGroup("Kate Document Defaults"); + DefaultEncoding = config->readEntry("Encoding", QString::null ); + } + + KEncodingFileDialog::Result result = KEncodingFileDialog::getOpenURLsAndEncoding( DefaultEncoding, QString::null, + QString::null, TopLevel::getInstance()->main(), QString::null ); + + for ( KURL::List::Iterator it = result.URLs.begin(); it != result.URLs.end(); ++it ) + { + m_presetEncoding = result.encoding; + editDocument( *it ); + } +} + +void PartController::slotOpenRecent( const KURL& url ) +{ + editDocument( url ); + // stupid bugfix - don't allow an active item in the list + m_openRecentAction->setCurrentItem( -1 ); +} + +bool PartController::readyToClose() +{ + blockSignals( true ); + + closeAllFiles(); // this should never return false, as the files are already saved + + return true; +} + +void PartController::slotActivePartChanged( KParts::Part *part ) +{ + kdDebug(9000) << k_funcinfo << part << endl; + + if ( !m_isJumping && !m_removingActivePart ) + { + if ( KParts::ReadOnlyPart * ro_part = dynamic_cast<KParts::ReadOnlyPart*>(m_currentActivePart) ) + { + addHistoryEntry( ro_part ); + } + } + + m_currentActivePart = part; + m_removingActivePart = false; + + if (part) { + KXMLGUIClient* client = dynamic_cast<KXMLGUIClient*>(part->widget()); + if (client) Core::setupShourtcutTips(client); + } + + updateMenuItems(); + QTimer::singleShot( 100, this, SLOT(slotWaitForFactoryHack()) ); +} + +void PartController::showPart( KParts::Part* part, const QString& name, const QString& shortDescription ) +{ + if (!part->widget()) { + /// @todo error handling + return; // to avoid later crash + } + + QPtrListIterator<KParts::Part> it(*parts()); + for ( ; it.current(); ++it) + { + if( it.current() == part ){ + // part already embedded + activatePart( it.current() ); + return; + } + } + + // embed the part + TopLevel::getInstance()->embedPartView( part->widget(), name, shortDescription ); + addPart( part ); +} + +void PartController::slotDocumentDirty( Kate::Document * d, bool isModified, unsigned char reason ) +{ + kdDebug(9000) << k_funcinfo << endl; + + // KTextEditor::Document * doc = reinterpret_cast<KTextEditor::Document*>( d ); // theoretically unsafe in MI scenario + KTextEditor::Document * doc = 0; + + QPtrListIterator<KParts::Part> it( *parts() ); + while( it.current() ) + { + if ( (void*)it.current() == (void*)d ) + { + doc = dynamic_cast<KTextEditor::Document*>( it.current() ); + break; + } + ++it; + } + + // this is a bit strange, but in order to avoid weird crashes + // down in KDirWatcher, we want to step off the call chain before + // opening any messageboxes + if ( doc ) + { + ModificationData * p = new ModificationData; + p->doc = doc; + p->isModified = isModified; + p->reason = reason; + KDevJobTimer::singleShot( 0, this, SLOT(slotDocumentDirtyStepTwo(void*)), p ); + } +} + +void PartController::slotDocumentDirtyStepTwo( void * payload ) +{ + if ( !payload ) return; + + ModificationData * p = reinterpret_cast<ModificationData*>( payload ); + KTextEditor::Document * doc = p->doc; + + // let's make sure the document is still loaded + bool haveDocument = false; + if( const QPtrList<KParts::Part> * partlist = parts() ) + { + QPtrListIterator<KParts::Part> it( *partlist ); + while ( KParts::Part * part = it.current() ) + { + if ( p->doc == dynamic_cast<KTextEditor::Document*>( part ) ) + { + haveDocument = true; + break; + } + ++it; + } + } + if ( !haveDocument ) return; + + bool isModified = p->isModified; + unsigned char reason = p->reason; + delete p; + + KURL url = storedURLForPart( doc ); + if ( url.isEmpty() ) + { + kdDebug(9000) << "Warning!! the stored url is empty. Bailing out!" << endl; + } + + if ( reason > 0 ) + { + if ( !_dirtyDocuments.contains( doc ) ) + { + _dirtyDocuments.append( doc ); + } + + if ( reactToDirty( url, reason ) ) + { + // file has been reloaded + emit documentChangedState( url, Clean ); + _dirtyDocuments.remove( doc ); + } + else + { + doEmitState( url ); + } + } + else + { + _dirtyDocuments.remove( doc ); + emit documentChangedState( url, Clean ); + } + + kdDebug(9000) << doc->url().url() << endl; + kdDebug(9000) << isModified << endl; + kdDebug(9000) << reason << endl; +} + +bool PartController::isDirty( KURL const & url ) +{ + return _dirtyDocuments.contains( static_cast<KTextEditor::Document*>( partForURL( url ) ) ); +} + +bool PartController::reactToDirty( KURL const & url, unsigned char reason ) +{ + KConfig *config = kapp->config(); + config->setGroup("Editor"); + QString dirtyAction = config->readEntry( "DirtyAction" ); + + if ( dirtyAction == "nothing" ) return false; + + bool isModified = true; + if( KParts::ReadWritePart * part = dynamic_cast<KParts::ReadWritePart*>( partForURL( url ) ) ) + { + isModified = part->isModified(); + } + else + { + kdDebug(9000) << k_funcinfo << " Warning. Not a ReadWritePart." << endl; + return false; + } + + if ( isModified ) + { + KMessageBox::sorry( TopLevel::getInstance()->main(), + i18n("Conflict: The file \"%1\" has changed on disk while being modified in memory.\n\n" + "You should investigate before saving to make sure you are not losing data.").arg( url.path() ), + i18n("Conflict") ); + return false; + } + + if ( reason == 3 ) // means the file was deleted + { + KMessageBox::sorry( TopLevel::getInstance()->main(), + i18n("Warning: The file \"%1\" has been deleted on disk.\n\n" + "If this was not your intention, make sure to save this file now.").arg( url.path() ), + i18n("File Deleted") ); + return false; + } + + if ( dirtyAction == "alert" ) + { + if ( KMessageBox::warningYesNo( TopLevel::getInstance()->main(), + i18n("The file \"%1\" has changed on disk.\n\nDo you want to reload it?").arg( url.path() ), + i18n("File Changed"), i18n("Reload"), i18n("Do Not Reload") ) == KMessageBox::No ) + { + return false; + } + } + + // here we either answered yes above or are in autoreload mode + reloadFile( url ); + + return true; +} + +void PartController::slotNewDesignerStatus(const QString &formName, int status) +{ + kdDebug(9000) << k_funcinfo << endl; + kdDebug(9000) << " formName: " << formName << ", status: " << status << endl; + emit documentChangedState( KURL::fromPathOrURL(formName), DocumentState(status) ); +} + +void PartController::slotNewStatus( ) +{ + kdDebug(9000) << k_funcinfo << endl; + + QObject * senderobj = const_cast<QObject*>( sender() ); + KTextEditor::View * view = dynamic_cast<KTextEditor::View*>( senderobj ); + if ( view ) + { + doEmitState( view->document()->url() ); + } +} + +DocumentState PartController::documentState( KURL const & url ) +{ + KParts::ReadWritePart * rw_part = dynamic_cast<KParts::ReadWritePart*>( partForURL( url ) ); + if ( !rw_part ) return Clean; + + DocumentState state = Clean; + if ( rw_part->isModified() ) + { + state = Modified; + } + + if ( isDirty( url ) ) + { + if ( state == Modified ) + { + state = DirtyAndModified; + } + else + { + state = Dirty; + } + } + + return state; +} + +void PartController::doEmitState( KURL const & url ) +{ + emit documentChangedState( url, documentState( url ) ); +} + +KURL::List PartController::openURLs( ) +{ + KURL::List list; + QPtrListIterator<KParts::Part> it(*parts()); + for ( ; it.current(); ++it) + { + if ( KParts::ReadOnlyPart *ro_part = dynamic_cast<KParts::ReadOnlyPart*>(it.current()) ) + { + list << ro_part->url(); + } + } + return list; +} + +///////////////////////////////////////////////////////////////////////////// + +//BEGIN History methods + +PartController::HistoryEntry::HistoryEntry( const KURL & u, int l, int c) + : url(u), line(l), col(c) +{ + id = abs( QTime::currentTime().msecsTo( QTime() ) ); // should provide a reasonably unique number +} + +void PartController::slotBack() +{ + HistoryEntry thatEntry = m_backHistory.front(); + m_backHistory.pop_front(); + m_backAction->setEnabled( !m_backHistory.isEmpty() ); + + HistoryEntry thisEntry = createHistoryEntry(); + if ( !thisEntry.url.isEmpty() ) + { + m_forwardHistory.push_front( thisEntry ); + m_forwardAction->setEnabled( true ); + } + + jumpTo( thatEntry ); +} + +void PartController::slotForward() +{ + HistoryEntry thatEntry = m_forwardHistory.front(); + m_forwardHistory.pop_front(); + m_forwardAction->setEnabled( !m_forwardHistory.isEmpty() ); + + HistoryEntry thisEntry = createHistoryEntry(); + if ( !thisEntry.url.isEmpty() ) + { + m_backHistory.push_front( thisEntry ); + m_backAction->setEnabled( true ); + } + + jumpTo( thatEntry ); +} + +void PartController::slotBackAboutToShow() +{ + KPopupMenu *popup = m_backAction->popupMenu(); + popup->clear(); + + if ( m_backHistory.isEmpty()) return; + + int i = 0; + QValueList<HistoryEntry>::ConstIterator it = m_backHistory.begin(); + while( i < 10 && it != m_backHistory.end() ) + { + popup->insertItem( (*it).url.fileName() + QString(" (%1)").arg( (*it).line +1), (*it).id ); + ++i; + ++it; + } +} + +void PartController::slotForwardAboutToShow( ) +{ + KPopupMenu * popup = m_forwardAction->popupMenu(); + popup->clear(); + + if ( m_forwardHistory.isEmpty() ) return; + + int i = 0; + QValueList<HistoryEntry>::ConstIterator it = m_forwardHistory.begin(); + while( i < 10 && it != m_forwardHistory.end() ) + { + popup->insertItem( (*it).url.fileName() + QString(" (%1)").arg( (*it).line +1), (*it).id ); + ++i; + ++it; + } +} + +void PartController::slotBackPopupActivated( int id ) +{ + QValueList<HistoryEntry>::Iterator it = m_backHistory.begin(); + while( it != m_backHistory.end() ) + { + if ( (*it).id == id ) + { + HistoryEntry entry = *it; + m_backHistory.erase( m_backHistory.begin(), ++it ); + m_backAction->setEnabled( !m_backHistory.isEmpty() ); + + HistoryEntry thisEntry = createHistoryEntry(); + if ( !thisEntry.url.isEmpty() ) + { + m_forwardHistory.push_front( thisEntry ); + m_forwardAction->setEnabled( true ); + } + + jumpTo( entry ); + return; + } + ++it; + } +} + +void PartController::slotForwardPopupActivated( int id ) +{ + QValueList<HistoryEntry>::Iterator it = m_forwardHistory.begin(); + while( it != m_forwardHistory.end() ) + { + if ( (*it).id == id ) + { + HistoryEntry entry = *it; + m_forwardHistory.erase( m_forwardHistory.begin(), ++it ); + m_forwardAction->setEnabled( !m_forwardHistory.isEmpty() ); + + HistoryEntry thisEntry = createHistoryEntry(); + if ( !thisEntry.url.isEmpty() ) + { + m_backHistory.push_front( thisEntry ); + m_backAction->setEnabled( true ); + } + + jumpTo( entry ); + return; + } + ++it; + } +} + +void PartController::jumpTo( const HistoryEntry & entry ) +{ + m_isJumping = true; + editDocument( entry.url, entry.line, entry.col ); + m_isJumping = false; +} + + +PartController::HistoryEntry PartController::createHistoryEntry( KParts::ReadOnlyPart * ro_part ) { + if( ro_part == 0 ) + ro_part = dynamic_cast<KParts::ReadOnlyPart*>( activePart() ); + + if ( !ro_part ) return HistoryEntry(); + KTextEditor::ViewCursorInterface * cursorIface = dynamic_cast<KTextEditor::ViewCursorInterface*>( ro_part->widget() ); + if ( !cursorIface ) return HistoryEntry(); + + uint line = 0; + uint col = 0; + cursorIface->cursorPositionReal( &line, &col ); + + return HistoryEntry( ro_part->url(), line, col ); +} + + +// this should be called _before_ a jump is made +void PartController::addHistoryEntry( KParts::ReadOnlyPart * part ) +{ + if ( m_isJumping ) return; + + HistoryEntry thisEntry = createHistoryEntry( part ); + if ( !thisEntry.url.isEmpty() ) + { + HistoryEntry lastEntry = m_backHistory.front(); + if ( (lastEntry.url.path() != thisEntry.url.path()) || (lastEntry.line != thisEntry.line) ) + { + m_backHistory.push_front( thisEntry ); + m_backAction->setEnabled( true ); + + m_forwardHistory.clear(); + m_forwardAction->setEnabled( false ); + } + else + { + kdDebug(9000) << "** avoiding to create duplicate history entry **" << endl; + } + } +} + +//END History methods + +void PartController::slotWaitForFactoryHack( ) +{ + //kdDebug(9000) << k_funcinfo << endl; + + if ( !activePart() ) return; + + if ( dynamic_cast<KTextEditor::View*>( activePart()->widget() ) ) + { + if ( !activePart()->factory() ) + { + QTimer::singleShot( 100, this, SLOT(slotWaitForFactoryHack()) ); + return; + } + else + { + EditorProxy::getInstance()->installPopup( activePart() ); + } + } + + if ( MultiBuffer *multiBuffer = + dynamic_cast<MultiBuffer*>( + EditorProxy::getInstance()->topWidgetForPart( activePart() ) ) + ) + { + KURL url = dynamic_cast<KParts::ReadOnlyPart*>( activePart() )->url(); + multiBuffer->activePartChanged( url ); + // Really unfortunate, but the mainWindow relies upon this + // to set the tab's icon + emit documentChangedState( url, documentState( url ) ); + } +} + +KParts::ReadOnlyPart *PartController::qtDesignerPart() +{ + QPtrListIterator<KParts::Part> it(*parts()); + for ( ; it.current(); ++it) + { + KInterfaceDesigner::Designer *des = dynamic_cast<KInterfaceDesigner::Designer*>(it.current()); + if (des && des->designerType() == KInterfaceDesigner::QtDesigner) + return des; + } + return 0; +} + +KTextEditor::Editor *PartController::openTextDocument( bool activate ) +{ + KTextEditor::Editor *editorpart = + createEditorPart( activate, false, KURL( i18n("unnamed") ) ); + + if ( editorpart ) + { + if ( !m_presetEncoding.isNull() ) + { + KParts::BrowserExtension * extension = + KParts::BrowserExtension::childObject( editorpart ); + if ( extension ) + { + KParts::URLArgs args; + args.serviceType = QString( "text/plain;" ) + m_presetEncoding; + extension->setURLArgs(args); + } + m_presetEncoding = QString::null; + } + + QWidget * widget = + EditorProxy::getInstance()->topWidgetForPart( editorpart ); + + addHistoryEntry(); + integratePart(editorpart, KURL(i18n("unnamed")), widget, true, true); + + EditorProxy::getInstance()->setLineNumber(editorpart, 0, 0); + } + + return editorpart; +} + +void PartController::textChanged() +{ + if ( KTextEditor::Document * doc = dynamic_cast<KTextEditor::Document*>( activePart() ) ) + { + if ( KTextEditor::ViewCursorInterface * vci = dynamic_cast<KTextEditor::ViewCursorInterface*>( doc->widget() ) ) + { + m_gotoLastEditPosAction->setEnabled( true ); + + m_lastEditPos.url = doc->url(); + vci->cursorPosition( &m_lastEditPos.pos.first, &m_lastEditPos.pos.second ); + } + } +} + +void PartController::gotoLastEditPos() +{ + editDocument( m_lastEditPos.url, m_lastEditPos.pos.first, m_lastEditPos.pos.second ); +} + +void PartController::slotDocumentUrlChanged() +{ + QObject *obj = const_cast<QObject*>(sender()); + KTextEditor::Document *doc = dynamic_cast<KTextEditor::Document*>( obj ); + if (!doc) + return; + + MultiBuffer *multiBuffer = dynamic_cast<MultiBuffer*>( + EditorProxy::getInstance()->findPartWidget( doc )); + if (!multiBuffer) + return; + + multiBuffer->updateUrlForPart(doc, doc->url()); + updatePartURL(doc); + emit partURLChanged(doc); +} + +#include "partcontroller.moc" + |