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