[rkward-cvs] SF.net SVN: rkward:[2859] trunk/rkward

tfry at users.sourceforge.net tfry at users.sourceforge.net
Wed May 5 10:00:22 UTC 2010


Revision: 2859
          http://rkward.svn.sourceforge.net/rkward/?rev=2859&view=rev
Author:   tfry
Date:     2010-05-05 10:00:21 +0000 (Wed, 05 May 2010)

Log Message:
-----------
Add autosave feature for script files

Modified Paths:
--------------
    trunk/rkward/ChangeLog
    trunk/rkward/rkward/misc/CMakeLists.txt
    trunk/rkward/rkward/settings/rksettingsmodulecommandeditor.cpp
    trunk/rkward/rkward/settings/rksettingsmodulecommandeditor.h
    trunk/rkward/rkward/windows/rkcommandeditorwindow.cpp
    trunk/rkward/rkward/windows/rkcommandeditorwindow.h

Added Paths:
-----------
    trunk/rkward/rkward/misc/rkjobsequence.cpp
    trunk/rkward/rkward/misc/rkjobsequence.h

Modified: trunk/rkward/ChangeLog
===================================================================
--- trunk/rkward/ChangeLog	2010-05-05 09:55:30 UTC (rev 2858)
+++ trunk/rkward/ChangeLog	2010-05-05 10:00:21 UTC (rev 2859)
@@ -1,5 +1,6 @@
 TODO: Do not use SmartInterface for KDE 4.5 and above
 
+- Add option to autosave script files (enabled by default)
 - The tabbar in the main window now shows a context menu with options to close/detach a window
 - The tabs in the main window can now be re-ordered by dragging with the mouse (left click if compiled with Qt 4.5 or above, middle click for earlier versions)
 - Add alternating row backgrounds in data.frame-editor

Modified: trunk/rkward/rkward/misc/CMakeLists.txt
===================================================================
--- trunk/rkward/rkward/misc/CMakeLists.txt	2010-05-05 09:55:30 UTC (rev 2858)
+++ trunk/rkward/rkward/misc/CMakeLists.txt	2010-05-05 10:00:21 UTC (rev 2859)
@@ -13,6 +13,7 @@
    rkprogresscontrol.cpp
    rksaveobjectchooser.cpp
    rkdummypart.cpp
+   rkjobsequence.cpp
    rkspecialactions.cpp
    rkstandardicons.cpp
    rkstandardactions.cpp

Added: trunk/rkward/rkward/misc/rkjobsequence.cpp
===================================================================
--- trunk/rkward/rkward/misc/rkjobsequence.cpp	                        (rev 0)
+++ trunk/rkward/rkward/misc/rkjobsequence.cpp	2010-05-05 10:00:21 UTC (rev 2859)
@@ -0,0 +1,77 @@
+/***************************************************************************
+                          rkjobsequence  -  description
+                             -------------------
+    begin                : Tue May 04
+    copyright            : (C) 2010 by Thomas Friedrichsmeier
+    email                : tfry at users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+#include "rkjobsequence.h"
+
+#include "../debug.h"
+
+RKJobSequence::RKJobSequence () : QObject () {
+	RK_TRACE (MISC);
+}
+
+RKJobSequence::~RKJobSequence () {
+	RK_TRACE (MISC);
+}
+
+void RKJobSequence::addJob (KJob* job) {
+	RK_TRACE (MISC);
+
+	outstanding_jobs.append (job);
+	connect (job, SIGNAL (result(KJob*)), this, SLOT(jobDone(KJob*)));
+}
+
+bool RKJobSequence::hadError () const {
+	RK_TRACE (MISC);
+
+	return (!_errors.isEmpty ());
+}
+
+QStringList RKJobSequence::errors () const {
+	RK_TRACE (MISC);
+
+	return (_errors);
+}
+
+void RKJobSequence::start () {
+	RK_TRACE (MISC);
+
+	RK_ASSERT (!outstanding_jobs.isEmpty ());
+	nextJob ();
+}
+
+void RKJobSequence::nextJob () {
+	RK_TRACE (MISC);
+
+	if (outstanding_jobs.isEmpty ()) {
+		emit (finished (this));
+		deleteLater ();
+		return;
+	}
+
+	outstanding_jobs.first ()->start ();
+}
+
+void RKJobSequence::jobDone (KJob* job) {
+	RK_TRACE (MISC);
+
+	outstanding_jobs.removeAll (job);
+	if (job->error ()) {
+		_errors.append (job->errorString ());
+	}
+	nextJob ();
+}
+
+#include "rkjobsequence.moc"

Added: trunk/rkward/rkward/misc/rkjobsequence.h
===================================================================
--- trunk/rkward/rkward/misc/rkjobsequence.h	                        (rev 0)
+++ trunk/rkward/rkward/misc/rkjobsequence.h	2010-05-05 10:00:21 UTC (rev 2859)
@@ -0,0 +1,47 @@
+/***************************************************************************
+                          rkjobsequence  -  description
+                             -------------------
+    begin                : Tue May 04
+    copyright            : (C) 2010 by Thomas Friedrichsmeier
+    email                : tfry at users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#ifndef RKJOBSEQUENCE_H
+#define RKJOBSEQUENCE_H
+
+#include <QStringList>
+
+#include <kjob.h>
+
+/** Simple class to queue up a sequnce of KJob that will be executed one after the other */
+class RKJobSequence : public QObject {
+	Q_OBJECT
+public:
+	RKJobSequence ();
+	~RKJobSequence ();
+
+	void addJob (KJob* job);
+	bool hadError () const;
+	QStringList errors () const;
+	void start ();
+private slots:
+	void jobDone (KJob* job);
+signals:
+	void finished (RKJobSequence *seq);
+private:
+	void nextJob ();
+
+	QList<KJob*> outstanding_jobs;
+	QStringList _errors;
+};
+
+#endif
\ No newline at end of file

Modified: trunk/rkward/rkward/settings/rksettingsmodulecommandeditor.cpp
===================================================================
--- trunk/rkward/rkward/settings/rksettingsmodulecommandeditor.cpp	2010-05-05 09:55:30 UTC (rev 2858)
+++ trunk/rkward/rkward/settings/rksettingsmodulecommandeditor.cpp	2010-05-05 10:00:21 UTC (rev 2859)
@@ -2,7 +2,7 @@
                           rksettingsmodulecommandeditor  -  description
                              -------------------
     begin                : Tue Oct 23 2007
-    copyright            : (C) 2007 by Thomas Friedrichsmeier
+    copyright            : (C) 2007, 2010 by Thomas Friedrichsmeier
     email                : tfry at users.sourceforge.net
  ***************************************************************************/
 
@@ -25,6 +25,7 @@
 #include <QVBoxLayout>
 #include <QCheckBox>
 #include <QGroupBox>
+#include <QLineEdit>
 
 #include "../misc/rkspinbox.h"
 #include "../rkglobals.h"
@@ -34,6 +35,10 @@
 int RKSettingsModuleCommandEditor::completion_min_chars;
 int RKSettingsModuleCommandEditor::completion_timeout;
 bool RKSettingsModuleCommandEditor::completion_enabled;
+bool RKSettingsModuleCommandEditor::autosave_enabled;
+bool RKSettingsModuleCommandEditor::autosave_keep;
+int RKSettingsModuleCommandEditor::autosave_interval;
+//QString RKSettingsModuleCommandEditor::autosave_suffix;
 
 RKSettingsModuleCommandEditor::RKSettingsModuleCommandEditor (RKSettings *gui, QWidget *parent) : RKSettingsModule (gui, parent) {
 	RK_TRACE (SETTINGS);
@@ -45,7 +50,7 @@
 
 	completion_enabled_box = new QCheckBox (i18n ("Enable code completion"), group);
 	completion_enabled_box->setChecked (completion_enabled);
-	connect (completion_enabled_box, SIGNAL (stateChanged(int)), this, SLOT (settingChanged(int)));
+	connect (completion_enabled_box, SIGNAL (stateChanged(int)), this, SLOT (settingChanged()));
 	box_layout->addWidget (completion_enabled_box);
 
 	box_layout->addSpacing (RKGlobals::spacingHint ());
@@ -55,7 +60,7 @@
 	completion_min_chars_box = new RKSpinBox (group);
 	completion_min_chars_box->setIntMode (1, INT_MAX, completion_min_chars);
 	completion_min_chars_box->setEnabled (completion_enabled);
-	connect (completion_min_chars_box, SIGNAL (valueChanged(int)), this, SLOT (settingChanged(int)));
+	connect (completion_min_chars_box, SIGNAL (valueChanged(int)), this, SLOT (settingChanged()));
 	box_layout->addWidget (label);
 	box_layout->addWidget (completion_min_chars_box);
 
@@ -66,12 +71,42 @@
 	completion_timeout_box = new RKSpinBox (group);
 	completion_timeout_box->setIntMode (0, INT_MAX, completion_timeout);
 	completion_timeout_box->setEnabled (completion_enabled);
-	connect (completion_timeout_box, SIGNAL (valueChanged(int)), this, SLOT (settingChanged(int)));
+	connect (completion_timeout_box, SIGNAL (valueChanged(int)), this, SLOT (settingChanged()));
 	box_layout->addWidget (label);
 	box_layout->addWidget (completion_timeout_box);
 
 	main_vbox->addWidget (group);
 
+	main_vbox->addSpacing (2 * RKGlobals::spacingHint ());
+
+	group = autosave_enabled_box = new QGroupBox (i18n ("Autosaves"), this);
+	autosave_enabled_box->setCheckable (true);
+	autosave_enabled_box->setChecked (autosave_enabled);
+	connect (autosave_enabled_box, SIGNAL (toggled(bool)), this, SLOT (settingChanged()));
+	box_layout = new QVBoxLayout (group);
+
+	label = new QLabel (i18n ("Autosave interval (minutes)"), group);
+	autosave_interval_box = new RKSpinBox (group);
+	autosave_interval_box->setIntMode (1, INT_MAX, autosave_interval);
+	connect (autosave_interval_box, SIGNAL (valueChanged(int)), this, SLOT (settingChanged()));
+	box_layout->addWidget (label);
+	box_layout->addWidget (autosave_interval_box);
+	box_layout->addSpacing (RKGlobals::spacingHint ());
+
+/*	label = new QLabel (i18n ("Filename suffix for autosave files"), group);
+	autosave_suffix_edit = new QLineEdit (autosave_suffix, group);
+	connect (autosave_suffix_edit, SIGNAL (textChanged(const QString&)), this, SLOT (settingChanged()));
+	box_layout->addWidget (label);
+	box_layout->addWidget (autosave_suffix_edit);
+	box_layout->addSpacing (RKGlobals::spacingHint ()); */
+
+	autosave_keep_box = new QCheckBox (i18n ("Keep autosave file after manual save"), group);
+	autosave_keep_box->setChecked (autosave_keep);
+	connect (autosave_keep_box, SIGNAL (stateChanged(int)), this, SLOT (settingChanged()));
+	box_layout->addWidget (autosave_keep_box);
+
+	main_vbox->addWidget (group);
+
 	main_vbox->addStretch ();
 }
 
@@ -79,7 +114,7 @@
 	RK_TRACE (SETTINGS);
 }
 
-void RKSettingsModuleCommandEditor::settingChanged (int) {
+void RKSettingsModuleCommandEditor::settingChanged () {
 	RK_TRACE (SETTINGS);
 	change ();
 
@@ -103,6 +138,13 @@
 	completion_enabled = completion_enabled_box->isChecked ();
 	completion_min_chars = completion_min_chars_box->intValue ();
 	completion_timeout = completion_timeout_box->intValue ();
+
+	autosave_enabled = autosave_enabled_box->isChecked ();
+	autosave_keep = autosave_keep_box->isChecked ();
+	autosave_interval = autosave_interval_box->intValue ();
+/*	autosave_suffix = autosave_suffix_edit->text ();
+	// prevent user from shooting themselves in the foot
+	if (autosave_suffix.isEmpty ()) autosave_suffix = ".autosave"; */
 }
 
 void RKSettingsModuleCommandEditor::save (KConfig *config) {
@@ -117,6 +159,11 @@
 	cg.writeEntry ("Completion enabled", completion_enabled);
 	cg.writeEntry ("Completion min chars", completion_min_chars);
 	cg.writeEntry ("Completion timeout", completion_timeout);
+
+	cg.writeEntry ("Autosave enabled", autosave_enabled);
+	cg.writeEntry ("Autosave keep saves", autosave_keep);
+	cg.writeEntry ("Autosave interval", autosave_interval);
+//	cg.writeEntry ("Autosave suffix", autosave_suffix);
 }
 
 void RKSettingsModuleCommandEditor::loadSettings (KConfig *config) {
@@ -126,6 +173,11 @@
 	completion_enabled = cg.readEntry ("Completion enabled", true);
 	completion_min_chars = cg.readEntry ("Completion min chars", 2);
 	completion_timeout = cg.readEntry ("Completion timeout", 500);
+
+	autosave_enabled = cg.readEntry ("Autosave enabled", true);
+	autosave_keep = cg.readEntry ("Autosave keep saves", false);
+	autosave_interval = cg.readEntry ("Autosave interval", 5);
+//	autosave_suffix = cg.readEntry ("Autosave suffix", ".rkward_autosave");
 }
 
 #include "rksettingsmodulecommandeditor.moc"

Modified: trunk/rkward/rkward/settings/rksettingsmodulecommandeditor.h
===================================================================
--- trunk/rkward/rkward/settings/rksettingsmodulecommandeditor.h	2010-05-05 09:55:30 UTC (rev 2858)
+++ trunk/rkward/rkward/settings/rksettingsmodulecommandeditor.h	2010-05-05 10:00:21 UTC (rev 2859)
@@ -2,7 +2,7 @@
                           rksettingsmodulecommandeditor  -  description
                              -------------------
     begin                : Tue Oct 23 2007
-    copyright            : (C) 2007 by Thomas Friedrichsmeier
+    copyright            : (C) 2007, 2010 by Thomas Friedrichsmeier
     email                : tfry at users.sourceforge.net
  ***************************************************************************/
 
@@ -21,6 +21,8 @@
 
 class RKSpinBox;
 class QCheckBox;
+class QLineEdit;
+class QGroupBox;
 
 /**
 configuration for the Command Editor windows
@@ -47,8 +49,13 @@
 	static int completionMinChars () { return completion_min_chars; };
 	static int completionTimeout () { return completion_timeout; };
 	static bool completionEnabled () { return completion_enabled; };
+
+	static bool autosaveEnabled () { return autosave_enabled; };
+	static bool autosaveKeep () { return autosave_keep; };
+	static int autosaveInterval () { return autosave_interval; };
+	static QString autosaveSuffix () { return ".rkward_autosave"; };
 public slots:
-	void settingChanged (int);
+	void settingChanged ();
 private:
 	static int completion_min_chars;
 	static int completion_timeout;
@@ -57,6 +64,16 @@
 	RKSpinBox* completion_min_chars_box;
 	RKSpinBox* completion_timeout_box;
 	QCheckBox* completion_enabled_box;
+
+	static bool autosave_enabled;
+	static bool autosave_keep;
+	static int autosave_interval;
+//	static QString autosave_suffix;
+
+	QGroupBox* autosave_enabled_box;
+	QCheckBox* autosave_keep_box;
+	RKSpinBox* autosave_interval_box;
+//	QLineEdit* autosave_suffix_edit;
 };
 
 #endif

Modified: trunk/rkward/rkward/windows/rkcommandeditorwindow.cpp
===================================================================
--- trunk/rkward/rkward/windows/rkcommandeditorwindow.cpp	2010-05-05 09:55:30 UTC (rev 2858)
+++ trunk/rkward/rkward/windows/rkcommandeditorwindow.cpp	2010-05-05 10:00:21 UTC (rev 2859)
@@ -46,11 +46,15 @@
 #include <klibloader.h>
 #include <kactioncollection.h>
 #include <kactionmenu.h>
+#include <ktemporaryfile.h>
+#include <kio/deletejob.h>
+#include <kio/job.h>
 
 #include "../misc/rkcommonfunctions.h"
 #include "../misc/rkstandardicons.h"
 #include "../misc/rkstandardactions.h"
 #include "../misc/rkxmlguisyncer.h"
+#include "../misc/rkjobsequence.h"
 #include "../core/robjectlist.h"
 #include "../settings/rksettings.h"
 #include "../settings/rksettingsmodulecommandeditor.h"
@@ -108,8 +112,10 @@
 	layout->addWidget(m_view);
 
 	connect (m_doc, SIGNAL (documentUrlChanged (KTextEditor::Document*)), this, SLOT (updateCaption (KTextEditor::Document*)));
-	connect (m_doc, SIGNAL (modifiedChanged (KTextEditor::Document*)), this, SLOT (updateCaption (KTextEditor::Document*)));		// of course most of the time this causes a redundant call to updateCaption. Not if a modification is undone, however.
+	connect (m_doc, SIGNAL (modifiedChanged (KTextEditor::Document*)), this, SLOT (updateCaption(KTextEditor::Document*)));                // of course most of the time this causes a redundant call to updateCaption. Not if a modification is undone, however.
+	connect (m_doc, SIGNAL (modifiedChanged (KTextEditor::Document*)), this, SLOT (autoSaveHandlerModifiedChanged()));
 	connect (m_doc, SIGNAL (textChanged (KTextEditor::Document*)), this, SLOT (tryCompletionProxy (KTextEditor::Document*)));
+	connect (m_doc, SIGNAL (textChanged (KTextEditor::Document*)), this, SLOT (autoSaveHandlerTextChanged()));
 	connect (m_view, SIGNAL (selectionChanged(KTextEditor::View*)), this, SLOT (selectionChanged(KTextEditor::View*)));
 	// somehow the katepart loses the context menu each time it loses focus
 	connect (m_view, SIGNAL (focusIn(KTextEditor::View*)), this, SLOT (focusIn(KTextEditor::View*)));
@@ -136,6 +142,9 @@
 	initBlocks ();
 	RK_ASSERT (smart_iface);
 
+	autosave_timer = new QTimer (this);
+	connect (autosave_timer, SIGNAL (timeout()), this, SLOT (doAutoSave()));
+
 	updateCaption ();	// initialize
 	QTimer::singleShot (0, this, SLOT (setPopupMenu ()));
 }
@@ -332,6 +341,83 @@
 	return false;
 }
 
+void RKCommandEditorWindow::autoSaveHandlerModifiedChanged () {
+	RK_TRACE (COMMANDEDITOR);
+
+	if (!isModified ()) {
+		autosave_timer->stop ();
+
+		if (RKSettingsModuleCommandEditor::autosaveKeep ()) return;
+		if (!previous_autosave_url.isValid ()) return;
+		RKJobSequence* dummy = new RKJobSequence ();
+		dummy->addJob (KIO::del (previous_autosave_url));
+		connect (dummy, SIGNAL (finished(RKJobSequence*)), this, SLOT (autoSaveHandlerJobFinished(RKJobSequence*)));
+		dummy->start ();
+		previous_autosave_url.clear ();
+	}
+}
+
+void RKCommandEditorWindow::autoSaveHandlerTextChanged () {
+	RK_TRACE (COMMANDEDITOR);
+
+	if (!isModified ()) return;		// may happen after load or undo
+	if (!RKSettingsModuleCommandEditor::autosaveEnabled ()) return;
+	if (!autosave_timer->isActive ()) {
+		autosave_timer->start (RKSettingsModuleCommandEditor::autosaveInterval () * 60 * 1000);
+	}
+}
+
+void RKCommandEditorWindow::doAutoSave () {
+	RK_TRACE (COMMANDEDITOR);
+	RK_ASSERT (isModified ());
+
+	KTemporaryFile save;
+	save.setSuffix (RKSettingsModuleCommandEditor::autosaveSuffix ());
+	RK_ASSERT (save.open ());
+	QTextStream out (&save);
+	out.setCodec ("UTF-8");		// make sure that all characters can be saved, without nagging the user
+	out << m_doc->text ();
+	save.close ();
+	save.setAutoRemove (false);
+
+	RKJobSequence* alljobs = new RKJobSequence ();
+	connect (alljobs, SIGNAL (finished(RKJobSequence*)), this, SLOT (autoSaveHandlerJobFinished(RKJobSequence*)));
+	// backup the old autosave file in case something goes wrong during pushing the new one
+	KUrl backup_autosave_url;
+	if (previous_autosave_url.isValid ()) {
+		backup_autosave_url = previous_autosave_url;
+		backup_autosave_url.setFileName (backup_autosave_url.fileName () + "~");
+		alljobs->addJob (KIO::file_move (previous_autosave_url, backup_autosave_url, -1, KIO::Overwrite));
+	}
+	
+	// push the newly written file
+	if (url ().isValid ()) {
+		KUrl autosave_url = url ();
+		autosave_url.setFileName (autosave_url.fileName () + RKSettingsModuleCommandEditor::autosaveSuffix ());
+		alljobs->addJob (KIO::file_move (KUrl::fromLocalFile (save.fileName ()), autosave_url, -1, KIO::Overwrite));
+		previous_autosave_url = autosave_url;
+	} else {		// i.e., the document is still "Untitled"
+		previous_autosave_url = KUrl::fromLocalFile (save.fileName ());
+	}
+
+	// remove the backup
+	if (backup_autosave_url.isValid ()) {
+		alljobs->addJob (KIO::del (backup_autosave_url));
+	}
+	alljobs->start ();
+
+	// do not create any more autosaves until the text is changed, again
+	autosave_timer->stop ();
+}
+
+void RKCommandEditorWindow::autoSaveHandlerJobFinished (RKJobSequence* seq) {
+	RK_TRACE (COMMANDEDITOR);
+
+	if (seq->hadError ()) {
+		KMessageBox::detailedError (this, i18n ("An error occurred during while trying to create an autosave of the script file '%1':", url ().url ()), "- " + seq->errors ().join ("\n- "));
+	}
+}
+
 KUrl RKCommandEditorWindow::url () {
 //	RK_TRACE (COMMANDEDITOR);
 	return (m_doc->url ());

Modified: trunk/rkward/rkward/windows/rkcommandeditorwindow.h
===================================================================
--- trunk/rkward/rkward/windows/rkcommandeditorwindow.h	2010-05-05 09:55:30 UTC (rev 2858)
+++ trunk/rkward/rkward/windows/rkcommandeditorwindow.h	2010-05-05 10:00:21 UTC (rev 2859)
@@ -116,6 +116,7 @@
 };
 
 class QTimer;
+class RKJobSequence;
 
 /**
 	\brief Provides an editor window for R-commands, as well as a text-editor window in general.
@@ -199,6 +200,14 @@
 /** run a block */
 	void runBlock ();
 	void clearUnusedBlocks ();
+/** creates an autosave file */
+	void doAutoSave ();
+/** handler to control when autosaves should be created */
+	void autoSaveHandlerModifiedChanged ();
+/** handler to control when autosaves should be created */
+	void autoSaveHandlerTextChanged ();
+/** handle any errors during auto-saving */
+	void autoSaveHandlerJobFinished (RKJobSequence* seq);
 private:
 	KTextEditor::Document *m_doc;
 	KTextEditor::View *m_view;
@@ -237,6 +246,9 @@
 	KAction* action_setwd_to_script;
 
 	KAction* action_help_function;
+
+	KUrl previous_autosave_url;
+	QTimer* autosave_timer;
 };
 
 #endif


This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.




More information about the rkward-tracker mailing list