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