~ubuntu-core-dev/update-manager/main

« back to all changes in this revision

Viewing changes to DistUpgrade/DistUpgradeViewKDE.py

  • Committer: Michael Terry
  • Date: 2012-05-15 19:43:43 UTC
  • mto: This revision was merged to the branch mainline in revision 2428.
  • Revision ID: michael.terry@canonical.com-20120515194343-tocvo0awttmggie1
update several strings to match the software updater spec; most notably, rename from Update Manager to Software Updater

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# DistUpgradeViewKDE.py 
 
2
#  
 
3
#  Copyright (c) 2007 Canonical Ltd
 
4
#  
 
5
#  Author: Jonathan Riddell <jriddell@ubuntu.com>
 
6
 
7
#  This program is free software; you can redistribute it and/or 
 
8
#  modify it under the terms of the GNU General Public License as 
 
9
#  published by the Free Software Foundation; either version 2 of the
 
10
#  License, or (at your option) any later version.
 
11
 
12
#  This program is distributed in the hope that it will be useful,
 
13
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
#  GNU General Public License for more details.
 
16
 
17
#  You should have received a copy of the GNU General Public License
 
18
#  along with this program; if not, write to the Free Software
 
19
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 
20
#  USA
 
21
 
 
22
from __future__ import absolute_import, print_function
 
23
 
 
24
from PyQt4.QtCore import QUrl, Qt, SIGNAL, QTimer
 
25
from PyQt4.QtGui import (
 
26
    QDesktopServices, QDialog, QPixmap, QTreeWidgetItem, QMessageBox, 
 
27
    QApplication, QTextEdit, QTextOption, QTextCursor, QPushButton, 
 
28
    QWidget, QIcon, QHBoxLayout, QLabel
 
29
    )
 
30
from PyQt4 import uic
 
31
 
 
32
import sys
 
33
import logging
 
34
import time
 
35
import subprocess
 
36
import traceback
 
37
import tempfile
 
38
 
 
39
import apt
 
40
import apt_pkg
 
41
import os
 
42
import shutil
 
43
 
 
44
import pty
 
45
 
 
46
from .DistUpgradeApport import run_apport, apport_crash
 
47
 
 
48
from .DistUpgradeView import DistUpgradeView, FuzzyTimeToStr, InstallProgress, AcquireProgress
 
49
 
 
50
import select
 
51
import gettext
 
52
from .DistUpgradeGettext import gettext as gett
 
53
 
 
54
def _(str):
 
55
    return unicode(gett(str), 'UTF-8')
 
56
 
 
57
def utf8(str):
 
58
  if isinstance(str, unicode):
 
59
      return str
 
60
  return unicode(str, 'UTF-8')
 
61
 
 
62
def loadUi(file, parent):
 
63
    if os.path.exists(file):
 
64
        uic.loadUi(file, parent)
 
65
    else:
 
66
        #FIXME find file
 
67
        print("error, can't find file: " + file)
 
68
 
 
69
class DumbTerminal(QTextEdit):
 
70
    """ A very dumb terminal """
 
71
    def __init__(self, installProgress, parent_frame):
 
72
        " really dumb terminal with simple editing support "
 
73
        QTextEdit.__init__(self, "", parent_frame)
 
74
        self.installProgress = installProgress
 
75
        self.setFontFamily("Monospace")
 
76
        self.setFontPointSize(8)
 
77
        self.setWordWrapMode(QTextOption.NoWrap)
 
78
        self.setUndoRedoEnabled(False)
 
79
        self.setOverwriteMode(True)
 
80
        self._block = False
 
81
        #self.connect(self, SIGNAL("cursorPositionChanged()"), 
 
82
        #             self.onCursorPositionChanged)
 
83
 
 
84
    def fork(self):
 
85
        """pty voodoo"""
 
86
        (self.child_pid, self.installProgress.master_fd) = pty.fork()
 
87
        if self.child_pid == 0:
 
88
            os.environ["TERM"] = "dumb"
 
89
        return self.child_pid
 
90
 
 
91
    def update_interface(self):
 
92
        (rlist, wlist, xlist) = select.select([self.installProgress.master_fd],[],[], 0)
 
93
        if len(rlist) > 0:
 
94
            line = os.read(self.installProgress.master_fd, 255)
 
95
            self.insertWithTermCodes(utf8(line))
 
96
        QApplication.processEvents()
 
97
 
 
98
    def insertWithTermCodes(self, text):
 
99
        """ support basic terminal codes """
 
100
        display_text = ""
 
101
        for c in text:
 
102
            # \b - backspace - this seems to comes as "^H" now ??!
 
103
            if ord(c) == 8:
 
104
                self.insertPlainText(display_text)
 
105
                self.textCursor().deletePreviousChar()
 
106
                display_text=""
 
107
            # \r - is filtered out
 
108
            elif c == chr(13):
 
109
                pass
 
110
            # \a - bell - ignore for now
 
111
            elif c == chr(7):
 
112
                pass
 
113
            else:
 
114
                display_text += c
 
115
        self.insertPlainText(display_text)
 
116
 
 
117
    def keyPressEvent(self, ev):
 
118
        """ send (ascii) key events to the pty """
 
119
        # no master_fd yet
 
120
        if not hasattr(self.installProgress, "master_fd"):
 
121
            return
 
122
        # special handling for backspace
 
123
        if ev.key() == Qt.Key_Backspace:
 
124
            #print("sent backspace")
 
125
            os.write(self.installProgress.master_fd, chr(8))
 
126
            return
 
127
        # do nothing for events like "shift" 
 
128
        if not ev.text():
 
129
            return
 
130
        # now sent the key event to the termianl as utf-8
 
131
        os.write(self.installProgress.master_fd, ev.text().toUtf8())
 
132
 
 
133
    def onCursorPositionChanged(self):
 
134
        """ helper that ensures that the cursor is always at the end """
 
135
        if self._block:
 
136
            return
 
137
        # block signals so that we do not run into a recursion
 
138
        self._block = True
 
139
        self.moveCursor(QTextCursor.End)
 
140
        self._block = False
 
141
 
 
142
class KDECdromProgressAdapter(apt.progress.base.CdromProgress):
 
143
    """ Report the cdrom add progress """
 
144
    def __init__(self, parent):
 
145
        self.status = parent.window_main.label_status
 
146
        self.progressbar = parent.window_main.progressbar_cache
 
147
        self.parent = parent
 
148
 
 
149
    def update(self, text, step):
 
150
        """ update is called regularly so that the gui can be redrawn """
 
151
        if text:
 
152
          self.status.setText(text)
 
153
        self.progressbar.setValue(step/float(self.totalSteps))
 
154
        QApplication.processEvents()
 
155
 
 
156
    def ask_cdrom_name(self):
 
157
        return (False, "")
 
158
 
 
159
    def change_cdrom(self):
 
160
        return False
 
161
 
 
162
class KDEOpProgress(apt.progress.base.OpProgress):
 
163
  """ methods on the progress bar """
 
164
  def __init__(self, progressbar, progressbar_label):
 
165
      self.progressbar = progressbar
 
166
      self.progressbar_label = progressbar_label
 
167
      #self.progressbar.set_pulse_step(0.01)
 
168
      #self.progressbar.pulse()
 
169
 
 
170
  def update(self, percent):
 
171
      #if percent > 99:
 
172
      #    self.progressbar.set_fraction(1)
 
173
      #else:
 
174
      #    self.progressbar.pulse()
 
175
      #self.progressbar.set_fraction(percent/100.0)
 
176
      self.progressbar.setValue(percent)
 
177
      QApplication.processEvents()
 
178
 
 
179
  def done(self):
 
180
      self.progressbar_label.setText("")
 
181
 
 
182
class KDEAcquireProgressAdapter(AcquireProgress):
 
183
    """ methods for updating the progress bar while fetching packages """
 
184
    # FIXME: we really should have some sort of "we are at step"
 
185
    # xy in the gui
 
186
    # FIXME2: we need to thing about mediaCheck here too
 
187
    def __init__(self, parent):
 
188
        AcquireProgress.__init__(self)
 
189
        # if this is set to false the download will cancel
 
190
        self.status = parent.window_main.label_status
 
191
        self.progress = parent.window_main.progressbar_cache
 
192
        self.parent = parent
 
193
 
 
194
    def media_change(self, medium, drive):
 
195
      msg = _("Please insert '%s' into the drive '%s'") % (medium,drive)
 
196
      change = QMessageBox.question(self.parent.window_main, _("Media Change"), msg, QMessageBox.Ok, QMessageBox.Cancel)
 
197
      if change == QMessageBox.Ok:
 
198
        return True
 
199
      return False
 
200
 
 
201
    def start(self):
 
202
        AcquireProgress.start(self)
 
203
        #self.progress.show()
 
204
        self.progress.setValue(0)
 
205
        self.status.show()
 
206
 
 
207
    def stop(self):
 
208
        self.parent.window_main.progress_text.setText("  ")
 
209
        self.status.setText(_("Fetching is complete"))
 
210
 
 
211
    def pulse(self, owner):
 
212
        """ we don't have a mainloop in this application, we just call processEvents here and elsewhere"""
 
213
        # FIXME: move the status_str and progress_str into python-apt
 
214
        # (python-apt need i18n first for this)
 
215
        AcquireProgress.pulse(self, owner)
 
216
        self.progress.setValue(self.percent)
 
217
        current_item = self.current_items + 1
 
218
        if current_item > self.total_items:
 
219
            current_item = self.total_items
 
220
 
 
221
        if self.current_cps > 0:
 
222
            self.status.setText(_("Fetching file %li of %li at %sB/s") % (current_item, self.total_items, apt_pkg.size_to_str(self.current_cps)))
 
223
            self.parent.window_main.progress_text.setText("<i>" + _("About %s remaining") % unicode(FuzzyTimeToStr(self.eta), 'utf-8') + "</i>")
 
224
        else:
 
225
            self.status.setText(_("Fetching file %li of %li") % (current_item, self.total_items))
 
226
            self.parent.window_main.progress_text.setText("  ")
 
227
 
 
228
        QApplication.processEvents()
 
229
        return True
 
230
 
 
231
class KDEInstallProgressAdapter(InstallProgress):
 
232
    """methods for updating the progress bar while installing packages"""
 
233
    # timeout with no status change when the terminal is expanded
 
234
    # automatically
 
235
    TIMEOUT_TERMINAL_ACTIVITY = 240
 
236
 
 
237
    def __init__(self,parent):
 
238
        InstallProgress.__init__(self)
 
239
        self._cache = None
 
240
        self.label_status = parent.window_main.label_status
 
241
        self.progress = parent.window_main.progressbar_cache
 
242
        self.progress_text = parent.window_main.progress_text
 
243
        self.parent = parent
 
244
        try:
 
245
            self._terminal_log = open("/var/log/dist-upgrade/term.log","w")
 
246
        except Exception as e:
 
247
            # if something goes wrong (permission denied etc), use stdout
 
248
            logging.error("Can not open terminal log: '%s'" % e)
 
249
            self._terminal_log = sys.stdout
 
250
        # some options for dpkg to make it die less easily
 
251
        apt_pkg.config.set("DPkg::StopOnError","False")
 
252
 
 
253
    def start_update(self):
 
254
        InstallProgress.start_update(self)
 
255
        self.finished = False
 
256
        # FIXME: add support for the timeout
 
257
        # of the terminal (to display something useful then)
 
258
        # -> longer term, move this code into python-apt 
 
259
        self.label_status.setText(_("Applying changes"))
 
260
        self.progress.setValue(0)
 
261
        self.progress_text.setText(" ")
 
262
        # do a bit of time-keeping
 
263
        self.start_time = 0.0
 
264
        self.time_ui = 0.0
 
265
        self.last_activity = 0.0
 
266
        self.parent.window_main.showTerminalButton.setEnabled(True)
 
267
 
 
268
    def error(self, pkg, errormsg):
 
269
        InstallProgress.error(self, pkg, errormsg)
 
270
        logging.error("got an error from dpkg for pkg: '%s': '%s'" % (pkg, errormsg))
 
271
        # we do not report followup errors from earlier failures
 
272
        if gettext.dgettext('dpkg', "dependency problems - leaving unconfigured") in errormsg:
 
273
          return False
 
274
        summary = _("Could not install '%s'") % pkg
 
275
        msg = _("The upgrade will continue but the '%s' package may not "
 
276
                "be in a working state. Please consider submitting a "
 
277
                "bug report about it.") % pkg
 
278
        msg = "<big><b>%s</b></big><br />%s" % (summary, msg)
 
279
 
 
280
        dialogue = QDialog(self.parent.window_main)
 
281
        loadUi("dialog_error.ui", dialogue)
 
282
        self.parent.translate_widget_children(dialogue)
 
283
        dialogue.label_error.setText(utf8(msg))
 
284
        if errormsg != None:
 
285
            dialogue.textview_error.setText(utf8(errormsg))
 
286
            dialogue.textview_error.show()
 
287
        else:
 
288
            dialogue.textview_error.hide()
 
289
        dialogue.connect(dialogue.button_bugreport, SIGNAL("clicked()"), self.parent.reportBug)
 
290
        dialogue.exec_()
 
291
 
 
292
    def conffile(self, current, new):
 
293
        """ask question in case conffile has been changed by user"""
 
294
        logging.debug("got a conffile-prompt from dpkg for file: '%s'" % current)
 
295
        start = time.time()
 
296
        prim = _("Replace the customized configuration file\n'%s'?") % current
 
297
        sec = _("You will lose any changes you have made to this "
 
298
                "configuration file if you choose to replace it with "
 
299
                "a newer version.")
 
300
        markup = "<span weight=\"bold\" size=\"larger\">%s </span> \n\n%s" % (prim, sec)
 
301
        self.confDialogue = QDialog(self.parent.window_main)
 
302
        loadUi("dialog_conffile.ui", self.confDialogue)
 
303
        self.confDialogue.label_conffile.setText(markup)
 
304
        self.confDialogue.textview_conffile.hide()
 
305
        #FIXME, below to be tested
 
306
        #self.confDialogue.resize(self.confDialogue.minimumSizeHint())
 
307
        self.confDialogue.connect(self.confDialogue.show_difference_button, SIGNAL("clicked()"), self.showConffile)
 
308
 
 
309
        # workaround silly dpkg 
 
310
        if not os.path.exists(current):
 
311
          current = current+".dpkg-dist"
 
312
 
 
313
        # now get the diff
 
314
        if os.path.exists("/usr/bin/diff"):
 
315
          cmd = ["/usr/bin/diff", "-u", current, new]
 
316
          diff = utf8(subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0])
 
317
          self.confDialogue.textview_conffile.setText(diff)
 
318
        else:
 
319
          self.confDialogue.textview_conffile.setText(_("The 'diff' command was not found"))
 
320
        result = self.confDialogue.exec_()
 
321
        self.time_ui += time.time() - start
 
322
        # if replace, send this to the terminal
 
323
        if result == QDialog.Accepted:
 
324
            os.write(self.master_fd, "y\n")
 
325
        else:
 
326
            os.write(self.master_fd, "n\n")
 
327
 
 
328
    def showConffile(self):
 
329
        if self.confDialogue.textview_conffile.isVisible():
 
330
            self.confDialogue.textview_conffile.hide()
 
331
            self.confDialogue.show_difference_button.setText(_("Show Difference >>>"))
 
332
        else:
 
333
            self.confDialogue.textview_conffile.show()
 
334
            self.confDialogue.show_difference_button.setText(_("<<< Hide Difference"))
 
335
 
 
336
    def fork(self):
 
337
        """pty voodoo"""
 
338
        (self.child_pid, self.master_fd) = pty.fork()
 
339
        if self.child_pid == 0:
 
340
            os.environ["TERM"] = "dumb"
 
341
            if (not os.environ.has_key("DEBIAN_FRONTEND") or
 
342
                os.environ["DEBIAN_FRONTEND"] == "kde"):
 
343
                os.environ["DEBIAN_FRONTEND"] = "noninteractive"
 
344
            os.environ["APT_LISTCHANGES_FRONTEND"] = "none"
 
345
        logging.debug(" fork pid is: %s" % self.child_pid)
 
346
        return self.child_pid
 
347
 
 
348
    def status_change(self, pkg, percent, status):
 
349
        """update progress bar and label"""
 
350
        # start the timer when the first package changes its status
 
351
        if self.start_time == 0.0:
 
352
          #print("setting start time to %s" % self.start_time)
 
353
          self.start_time = time.time()
 
354
        self.progress.setValue(self.percent)
 
355
        self.label_status.setText(unicode(status.strip(), 'UTF-8'))
 
356
        # start showing when we gathered some data
 
357
        if percent > 1.0:
 
358
          self.last_activity = time.time()
 
359
          self.activity_timeout_reported = False
 
360
          delta = self.last_activity - self.start_time
 
361
          # time wasted in conffile questions (or other ui activity)
 
362
          delta -= self.time_ui
 
363
          time_per_percent = (float(delta)/percent)
 
364
          eta = (100.0 - self.percent) * time_per_percent
 
365
          # only show if we have some sensible data (60sec < eta < 2days)
 
366
          if eta > 61.0 and eta < (60*60*24*2):
 
367
            self.progress_text.setText(_("About %s remaining") % FuzzyTimeToStr(eta))
 
368
          else:
 
369
            self.progress_text.setText(" ")
 
370
 
 
371
    def finish_update(self):
 
372
        self.label_status.setText("")
 
373
 
 
374
    def update_interface(self):
 
375
        """
 
376
        no mainloop in this application, just call processEvents lots here
 
377
        it's also important to sleep for a minimum amount of time
 
378
        """
 
379
        # log the output of dpkg (on the master_fd) to the terminal log
 
380
        while True:
 
381
            try:
 
382
                (rlist, wlist, xlist) = select.select([self.master_fd],[],[], 0)
 
383
                if len(rlist) > 0:
 
384
                    line = os.read(self.master_fd, 255)
 
385
                    self._terminal_log.write(line)
 
386
                    self.parent.terminal_text.insertWithTermCodes(utf8(line))
 
387
                else:
 
388
                    break
 
389
            except Exception as e:
 
390
                print(e)
 
391
                logging.debug("error reading from self.master_fd '%s'" % e)
 
392
                break
 
393
 
 
394
        # now update the GUI
 
395
        try:
 
396
          InstallProgress.update_interface(self)
 
397
        except ValueError as e:
 
398
          logging.error("got ValueError from InstallProgress.update_interface. Line was '%s' (%s)" % (self.read, e))
 
399
          # reset self.read so that it can continue reading and does not loop
 
400
          self.read = ""
 
401
        # check about terminal activity
 
402
        if self.last_activity > 0 and \
 
403
           (self.last_activity + self.TIMEOUT_TERMINAL_ACTIVITY) < time.time():
 
404
          if not self.activity_timeout_reported:
 
405
            #FIXME bug 95465, I can't recreate this, so here's a hacky fix
 
406
            try:
 
407
                logging.warning("no activity on terminal for %s seconds (%s)" % (self.TIMEOUT_TERMINAL_ACTIVITY, self.label_status.text()))
 
408
            except UnicodeEncodeError:
 
409
                logging.warning("no activity on terminal for %s seconds" % (self.TIMEOUT_TERMINAL_ACTIVITY))
 
410
            self.activity_timeout_reported = True
 
411
          self.parent.window_main.konsole_frame.show()
 
412
        QApplication.processEvents()
 
413
        time.sleep(0.02)
 
414
 
 
415
    def wait_child(self):
 
416
        while True:
 
417
            self.update_interface()
 
418
            (pid, res) = os.waitpid(self.child_pid,os.WNOHANG)
 
419
            if pid == self.child_pid:
 
420
                break
 
421
        # we need the full status here (not just WEXITSTATUS)
 
422
        return res
 
423
 
 
424
# inherit from the class created in window_main.ui
 
425
# to add the handler for closing the window
 
426
class UpgraderMainWindow(QWidget):
 
427
 
 
428
    def __init__(self):
 
429
        QWidget.__init__(self)
 
430
        #uic.loadUi("window_main.ui", self)
 
431
        loadUi("window_main.ui", self)
 
432
 
 
433
    def setParent(self, parentRef):
 
434
        self.parent = parentRef
 
435
 
 
436
    def closeEvent(self, event):
 
437
        close = self.parent.on_window_main_delete_event()
 
438
        if close:
 
439
            event.accept()
 
440
        else:
 
441
            event.ignore()
 
442
 
 
443
class DistUpgradeViewKDE(DistUpgradeView):
 
444
    """KDE frontend of the distUpgrade tool"""
 
445
    def __init__(self, datadir=None, logdir=None):
 
446
        DistUpgradeView.__init__(self)
 
447
        # silence the PyQt4 logger
 
448
        logger = logging.getLogger("PyQt4")
 
449
        logger.setLevel(logging.INFO)
 
450
        if not datadir:
 
451
          localedir=os.path.join(os.getcwd(),"mo")
 
452
        else:
 
453
          localedir="/usr/share/locale/update-manager"
 
454
 
 
455
        # FIXME: i18n must be somewhere relative do this dir
 
456
        try:
 
457
          gettext.bindtextdomain("update-manager", localedir)
 
458
          gettext.textdomain("update-manager")
 
459
        except Exception as e:
 
460
          logging.warning("Error setting locales (%s)" % e)
 
461
 
 
462
        #about = KAboutData("adept_manager","Upgrader","0.1","Dist Upgrade Tool for Kubuntu",KAboutData.License_GPL,"(c) 2007 Canonical Ltd",
 
463
        #"http://wiki.kubuntu.org/KubuntuUpdateManager", "jriddell@ubuntu.com")
 
464
        #about.addAuthor("Jonathan Riddell", None,"jriddell@ubuntu.com")
 
465
        #about.addAuthor("Michael Vogt", None,"michael.vogt@ubuntu.com")
 
466
        #KCmdLineArgs.init(["./dist-upgrade.py"],about)
 
467
 
 
468
        # we test for DISPLAY here, QApplication does not throw a 
 
469
        # exception when run without DISPLAY but dies instead
 
470
        if not "DISPLAY" in os.environ:
 
471
            raise Exception("No DISPLAY in os.environ found")
 
472
        self.app = QApplication(["update-manager"])
 
473
 
 
474
        if os.path.exists("/usr/share/icons/oxygen/48x48/apps/system-software-update.png"):
 
475
            messageIcon = QPixmap("/usr/share/icons/oxygen/48x48/apps/system-software-update.png")
 
476
        else:
 
477
            messageIcon = QPixmap("/usr/share/icons/hicolor/48x48/apps/adept_manager.png")
 
478
        self.app.setWindowIcon(QIcon(messageIcon))
 
479
 
 
480
        self.window_main = UpgraderMainWindow()
 
481
        self.window_main.setParent(self)
 
482
        self.window_main.show()
 
483
 
 
484
        self.prev_step = 0 # keep a record of the latest step
 
485
 
 
486
        self._opCacheProgress = KDEOpProgress(self.window_main.progressbar_cache, self.window_main.progress_text)
 
487
        self._acquireProgress = KDEAcquireProgressAdapter(self)
 
488
        self._cdromProgress = KDECdromProgressAdapter(self)
 
489
 
 
490
        self._installProgress = KDEInstallProgressAdapter(self)
 
491
 
 
492
        # reasonable fault handler
 
493
        sys.excepthook = self._handleException
 
494
 
 
495
        self.window_main.showTerminalButton.setEnabled(False)
 
496
        self.app.connect(self.window_main.showTerminalButton, SIGNAL("clicked()"), self.showTerminal)
 
497
 
 
498
        #kdesu requires us to copy the xauthority file before it removes it when Adept is killed
 
499
        fd, copyXauth = tempfile.mkstemp("", "adept")
 
500
        if 'XAUTHORITY' in os.environ and os.environ['XAUTHORITY'] != copyXauth:
 
501
            shutil.copy(os.environ['XAUTHORITY'], copyXauth)
 
502
            os.environ["XAUTHORITY"] = copyXauth
 
503
 
 
504
        # Note that with kdesudo this needs --nonewdcop
 
505
        ## create a new DCOP-Client:
 
506
        #client = DCOPClient()
 
507
        ## connect the client to the local DCOP-server:
 
508
        #client.attach()
 
509
 
 
510
        #for qcstring_app in client.registeredApplications():
 
511
        #    app = str(qcstring_app)
 
512
        #    if app.startswith("adept"): 
 
513
        #        adept = DCOPApp(qcstring_app, client)
 
514
        #        adeptInterface = adept.object("MainApplication-Interface")
 
515
        #        adeptInterface.quit()
 
516
 
 
517
        # This works just as well
 
518
        subprocess.call(["killall", "adept_manager"])
 
519
        subprocess.call(["killall", "adept_updater"])
 
520
 
 
521
        # init gettext
 
522
        gettext.bindtextdomain("update-manager",localedir)
 
523
        gettext.textdomain("update-manager")
 
524
        self.translate_widget_children()
 
525
        self.window_main.label_title.setText(self.window_main.label_title.text().replace("Ubuntu", "Kubuntu"))
 
526
 
 
527
        # setup terminal text in hidden by default spot
 
528
        self.window_main.konsole_frame.hide()
 
529
        self.konsole_frame_layout = QHBoxLayout(self.window_main.konsole_frame)
 
530
        self.window_main.konsole_frame.setMinimumSize(600, 400)
 
531
        self.terminal_text = DumbTerminal(self._installProgress, self.window_main.konsole_frame)
 
532
        self.konsole_frame_layout.addWidget(self.terminal_text)
 
533
        self.terminal_text.show()
 
534
 
 
535
        # for some reason we need to start the main loop to get everything displayed
 
536
        # this app mostly works with processEvents but run main loop briefly to keep it happily displaying all widgets
 
537
        QTimer.singleShot(10, self.exitMainLoop)
 
538
        self.app.exec_()
 
539
 
 
540
    def exitMainLoop(self):
 
541
        print("exitMainLoop")
 
542
        self.app.exit()
 
543
 
 
544
    def translate_widget_children(self, parentWidget=None):
 
545
        if parentWidget == None:
 
546
            parentWidget = self.window_main
 
547
        if isinstance(parentWidget, QDialog) or isinstance(parentWidget, QWidget):
 
548
            if str(parentWidget.windowTitle()) == "Error":
 
549
                parentWidget.setWindowTitle( gettext.dgettext("kdelibs", "Error"))
 
550
            else:
 
551
                parentWidget.setWindowTitle(_( str(parentWidget.windowTitle()) ))
 
552
 
 
553
        if parentWidget.children() != None:
 
554
            for widget in parentWidget.children():
 
555
                self.translate_widget(widget)
 
556
                self.translate_widget_children(widget)
 
557
 
 
558
    def translate_widget(self, widget):
 
559
        if isinstance(widget, QLabel) or isinstance(widget, QPushButton):
 
560
            if str(widget.text()) == "&Cancel":
 
561
                widget.setText(unicode(gettext.dgettext("kdelibs", "&Cancel"), 'UTF-8'))
 
562
            elif str(widget.text()) == "&Close":
 
563
                widget.setText(unicode(gettext.dgettext("kdelibs", "&Close"), 'UTF-8'))
 
564
            elif str(widget.text()) != "":
 
565
                widget.setText( _(str(widget.text())).replace("_", "&") )
 
566
 
 
567
    def _handleException(self, exctype, excvalue, exctb):
 
568
        """Crash handler."""
 
569
 
 
570
        if (issubclass(exctype, KeyboardInterrupt) or
 
571
            issubclass(exctype, SystemExit)):
 
572
            return
 
573
 
 
574
        # we handle the exception here, hand it to apport and run the
 
575
        # apport gui manually after it because we kill u-m during the upgrade
 
576
        # to prevent it from popping up for reboot notifications or FF restart
 
577
        # notifications or somesuch
 
578
        lines = traceback.format_exception(exctype, excvalue, exctb)
 
579
        logging.error("not handled exception in KDE frontend:\n%s" % "\n".join(lines))
 
580
        # we can't be sure that apport will run in the middle of a upgrade
 
581
        # so we still show a error message here
 
582
        apport_crash(exctype, excvalue, exctb)
 
583
        if not run_apport():
 
584
            tbtext = ''.join(traceback.format_exception(exctype, excvalue, exctb))
 
585
            dialog = QDialog(self.window_main)
 
586
            loadUi("dialog_error.ui", dialog)
 
587
            self.translate_widget_children(self.dialog)
 
588
            #FIXME make URL work
 
589
            #dialog.connect(dialog.beastie_url, SIGNAL("leftClickedURL(const QString&)"), self.openURL)
 
590
            dialog.crash_detail.setText(tbtext)
 
591
            dialog.exec_()
 
592
        sys.exit(1)
 
593
 
 
594
    def openURL(self, url):
 
595
        """start konqueror"""
 
596
        #need to run this else kdesu can't run Konqueror
 
597
        #subprocess.call(['su', 'ubuntu', 'xhost', '+localhost'])
 
598
        QDesktopServices.openUrl(QUrl(url))
 
599
 
 
600
    def reportBug(self):
 
601
        """start konqueror"""
 
602
        #need to run this else kdesu can't run Konqueror
 
603
        #subprocess.call(['su', 'ubuntu', 'xhost', '+localhost'])
 
604
        QDesktopServices.openUrl(QUrl("https://launchpad.net/ubuntu/+source/update-manager/+filebug"))
 
605
 
 
606
    def showTerminal(self):
 
607
        if self.window_main.konsole_frame.isVisible():
 
608
            self.window_main.konsole_frame.hide()
 
609
            self.window_main.showTerminalButton.setText(_("Show Terminal >>>"))
 
610
        else:
 
611
            self.window_main.konsole_frame.show()
 
612
            self.window_main.showTerminalButton.setText(_("<<< Hide Terminal"))
 
613
        self.window_main.resize(self.window_main.sizeHint())
 
614
 
 
615
    def getAcquireProgress(self):
 
616
        return self._acquireProgress
 
617
 
 
618
    def getInstallProgress(self, cache):
 
619
        self._installProgress._cache = cache
 
620
        return self._installProgress
 
621
 
 
622
    def getOpCacheProgress(self):
 
623
        return self._opCacheProgress
 
624
 
 
625
    def getCdromProgress(self):
 
626
        return self._cdromProgress
 
627
 
 
628
    def update_status(self, msg):
 
629
        self.window_main.label_status.setText(utf8(msg))
 
630
 
 
631
    def hideStep(self, step):
 
632
        image = getattr(self.window_main,"image_step%i" % step)
 
633
        label = getattr(self.window_main,"label_step%i" % step)
 
634
        image.hide()
 
635
        label.hide()
 
636
 
 
637
    def abort(self):
 
638
        step = self.prev_step
 
639
        if step > 0:
 
640
            image = getattr(self.window_main,"image_step%i" % step)
 
641
            if os.path.exists("/usr/share/icons/oxygen/16x16/actions/dialog-cancel.png"):
 
642
                cancelIcon = QPixmap("/usr/share/icons/oxygen/16x16/actions/dialog-cancel.png")
 
643
            elif os.path.exists("/usr/lib/kde4/share/icons/oxygen/16x16/actions/dialog-cancel.png"):
 
644
                cancelIcon = QPixmap("/usr/lib/kde4/share/icons/oxygen/16x16/actions/dialog-cancel.png")
 
645
            else:
 
646
                cancelIcon = QPixmap("/usr/share/icons/crystalsvg/16x16/actions/cancel.png")
 
647
            image.setPixmap(cancelIcon)
 
648
            image.show()
 
649
 
 
650
    def setStep(self, step):
 
651
        if os.path.exists("/usr/share/icons/oxygen/16x16/actions/dialog-ok.png"):
 
652
            okIcon = QPixmap("/usr/share/icons/oxygen/16x16/actions/dialog-ok.png")
 
653
        elif os.path.exists("/usr/lib/kde4/share/icons/oxygen/16x16/actions/dialog-ok.png"):
 
654
            okIcon = QPixmap("/usr/lib/kde4/share/icons/oxygen/16x16/actions/dialog-ok.png")
 
655
        else:
 
656
            okIcon = QPixmap("/usr/share/icons/crystalsvg/16x16/actions/ok.png")
 
657
 
 
658
        if os.path.exists("/usr/share/icons/oxygen/16x16/actions/arrow-right.png"):
 
659
            arrowIcon = QPixmap("/usr/share/icons/oxygen/16x16/actions/arrow-right.png")
 
660
        elif os.path.exists("/usr/lib/kde4/share/icons/oxygen/16x16/actions/arrow-right.png"):
 
661
            arrowIcon = QPixmap("/usr/lib/kde4/share/icons/oxygen/16x16/actions/arrow-right.png")
 
662
        else:
 
663
            arrowIcon = QPixmap("/usr/share/icons/crystalsvg/16x16/actions/1rightarrow.png")
 
664
 
 
665
        if self.prev_step:
 
666
            image = getattr(self.window_main,"image_step%i" % self.prev_step)
 
667
            label = getattr(self.window_main,"label_step%i" % self.prev_step)
 
668
            image.setPixmap(okIcon)
 
669
            image.show()
 
670
            ##arrow.hide()
 
671
        self.prev_step = step
 
672
        # show the an arrow for the current step and make the label bold
 
673
        image = getattr(self.window_main,"image_step%i" % step)
 
674
        label = getattr(self.window_main,"label_step%i" % step)
 
675
        image.setPixmap(arrowIcon)
 
676
        image.show()
 
677
        label.setText("<b>" + label.text() + "</b>")
 
678
 
 
679
    def information(self, summary, msg, extended_msg=None):
 
680
        msg = "<big><b>%s</b></big><br />%s" % (summary,msg)
 
681
 
 
682
        dialogue = QDialog(self.window_main)
 
683
        loadUi("dialog_error.ui", dialogue)
 
684
        self.translate_widget_children(dialogue)
 
685
        dialogue.label_error.setText(utf8(msg))
 
686
        if extended_msg != None:
 
687
            dialogue.textview_error.setText(utf8(extended_msg))
 
688
            dialogue.textview_error.show()
 
689
        else:
 
690
            dialogue.textview_error.hide()
 
691
        dialogue.button_bugreport.hide()
 
692
        dialogue.setWindowTitle(_("Information"))
 
693
 
 
694
        if os.path.exists("/usr/share/icons/oxygen/48x48/status/dialog-information.png"):
 
695
            messageIcon = QPixmap("/usr/share/icons/oxygen/48x48/status/dialog-information.png")
 
696
        elif os.path.exists("/usr/lib/kde4/share/icons/oxygen/48x48/status/dialog-information.png"):
 
697
            messageIcon = QPixmap("/usr/lib/kde4/share/icons/oxygen/48x48/status/dialog-information.png")
 
698
        else:
 
699
            messageIcon = QPixmap("/usr/share/icons/crystalsvg/32x32/actions/messagebox_info.png")
 
700
        dialogue.image.setPixmap(messageIcon)
 
701
        dialogue.exec_()
 
702
 
 
703
    def error(self, summary, msg, extended_msg=None):
 
704
        msg="<big><b>%s</b></big><br />%s" % (summary, msg)
 
705
 
 
706
        dialogue = QDialog(self.window_main)
 
707
        loadUi("dialog_error.ui", dialogue)
 
708
        self.translate_widget_children(dialogue)
 
709
        dialogue.label_error.setText(utf8(msg))
 
710
        if extended_msg != None:
 
711
            dialogue.textview_error.setText(utf8(extended_msg))
 
712
            dialogue.textview_error.show()
 
713
        else:
 
714
            dialogue.textview_error.hide()
 
715
        dialogue.button_close.show()
 
716
        self.app.connect(dialogue.button_bugreport, SIGNAL("clicked()"), self.reportBug)
 
717
 
 
718
        if os.path.exists("/usr/share/icons/oxygen/48x48/status/dialog-error.png"):
 
719
            messageIcon = QPixmap("/usr/share/icons/oxygen/48x48/status/dialog-error.png")
 
720
        elif os.path.exists("/usr/lib/kde4/share/icons/oxygen/48x48/status/dialog-error.png"):
 
721
            messageIcon = QPixmap("/usr/lib/kde4/share/icons/oxygen/48x48/status/dialog-error.png")
 
722
        else:
 
723
            messageIcon = QPixmap("/usr/share/icons/crystalsvg/32x32/actions/messagebox_critical.png")
 
724
        dialogue.image.setPixmap(messageIcon)
 
725
        dialogue.exec_()
 
726
 
 
727
        return False
 
728
 
 
729
    def confirmChanges(self, summary, changes, demotions, downloadSize, 
 
730
                       actions=None, removal_bold=True):
 
731
        """show the changes dialogue"""
 
732
        # FIXME: add a whitelist here for packages that we expect to be
 
733
        # removed (how to calc this automatically?)
 
734
        DistUpgradeView.confirmChanges(self, summary, changes, demotions, 
 
735
                                       downloadSize)
 
736
        msg = unicode(self.confirmChangesMessage, 'UTF-8')
 
737
        self.changesDialogue = QDialog(self.window_main)
 
738
        loadUi("dialog_changes.ui", self.changesDialogue)
 
739
 
 
740
        self.changesDialogue.treeview_details.hide()
 
741
        self.changesDialogue.connect(self.changesDialogue.show_details_button, SIGNAL("clicked()"), self.showChangesDialogueDetails)
 
742
        self.translate_widget_children(self.changesDialogue)
 
743
        self.changesDialogue.show_details_button.setText(_("Details") + " >>>")
 
744
        self.changesDialogue.resize(self.changesDialogue.sizeHint())
 
745
 
 
746
        if os.path.exists("/usr/share/icons/oxygen/48x48/status/dialog-warning.png"):
 
747
            warningIcon = QPixmap("/usr/share/icons/oxygen/48x48/status/dialog-warning.png")
 
748
        elif os.path.exists("/usr/lib/kde4/share/icons/oxygen/48x48/status/dialog-warning.png"):
 
749
            warningIcon = QPixmap("/usr/lib/kde4/share/icons/oxygen/48x48/status/dialog-warning.png")
 
750
        else:
 
751
            warningIcon = QPixmap("/usr/share/icons/crystalsvg/32x32/actions/messagebox_warning.png")
 
752
 
 
753
        self.changesDialogue.question_pixmap.setPixmap(warningIcon)
 
754
 
 
755
        if actions != None:
 
756
            cancel = actions[0].replace("_", "")
 
757
            self.changesDialogue.button_cancel_changes.setText(cancel)
 
758
            confirm = actions[1].replace("_", "")
 
759
            self.changesDialogue.button_confirm_changes.setText(confirm)
 
760
 
 
761
        summaryText = unicode("<big><b>%s</b></big>" % summary, 'UTF-8')
 
762
        self.changesDialogue.label_summary.setText(summaryText)
 
763
        self.changesDialogue.label_changes.setText(msg)
 
764
        # fill in the details
 
765
        self.changesDialogue.treeview_details.clear()
 
766
        self.changesDialogue.treeview_details.setHeaderLabels(["Packages"])
 
767
        self.changesDialogue.treeview_details.header().hide()
 
768
        for demoted in self.demotions:
 
769
            self.changesDialogue.treeview_details.insertTopLevelItem(0, QTreeWidgetItem(self.changesDialogue.treeview_details, [_("No longer supported %s") % demoted.name]) )
 
770
        for rm in self.toRemove:
 
771
            self.changesDialogue.treeview_details.insertTopLevelItem(0, QTreeWidgetItem(self.changesDialogue.treeview_details, [_("Remove %s") % rm.name]) )
 
772
        for rm in self.toRemoveAuto:
 
773
            self.changesDialogue.treeview_details.insertTopLevelItem(0, QTreeWidgetItem(self.changesDialogue.treeview_details, [_("Remove (was auto installed) %s") % rm.name]) )
 
774
        for inst in self.toInstall:
 
775
            self.changesDialogue.treeview_details.insertTopLevelItem(0, QTreeWidgetItem(self.changesDialogue.treeview_details, [_("Install %s") % inst.name]) )
 
776
        for up in self.toUpgrade:
 
777
            self.changesDialogue.treeview_details.insertTopLevelItem(0, QTreeWidgetItem(self.changesDialogue.treeview_details, [_("Upgrade %s") % up.name]) )
 
778
 
 
779
        #FIXME resize label, stop it being shrinkable
 
780
        res = self.changesDialogue.exec_()
 
781
        if res == QDialog.Accepted:
 
782
            return True
 
783
        return False
 
784
 
 
785
    def showChangesDialogueDetails(self):
 
786
        if self.changesDialogue.treeview_details.isVisible():
 
787
            self.changesDialogue.treeview_details.hide()
 
788
            self.changesDialogue.show_details_button.setText(_("Details") + " >>>")
 
789
        else:
 
790
            self.changesDialogue.treeview_details.show()
 
791
            self.changesDialogue.show_details_button.setText("<<< " + _("Details"))
 
792
        self.changesDialogue.resize(self.changesDialogue.sizeHint())
 
793
 
 
794
    def askYesNoQuestion(self, summary, msg, default='No'):
 
795
        answer = QMessageBox.question(self.window_main, unicode(summary, 'UTF-8'), unicode("<font>") + unicode(msg, 'UTF-8'), QMessageBox.Yes|QMessageBox.No, QMessageBox.No)
 
796
        if answer == QMessageBox.Yes:
 
797
            return True
 
798
        return False
 
799
 
 
800
    def confirmRestart(self):
 
801
        messageBox = QMessageBox(QMessageBox.Question, _("Restart required"), _("<b><big>Restart the system to complete the upgrade</big></b>"), QMessageBox.NoButton, self.window_main)
 
802
        yesButton = messageBox.addButton(QMessageBox.Yes)
 
803
        noButton = messageBox.addButton(QMessageBox.No)
 
804
        yesButton.setText(_("_Restart Now").replace("_", "&"))
 
805
        noButton.setText(gettext.dgettext("kdelibs", "&Close"))
 
806
        answer = messageBox.exec_()
 
807
        if answer == QMessageBox.Yes:
 
808
            return True
 
809
        return False
 
810
 
 
811
    def processEvents(self):
 
812
        QApplication.processEvents()
 
813
 
 
814
    def pulseProgress(self, finished=False):
 
815
        # FIXME: currently we do nothing here because this is
 
816
        # run in a different python thread and QT explodes if the UI is
 
817
        # touched from a non QThread
 
818
        pass
 
819
 
 
820
    def on_window_main_delete_event(self):
 
821
        #FIXME make this user friendly
 
822
        text = _("""<b><big>Cancel the running upgrade?</big></b>
 
823
 
 
824
The system could be in an unusable state if you cancel the upgrade. You are strongly advised to resume the upgrade.""")
 
825
        text = text.replace("\n", "<br />")
 
826
        cancel = QMessageBox.warning(self.window_main, _("Cancel Upgrade?"), text, QMessageBox.Yes, QMessageBox.No)
 
827
        if cancel == QMessageBox.Yes:
 
828
            return True
 
829
        return False
 
830
 
 
831
if __name__ == "__main__":
 
832
  
 
833
  view = DistUpgradeViewKDE()
 
834
  view.askYesNoQuestion("input box test","bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar ")
 
835
 
 
836
  if sys.argv[1] == "--test-term":
 
837
      pid = view.terminal_text.fork()
 
838
      if pid == 0:
 
839
          subprocess.call(["bash"])
 
840
          sys.exit()
 
841
      while True:
 
842
          view.terminal_text.update_interface()
 
843
          QApplication.processEvents()
 
844
          time.sleep(0.01)
 
845
 
 
846
  if sys.argv[1] == "--show-in-terminal":
 
847
      for c in open(sys.argv[2]).read():
 
848
          view.terminal_text.insertWithTermCodes( c )
 
849
          #print(c, ord(c))
 
850
          QApplication.processEvents()
 
851
          time.sleep(0.05)
 
852
      while True:
 
853
          QApplication.processEvents()
 
854
 
 
855
  cache = apt.Cache()
 
856
  for pkg in sys.argv[1:]:
 
857
    if cache[pkg].is_installed and not cache[pkg].isUpgradable: 
 
858
      cache[pkg].mark_delete(purge=True)
 
859
    else:
 
860
      cache[pkg].mark_install()
 
861
  cache.commit(view._acquireProgress,view._installProgress)
 
862
 
 
863
  # keep the window open
 
864
  while True:
 
865
      QApplication.processEvents()