Skip to content

Commit 4d19818

Browse files
committed
Handle out-of-memory situations by offering to save the project.
1 parent 024013b commit 4d19818

18 files changed

+1032
-87
lines changed

BackgroundExecutor.cpp

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
Scan Tailor - Interactive post-processing tool for scanned pages.
3-
Copyright (C) 2007-2008 Joseph Artsimovich <joseph_a@mail.ru>
3+
Copyright (C) Joseph Artsimovich <joseph.artsimovich@gmail.com>
44
55
This program is free software: you can redistribute it and/or modify
66
it under the terms of the GNU General Public License as published by
@@ -17,10 +17,12 @@
1717
*/
1818

1919
#include "BackgroundExecutor.h"
20+
#include "OutOfMemoryHandler.h"
2021
#include <QCoreApplication>
2122
#include <QObject>
2223
#include <QThread>
2324
#include <QEvent>
25+
#include <new>
2426
#include <assert.h>
2527

2628
template<typename T>
@@ -103,17 +105,21 @@ BackgroundExecutor::Dispatcher::Dispatcher(Impl& owner)
103105
void
104106
BackgroundExecutor::Dispatcher::customEvent(QEvent* event)
105107
{
106-
TaskEvent* evt = dynamic_cast<TaskEvent*>(event);
107-
assert(evt);
108-
109-
TaskPtr const& task = evt->payload();
110-
assert(task);
111-
112-
TaskResultPtr const result((*task)());
113-
if (result) {
114-
QCoreApplication::postEvent(
115-
&m_rOwner, new ResultEvent(result)
116-
);
108+
try {
109+
TaskEvent* evt = dynamic_cast<TaskEvent*>(event);
110+
assert(evt);
111+
112+
TaskPtr const& task = evt->payload();
113+
assert(task);
114+
115+
TaskResultPtr const result((*task)());
116+
if (result) {
117+
QCoreApplication::postEvent(
118+
&m_rOwner, new ResultEvent(result)
119+
);
120+
}
121+
} catch (std::bad_alloc const&) {
122+
OutOfMemoryHandler::instance().handleOutOfMemorySituation();
117123
}
118124
}
119125

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ SET(
441441
SkinnedButton.cpp SkinnedButton.h
442442
BubbleAnimation.cpp BubbleAnimation.h
443443
ProcessingIndicationWidget.cpp ProcessingIndicationWidget.h
444+
NonOwningWidget.cpp NonOwningWidget.h
444445
Dpi.cpp Dpi.h Dpm.cpp Dpm.h
445446
SmartFilenameOrdering.cpp SmartFilenameOrdering.h
446447
ImageInfo.cpp ImageInfo.h
@@ -452,6 +453,8 @@ SET(
452453
FixDpiSinglePageDialog.cpp FixDpiSinglePageDialog.h
453454
ProjectCreationContext.cpp ProjectCreationContext.h
454455
ProjectOpeningContext.cpp ProjectOpeningContext.h
456+
OutOfMemoryHandler.cpp OutOfMemoryHandler.h
457+
OutOfMemoryDialog.cpp OutOfMemoryDialog.h
455458
MainWindow.cpp MainWindow.h
456459
ConsoleBatch.cpp ConsoleBatch.h
457460
CommandLine.cpp CommandLine.h

MainWindow.cpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
#include "OrthogonalRotation.h"
5858
#include "FixDpiSinglePageDialog.h"
5959
#include "SettingsDialog.h"
60+
#include "OutOfMemoryHandler.h"
61+
#include "OutOfMemoryDialog.h"
6062
#include "filters/fix_orientation/Filter.h"
6163
#include "filters/fix_orientation/Task.h"
6264
#include "filters/fix_orientation/CacheDrivenTask.h"
@@ -128,6 +130,7 @@ MainWindow::MainWindow()
128130
m_ptrStages(new StageSequence(m_ptrPages, PageSelectionAccessor(this))),
129131
m_ptrWorkerThread(new WorkerThread),
130132
m_ptrInteractiveQueue(new ProcessingTaskQueue(ProcessingTaskQueue::RANDOM_ORDER)),
133+
m_ptrOutOfMemoryDialog(new OutOfMemoryDialog),
131134
m_curFilter(0),
132135
m_ignoreSelectionChanges(0),
133136
m_ignorePageOrderingChanges(0),
@@ -166,14 +169,21 @@ MainWindow::MainWindow()
166169
addAction(actionPrevPage);
167170
addAction(actionPrevPageQ);
168171
addAction(actionNextPageW);
169-
172+
173+
// Should be enough to save a project.
174+
OutOfMemoryHandler::instance().allocateEmergencyMemory(3*1024*1024);
175+
170176
connect(actionFirstPage, SIGNAL(triggered(bool)), SLOT(goFirstPage()));
171177
connect(actionLastPage, SIGNAL(triggered(bool)), SLOT(goLastPage()));
172178
connect(actionPrevPage, SIGNAL(triggered(bool)), SLOT(goPrevPage()));
173179
connect(actionNextPage, SIGNAL(triggered(bool)), SLOT(goNextPage()));
174180
connect(actionPrevPageQ, SIGNAL(triggered(bool)), this, SLOT(goPrevPage()));
175181
connect(actionNextPageW, SIGNAL(triggered(bool)), this, SLOT(goNextPage()));
176182
connect(actionAbout, SIGNAL(triggered(bool)), this, SLOT(showAboutDialog()));
183+
connect(
184+
&OutOfMemoryHandler::instance(),
185+
SIGNAL(outOfMemory()), SLOT(handleOutOfMemorySituation())
186+
);
177187

178188
connect(
179189
filterList->selectionModel(),
@@ -1201,6 +1211,8 @@ MainWindow::saveProjectTriggered()
12011211
void
12021212
MainWindow::saveProjectAsTriggered()
12031213
{
1214+
// XXX: this function is duplicated in OutOfMemoryDialog.
1215+
12041216
QString project_dir;
12051217
if (!m_projectFile.isEmpty()) {
12061218
project_dir = QFileInfo(m_projectFile).absolutePath();
@@ -1371,6 +1383,24 @@ MainWindow::showAboutDialog()
13711383
dialog->show();
13721384
}
13731385

1386+
/**
1387+
* This function is called asynchronously, always from the main thread.
1388+
*/
1389+
void
1390+
MainWindow::handleOutOfMemorySituation()
1391+
{
1392+
deleteLater();
1393+
1394+
m_ptrOutOfMemoryDialog->setParams(
1395+
m_projectFile, m_ptrStages, m_ptrPages, m_selectedPage, m_outFileNameGen
1396+
);
1397+
1398+
closeProjectWithoutSaving();
1399+
1400+
m_ptrOutOfMemoryDialog->setAttribute(Qt::WA_DeleteOnClose);
1401+
m_ptrOutOfMemoryDialog.release()->show();
1402+
}
1403+
13741404
/**
13751405
* Note: the removed widgets are not deleted.
13761406
*/

MainWindow.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class ProjectOpeningContext;
6363
class CompositeCacheDrivenTask;
6464
class TabbedDebugImages;
6565
class ProcessingTaskQueue;
66+
class OutOfMemoryDialog;
6667
class QLineF;
6768
class QRectF;
6869
class QLayout;
@@ -155,6 +156,8 @@ private slots:
155156
void openSettingsDialog();
156157

157158
void showAboutDialog();
159+
160+
void handleOutOfMemorySituation();
158161
private:
159162
enum SavePromptResult { SAVE, DONT_SAVE, CANCEL };
160163

@@ -272,6 +275,7 @@ private slots:
272275
SelectedPage m_selectedPage;
273276
QObjectCleanupHandler m_optionsWidgetCleanup;
274277
QObjectCleanupHandler m_imageWidgetCleanup;
278+
std::auto_ptr<OutOfMemoryDialog> m_ptrOutOfMemoryDialog;
275279
int m_curFilter;
276280
int m_ignoreSelectionChanges;
277281
int m_ignorePageOrderingChanges;

NonOwningWidget.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Scan Tailor - Interactive post-processing tool for scanned pages.
3+
Copyright (C) Joseph Artsimovich <joseph.artsimovich@gmail.com>
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include "NonOwningWidget.h"
20+
#include <boost/foreach.hpp>
21+
22+
NonOwningWidget::NonOwningWidget(QWidget* parent)
23+
: QWidget(parent)
24+
{
25+
}
26+
27+
NonOwningWidget::~NonOwningWidget()
28+
{
29+
BOOST_FOREACH(QObject* child, children()) {
30+
if (QWidget* widget = dynamic_cast<QWidget*>(child)) {
31+
widget->setParent(0);
32+
}
33+
}
34+
}

NonOwningWidget.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Scan Tailor - Interactive post-processing tool for scanned pages.
3+
Copyright (C) Joseph Artsimovich <joseph.artsimovich@gmail.com>
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#ifndef NON_OWNING_WIDGET_H_
20+
#define NON_OWNING_WIDGET_H_
21+
22+
#include <QWidget>
23+
24+
/**
25+
* \brief Your normal QWidget, except it doesn't delete its children with itself,
26+
* rather it calls setParent(0) on them.
27+
*/
28+
class NonOwningWidget : public QWidget
29+
{
30+
public:
31+
NonOwningWidget(QWidget* parent = 0);
32+
33+
virtual ~NonOwningWidget();
34+
};
35+
36+
#endif

OutOfMemoryDialog.cpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
Scan Tailor - Interactive post-processing tool for scanned pages.
3+
Copyright (C) Joseph Artsimovich <joseph.artsimovich@gmail.com>
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include "OutOfMemoryDialog.h"
20+
#include "OutOfMemoryDialog.h.moc"
21+
#include "ProjectWriter.h"
22+
#include "RecentProjects.h"
23+
#include <QFileDialog>
24+
#include <QMessageBox>
25+
#include <QFileInfo>
26+
#include <QSettings>
27+
#include <QVariant>
28+
29+
OutOfMemoryDialog::OutOfMemoryDialog(QWidget* parent)
30+
: QDialog(parent)
31+
{
32+
ui.setupUi(this);
33+
34+
ui.topLevelStack->setCurrentWidget(ui.mainPage);
35+
36+
connect(ui.saveProjectBtn, SIGNAL(clicked()), SLOT(saveProject()));
37+
connect(ui.saveProjectAsBtn, SIGNAL(clicked()), SLOT(saveProjectAs()));
38+
connect(ui.dontSaveBtn, SIGNAL(clicked()), SLOT(reject()));
39+
}
40+
41+
void
42+
OutOfMemoryDialog::setParams(
43+
QString const& project_file,
44+
IntrusivePtr<StageSequence> const& stages,
45+
IntrusivePtr<ProjectPages> const& pages,
46+
SelectedPage const& selected_page,
47+
OutputFileNameGenerator const& out_file_name_gen)
48+
{
49+
m_projectFile = project_file;
50+
m_ptrStages = stages;
51+
m_ptrPages = pages;
52+
m_selectedPage = selected_page;
53+
m_outFileNameGen = out_file_name_gen;
54+
55+
ui.saveProjectBtn->setVisible(!project_file.isEmpty());
56+
}
57+
58+
void
59+
OutOfMemoryDialog::saveProject()
60+
{
61+
if (m_projectFile.isEmpty()) {
62+
saveProjectAs();
63+
} else if (saveProjectWithFeedback(m_projectFile)) {
64+
showSaveSuccessScreen();
65+
}
66+
}
67+
68+
void
69+
OutOfMemoryDialog::saveProjectAs()
70+
{
71+
// XXX: this function is duplicated MainWindow
72+
73+
QString project_dir;
74+
if (!m_projectFile.isEmpty()) {
75+
project_dir = QFileInfo(m_projectFile).absolutePath();
76+
} else {
77+
QSettings settings;
78+
project_dir = settings.value("project/lastDir").toString();
79+
}
80+
81+
QString project_file(
82+
QFileDialog::getSaveFileName(
83+
this, QString(), project_dir,
84+
tr("Scan Tailor Projects")+" (*.ScanTailor)"
85+
)
86+
);
87+
if (project_file.isEmpty()) {
88+
return;
89+
}
90+
91+
if (!project_file.endsWith(".ScanTailor", Qt::CaseInsensitive)) {
92+
project_file += ".ScanTailor";
93+
}
94+
95+
if (saveProjectWithFeedback(project_file)) {
96+
m_projectFile = project_file;
97+
showSaveSuccessScreen();
98+
99+
QSettings settings;
100+
settings.setValue(
101+
"project/lastDir",
102+
QFileInfo(m_projectFile).absolutePath()
103+
);
104+
105+
RecentProjects rp;
106+
rp.read();
107+
rp.setMostRecent(m_projectFile);
108+
rp.write();
109+
}
110+
}
111+
112+
bool
113+
OutOfMemoryDialog::saveProjectWithFeedback(QString const& project_file)
114+
{
115+
ProjectWriter writer(m_ptrPages, m_selectedPage, m_outFileNameGen);
116+
117+
if (!writer.write(project_file, m_ptrStages->filters())) {
118+
QMessageBox::warning(
119+
this, tr("Error"),
120+
tr("Error saving the project file!")
121+
);
122+
return false;
123+
}
124+
125+
return true;
126+
}
127+
128+
void
129+
OutOfMemoryDialog::showSaveSuccessScreen()
130+
{
131+
ui.topLevelStack->setCurrentWidget(ui.saveSuccessPage);
132+
}

0 commit comments

Comments
 (0)