PDA

View Full Version : Cancelling Long-running Print/Print Preview



ChrisW67
16th June 2009, 06:34
G'day All,

I have a potentially long running (10-20 seconds) print job that I'm providing a QProgressDialog for. I'd like to be able to cancel the job cleanly.

My paintRequested() routine first paginates then renders the pages. During pagination I detect a cancellation request and exit the pagination with a boolean flag, which causes the actual rendering to be skipped. A similar check in the rendering routine aborts the rendering.

I appreciate that what has already gone to the printer cannot be recalled. It would be nice though to have the QPrintPreviewDialog not open if the process was cancelled.

I was going to emit a custom signal from paintRequested() connected to a slot in the object that owns the preview dialog; it would destroy the QPrintPreviewDialog object. This seems rather draconian. Is there a more elegant way to do this?

Cheers,
Chris W

wysota
16th June 2009, 09:59
You mean that if you cancel a printing operation, the pages that have already been processed should be printed and the remaining ones should just be skipped? This should work provided you just returned from the painting routine after a page has been sent to printer. Can we see the code?

ChrisW67
16th June 2009, 10:24
That's what I am doing. When Cancel is pressed the remaining pages are skipped and only those already painted are physically printed. There's nothing I can do about that.

When the same code is used on a print preview the preview dialog displays everything up to last processed page after the paintRequested() handler returns. If Cancel is pressed while the paginate is running the preview display a single blank page (as expected).

What the user expects is that pressing Cancel will terminate the preview process entirely... not displaying the preview with the half-finished output or a blank page. I cannot see a clean way to do this because the paintRequested() slot cannot return a userAborted value or the like.

The handler for the QPrintPreviewDialog paintRequested() signal and friends look like:
void LogView::paintRequested(QPrinter * printer)
{
Q_ASSERT(printer);
m_printer = printer;

// Force some settings on the printer
m_printer->setFullPage(true);
m_printer->setPageMargins(0, 0, 0, 0, QPrinter::Millimeter);
m_printer->setOrientation(QPrinter::Landscape);
m_printer->setPageSize(QPrinter::A4);

QPainter painter(printer);
// Ensure the geometry is right for the device
calcGeometry(m_printer);
QList< QList<int> > pages;
if (paginate(&painter, &pages))
printPages(&painter, pages);
}
and

void LogView::printPages(QPainter *painter,
const QList< QList<int> > &pages)
{

int firstPage = m_printer->fromPage() - 1;
if (firstPage >= pages.size())
return;
if (firstPage == -1)
firstPage = 0;
int lastPage = m_printer->toPage() - 1;
if (lastPage == -1 || lastPage >= pages.size())
lastPage = pages.size() - 1;
int numPages = lastPage - firstPage + 1;
for (int i = 0; i < m_printer->numCopies(); ++i) {
QProgressDialog progress(this);
progress.setLabelText(tr("Printing %1 pages (Copy %2)").arg(numPages).arg(i+1));
progress.setRange(0, numPages-1);
progress.setModal(true);
for (int j = 0; j < numPages; ++j) {
// Process events for responsivness
progress.setValue(j);
qApp->processEvents();
if (progress.wasCanceled()) {
break;
}
if (i != 0 || j != 0)
m_printer->newPage();
int index;
if (m_printer->pageOrder() == QPrinter::FirstPageFirst) {
index = firstPage + j;
} else {
index = lastPage - j;
}
printPage(painter, pages[index], index + 1);
}
if (progress.wasCanceled())
break;
}
}

wysota
16th June 2009, 11:53
Why don't you perform calculations first and only then do the painting? This way you should get a all-or-nothing semantics. You can make sure you can't cancel the operation while the actual painting is done (so that it becomes an atomic operation).

ChrisW67
17th June 2009, 00:05
Thanks for the suggestion.

The pagination is currently separate from the painting. For a 150 page document (~2500 rows from a DB, ugly rendering contortions) pagination is taking in the order of 2.3 seconds (mid-range machine). I could move this outside the paintRequested() handler and not actually invoke QPrintPreviewDialog::exec() if it is canceled.

The painting itself takes 10+ seconds. I will look into how much of the formatting/calculation can be done without having access to the actual painter... I can move this into a pre-processing stage with the pagination. Since much of it is wrapping text to fit columns and boxes I need to know the font/painter metrics to do this. I don't get the painter until QPrintPreviewDialog emits paintRequested() after exec() and then I am commited to display of at least one blank page even if I render nothing.

Edit: On reflection, the pagination requires painter metric also. I can certainly cache the result so it need not be repeated when an actual print is done from the preview dialog.