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