- Don't create layouts with parent. Qt 4.x automatically reparents
[duncan/yast2-qt4.git] / src / pkg / YQPkgConflictDialog.cc
1 /*---------------------------------------------------------------------\
2 |                                                                      |
3 |                      __   __    ____ _____ ____                      |
4 |                      \ \ / /_ _/ ___|_   _|___ \                     |
5 |                       \ V / _` \___ \ | |   __) |                    |
6 |                        | | (_| |___) || |  / __/                     |
7 |                        |_|\__,_|____/ |_| |_____|                    |
8 |                                                                      |
9 |                               core system                            |
10 |                                                        (C) SuSE GmbH |
11 \----------------------------------------------------------------------/
12
13   File:       YQPkgConflictDialog.cc
14
15   Author:     Stefan Hundhammer <sh@suse.de>
16
17   Textdomain "packages-qt"
18
19 /-*/
20
21 #define y2log_component "qt-pkg"
22 #include <ycp/y2log.h>
23
24 #include <zypp/ZYppFactory.h>
25 #include <zypp/Resolver.h>
26
27 #include <QLabel>
28 #include <QKeyEvent>
29 #include <QLayout>
30 #include <QMenu>
31 #include <QPushButton>
32 #include <QDateTime>
33 #include <QPainter>
34 #include <QMessageBox>
35 #include <QDesktopWidget>
36 #include <QPixmap>
37 #include <QBoxLayout>
38
39 #include "YQPkgConflictDialog.h"
40 #include "YQPkgConflictList.h"
41 #include "YQDialog.h"
42
43 #include "QY2LayoutUtils.h"
44 #include "YQUI.h"
45 #include "YQi18n.h"
46 #include "utf8.h"
47
48
49 #define SPACING                 6       // between subwidgets
50 #define MARGIN                  4       // around the widget
51
52
53 // The busy dialog ("Checking Dependencies") will only be shown if solving
54 // (on average) takes longer than this many seconds. The first one will be
55 // shown in any case.
56
57 #define SUPPRESS_BUSY_DIALOG_SECONDS    1.5
58
59
60 YQPkgConflictDialog::YQPkgConflictDialog( QWidget * parent )
61     : QDialog( parent )
62 {
63     _solveCount         = 0;
64     _totalSolveTime     = 0.0;
65
66
67     // Set the dialog title.
68     //
69     // "Dependency conflict" is already used as the conflict list header just
70     // some pixels below that, so don't use this twice. This dialog title may
71     // or may not be visible, depending on whether or not there is a window
72     // manager running (and configured to show any dialog titles).
73
74     setWindowTitle( _( "Warning" ) );
75
76     // Enable dialog resizing even without window manager
77     setSizeGripEnabled( true );
78
79
80     // Layout for the dialog (can't simply insert a QVbox)
81
82     QVBoxLayout * layout = new QVBoxLayout();
83     setLayout(layout);
84     layout->setMargin(MARGIN);
85     layout->setSpacing(SPACING);
86
87     Q_CHECK_PTR( layout );
88
89     // Conflict list
90
91     _conflictList = new YQPkgConflictList( this );
92     Q_CHECK_PTR( _conflictList );
93     layout->addWidget( _conflictList );
94     layout->addSpacing(8);
95
96     connect( _conflictList, SIGNAL( updatePackages() ),
97              this,          SIGNAL( updatePackages() ) );
98
99
100     // Button box
101     QHBoxLayout * buttonBox = new QHBoxLayout();
102     Q_CHECK_PTR( buttonBox );
103     buttonBox->setSpacing( SPACING );
104     buttonBox->setMargin ( MARGIN  );
105     layout->addLayout( buttonBox );
106
107     // "OK" button
108
109     QPushButton * button = new QPushButton( _( "&OK -- Try Again" ), this);
110     buttonBox->addWidget(button);
111     Q_CHECK_PTR( button );
112     button->setDefault( true );
113
114     connect( button, SIGNAL( clicked() ),
115              this,   SLOT  ( solveAndShowConflicts() ) );
116
117     // FIXME addHStretch( buttonBox );
118
119
120     // "Expert" menu button
121
122     button = new QPushButton( _( "&Expert" ), this );
123     buttonBox->addWidget(button);
124
125     Q_CHECK_PTR( button );
126
127     //FIXME addHStretch( buttonBox );
128
129
130     // "Expert" menu
131
132     _expertMenu = new QMenu( button );
133     Q_CHECK_PTR( _expertMenu );
134     button->setMenu( _expertMenu );
135
136     _expertMenu->addAction( _( "&Save This List to a File..." ),
137                              _conflictList, SLOT( askSaveToFile() ) );
138
139
140     // "Cancel" button
141
142     button = new QPushButton( _( "&Cancel" ), this);
143     buttonBox->addWidget(button);
144     Q_CHECK_PTR( button );
145
146     connect( button, SIGNAL( clicked() ),
147              this,   SLOT  ( reject()  ) );
148
149
150     // Busy popup
151
152     _busyPopup = new QLabel( "   " + _( "Checking Dependencies..." ) + "   ", parent, 0
153 #ifdef FIXME
154                              , WStyle_Customize | WStyle_DialogBorder | WStyle_Dialog | WStyle_Title
155 #endif
156                              );
157     Q_CHECK_PTR( _busyPopup );
158
159     _busyPopup->setWindowTitle( "" );
160     _busyPopup->resize( _busyPopup->sizeHint() );
161     YQDialog::center( _busyPopup, parent );
162
163
164     // Here comes a real nasty hack.
165     //
166     // The busy popup is needed to indicate that the application is (you
167     // guessed right) busy. But as long as it is busy, it doesn't process X
168     // events, either, and I didn't manage to convince Qt to please paint this
169     // popup before the solver's calculations (which take quite a while) start
170     // - all combinations of show(), repaint(), XSync(), XFlush(),
171     // processEvents() etc. failed.
172     //
173     // So, let's do it the hard way: Give this popup a background pixmap into
174     // which we render the text to display. The X server draws background
175     // pixmaps immediately, so we don't have to wait until the X server, the
176     // window manager and this application are finished negotiating all their
177     // various events.
178
179     // Create a pixmap. Make it large enough so it isn't replicated (i.e. the
180     // text is displayed several times) if some window manager chooses not to
181     // honor the size hints (KDM for example uses double the height we
182     // request).
183
184     QSize size = _busyPopup->sizeHint();
185     QPixmap pixmap( 3 * size.width(), 3 * size.height() );
186
187     // Clear the pixmap with the widget's normal background color.
188     //FIXME pixmap.fill( _busyPopup->paletteBackgroundColor() );
189
190     // Render the text - aligned top and left because otherwise it will of
191     // course be centered inside the pixmap which is usually much larger than
192     // the popup, thus the text would be cut off.
193     QPainter painter( &pixmap );
194     painter.drawText( pixmap.rect(), Qt::AlignLeft | Qt::AlignTop, _busyPopup->text() );
195     painter.end();
196
197     //FIXME _busyPopup->setPaletteBackgroundPixmap( pixmap );
198
199     // If the application manages to render the true contents of the label we
200     // just misused so badly, the real label will interfere with the background
201     // pixmap with (maybe) a few pixels offset (bug #25647). Fast or
202     // multiprocessor machines tend to have this problem.
203     // So let's get rid of the label text and solely rely on the background
204     // pixmap.
205     _busyPopup->setText( "" );
206
207     // Make sure the newly emptied text doesn't cause the busy dialog to be
208     // resized to nil (or a window manager dependent minimum size).
209     _busyPopup->setFixedSize( _busyPopup->size() );
210 }
211
212
213 YQPkgConflictDialog::~YQPkgConflictDialog()
214 {
215     // NOP
216 }
217
218
219 QSize
220 YQPkgConflictDialog::sizeHint() const
221 {
222     return limitToScreenSize( this, 550, 450 );
223 }
224
225
226 int
227 YQPkgConflictDialog::solveAndShowConflicts()
228 {
229     prepareSolving();
230
231     y2debug( "Solving..." );
232     QTime solveTime;
233     solveTime.start();
234
235     // Solve.
236
237     bool success = zypp::getZYpp()->resolver()->resolvePool();
238
239     _totalSolveTime += solveTime.elapsed() / 1000.0;
240
241     y2debug( "Solving done in %f s - average: %f s",
242              solveTime.elapsed() / 1000.0, averageSolveTime() );
243
244     return processSolverResult( success );
245 }
246
247
248 int
249 YQPkgConflictDialog::verifySystem()
250 {
251     prepareSolving();
252
253     y2debug( "Verifying system..." );
254     QTime solveTime;
255     solveTime.start();
256
257     bool success = zypp::getZYpp()->resolver()->verifySystem( true ); // considerNewHardware
258
259     y2debug( "System verified in %f s", solveTime.elapsed() / 1000.0 );
260
261     return processSolverResult( success );
262 }
263
264
265 void
266 YQPkgConflictDialog::prepareSolving()
267 {
268     Q_CHECK_PTR( _conflictList );
269     YQUI::ui()->busyCursor();
270
271     if ( isVisible() )
272     {
273         // This is not only the starting point for all the dependency solving
274         // magic, it is also used internally when clicking the "OK - Try again"
275         // button. Thus, before doing anything else, check if the conflict list
276         // still contains anything, and if so, apply any conflict resolutions
277         // the user selected - but only if this dialog is already visible.
278
279         _conflictList->applyResolutions();
280     }
281
282
283     // Initialize for next round of solving.
284     _conflictList->clear();
285
286     if ( _solveCount++ == 0 || averageSolveTime() > SUPPRESS_BUSY_DIALOG_SECONDS )
287     {
288         YQDialog::center( _busyPopup, parentWidget() );
289         _busyPopup->show();
290
291         // No _busyPopup->repaint() - that doesn't help anyway: Qt doesn't do
292         // any actual painting until the window is mapped. We just rely on the
293         // background pixmap we provided in the constructor.
294
295         // Make sure show() gets processed - usually, a window manager catches
296         // the show() (XMap) events, positions and maybe resizes the window and
297         // only then sends off an event that makes the window appear. This
298         // event needs to be processed.
299         qApp->processEvents();
300     }
301 }
302
303
304 int
305 YQPkgConflictDialog::processSolverResult( bool success )
306 {
307     if ( _busyPopup->isVisible() )
308         _busyPopup->hide();
309
310     // Package states may have changed: The solver may have set packages to
311     // autoInstall or autoUpdate. Make those changes known.
312     emit updatePackages();
313
314     YQUI::ui()->normalCursor();
315     int result = QDialog::Accepted;
316
317     if ( success )      // Solving went without any complaints?
318     {
319         result = QDialog::Accepted;
320
321         if ( isVisible() )
322             accept();   // Pop down the dialog.
323     }
324     else                // There were solving problems.
325     {
326         y2debug( "Dependency conflict!" );
327         YQUI::ui()->busyCursor();
328
329         _conflictList->fill( zypp::getZYpp()->resolver()->problems() );
330         YQUI::ui()->normalCursor();
331
332         if ( ! isVisible() )
333         {
334             // Pop up the dialog and run a local event loop.
335             result = exec();
336         }
337     }
338
339     return result;      // QDialog::Accepted or QDialog::Rejected
340 }
341
342
343 void
344 YQPkgConflictDialog::resetIgnoredDependencyProblems()
345 {
346     zypp::getZYpp()->resolver()->undo();
347 }
348
349
350 double
351 YQPkgConflictDialog::averageSolveTime() const
352 {
353     if ( _solveCount < 1 )
354         return 0.0;
355
356     return _totalSolveTime / _solveCount;
357 }
358
359
360 void
361 YQPkgConflictDialog::askCreateSolverTestCase()
362 {
363     QString testCaseDir = "/var/log/YaST2/solverTestcase";
364     // Heading for popup dialog
365     QString heading = QString( "<h2>%1</h2>" ).arg( _( "Create Dependency Resolver Test Case" ) );
366
367     QString msg =
368         _( "<p>Use this to generate extensive logs to help tracking down bugs in the dependency resolver. "
369            "The logs will be stored in directory <br><tt>%1</tt></p>" ).arg( testCaseDir );
370
371     int button_no = QMessageBox::information( 0,                        // parent
372                                               _( "Solver Test Case" ),  // caption
373                                               heading + msg,
374                                               _( "C&ontinue" ),         // button #0
375                                               _( "&Cancel" ) );         // button #1
376
377     if ( button_no == 1 )       // Cancel
378         return;
379
380     y2milestone( "Generating solver test case START" );
381     bool success = zypp::getZYpp()->resolver()->createSolverTestcase( qPrintable(testCaseDir) );
382     y2milestone( "Generating solver test case END" );
383
384     if ( success )
385     {
386         msg =
387             _( "<p>Dependency resolver test case written to <br><tt>%1</tt></p>"
388                "<p>Prepare <tt>y2logs.tgz tar</tt> archive to attach to Bugzilla?</p>" ).arg( testCaseDir ),
389         button_no = QMessageBox::question( 0,                           // parent
390                                            _( "Success" ),              // caption
391                                            msg,
392                                            QMessageBox::Yes    | QMessageBox::Default,
393                                            QMessageBox::No,
394                                            QMessageBox::Cancel | QMessageBox::Escape );
395
396         if ( button_no & QMessageBox::Yes ) // really binary (not logical) '&' - QMessageBox::Default is still in there
397             YQUI::ui()->askSaveLogs();
398     }
399     else // no success
400     {
401         QMessageBox::warning( 0,                                        // parent
402                               _( "Error" ),                             // caption
403                               _( "<p><b>Error</b> creating dependency resolver test case</p>"
404                                  "<p>Please check disk space and permissions for <tt>%1</tt></p>" ).arg( testCaseDir ),
405                               QMessageBox::Ok | QMessageBox::Default,
406                               QMessageBox::NoButton,
407                               QMessageBox::NoButton );
408     }
409 }
410
411 void
412 YQPkgConflictDialog::keyPressEvent( QKeyEvent * event )
413 {
414     if ( event &&  event->key() == Qt::Key_Print )
415     {
416         YQUI::ui()->makeScreenShot( "" );
417         return;
418     }
419     QWidget::keyPressEvent( event );
420 }
421
422
423
424 #include "YQPkgConflictDialog.moc"