~blueyed/apport/bug532944-lucid

« back to all changes in this revision

Viewing changes to kde/apport-kde

  • Committer: Martin Pitt
  • Date: 2009-06-26 08:41:37 UTC
  • mfrom: (1369.1.111 trunk)
  • Revision ID: martin.pitt@canonical.com-20090626084137-htg4q8ftjcs29ll5
* New upstream release. Compared to our previous snapshot, this changes:
  - Replace Qt4 frontend with KDE frontend, thanks to Richard Johnson!
  - apport/ui.py, run_report_bug(): Clean up PID information collection.
  - gtk/apport-gtk.ui: Drop invalid icon reference. (LP: #389064)
  - ui.py: Do not reject non-distro package reports if report sets CrashDB
    (for third-party destination). (LP: #391015)
  - bin/kernel_crashdump: Use packaging API properly.
  - apport-gtk.ui: Drop erroneous translatable flag from stock buttons.
  - Update German translations.
* debian/*: qt → kde, add transitional package for apport-qt.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
 
 
3
''' KDE 4 Apport User Interface
 
4
 
 
5
Copyright (C) 2007-2009 Canonical Ltd.
 
6
Author: Richard A. Johnson <nixternal@ubuntu.com>
 
7
 
 
8
This program is free software; you can redistribute it and/or modify it
 
9
under the terms of the GNU General Public License as published by the
 
10
Free Software Foundation; either version 2 of the License, or (at your
 
11
option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
 
12
the full text of the license.
 
13
'''
 
14
 
 
15
import os.path
 
16
import subprocess
 
17
import sys
 
18
 
 
19
try:
 
20
    from PyQt4.QtCore import *
 
21
    from PyQt4.QtGui import (QDialog, QLabel, QCheckBox, QRadioButton,
 
22
                             QTreeWidget, QTreeWidgetItem, QMessageBox,
 
23
                             QVBoxLayout, QFileDialog, QDialogButtonBox,
 
24
                             QProgressBar, QGroupBox)
 
25
    from PyQt4 import uic
 
26
    from PyKDE4.kdecore import (i18n, ki18n, KAboutData, KCmdLineArgs,
 
27
                                KLocalizedString)
 
28
    from PyKDE4.kdeui import (KApplication, KNotification, KMessageBox, KIcon,
 
29
                              KStandardGuiItem)
 
30
    import apport.ui
 
31
except ImportError, e:
 
32
    # this can happen while upgrading python packages
 
33
    print >> sys.stderr, 'Could not import module, is a package upgrade in progress? Error:', e
 
34
    sys.exit(1)
 
35
 
 
36
def translate(self, prop):
 
37
    ''' Reimplement method from uic to change it to use gettext '''
 
38
    if prop.get("notr", None) == "true":
 
39
        return self._cstring(prop)
 
40
    else:
 
41
        if prop.text is None:
 
42
            return ""
 
43
        text = prop.text.encode("UTF-8")
 
44
        return i18n(text)
 
45
 
 
46
uic.properties.Properties._string = translate
 
47
 
 
48
class Dialog(QDialog):
 
49
    ''' Main dialog wrapper '''
 
50
 
 
51
    def __init__(self, ui, title, heading, text):
 
52
        QDialog.__init__(self, None, Qt.Window)
 
53
 
 
54
        uic.loadUi(os.path.join(os.path.dirname(sys.argv[0]), ui), self)
 
55
 
 
56
        self.setWindowTitle(title)
 
57
        if self.findChild(QLabel, 'heading'):
 
58
            self.findChild(QLabel, 'heading').setText('<h2>%s</h2>' % heading)
 
59
        self.findChild(QLabel, 'text').setText(text)
 
60
 
 
61
    def on_buttons_clicked(self, button):
 
62
        self.actionbutton = button
 
63
        if self.sender().buttonRole(button) == QDialogButtonBox.ActionRole:
 
64
            button.window().done(2)
 
65
    
 
66
    def addbutton(self, button):
 
67
        return self.findChild(QDialogButtonBox, 'buttons').addButton(button,
 
68
                QDialogButtonBox.ActionRole)
 
69
 
 
70
class ErrorDialog(Dialog):
 
71
    ''' Error dialog wrapper '''
 
72
 
 
73
    def __init__(self, title, heading, text, checker=None):
 
74
        Dialog.__init__(self, 'error.ui', title, heading, text)
 
75
 
 
76
        self.setMaximumSize(1, 1)
 
77
        self.findChild(QLabel, 'icon').setPixmap(
 
78
                QMessageBox.standardIcon(QMessageBox.Critical))
 
79
 
 
80
        self.checker = self.findChild(QCheckBox, 'checker')
 
81
        if checker:
 
82
            self.checker.setText(checker)
 
83
        else:
 
84
            self.checker.hide()
 
85
    
 
86
    def checked(self):
 
87
        return self.checker.isChecked()
 
88
 
 
89
class ChoicesDialog(Dialog):
 
90
    ''' Choices dialog wrapper '''
 
91
 
 
92
    def __init__(self, title, text):
 
93
        Dialog.__init__(self, 'choices.ui', title, None, text)
 
94
 
 
95
        self.setMaximumSize(1, 1)
 
96
 
 
97
    def on_buttons_clicked(self, button):
 
98
        Dialog.on_buttons_clicked(self, button)
 
99
        if self.sender().buttonRole(button) == QDialogButtonBox.RejectRole:
 
100
            sys.exit(0)
 
101
 
 
102
class ProgressDialog(Dialog):
 
103
    ''' Progress dialog wrapper '''
 
104
 
 
105
    def __init__(self, title, heading, text):
 
106
        Dialog.__init__(self, 'progress.ui', title, heading, text)
 
107
 
 
108
        self.setMaximumSize(1, 1)
 
109
 
 
110
    def on_buttons_clicked(self, button):
 
111
        Dialog.on_buttons_clicked(self, button)
 
112
        if self.sender().buttonRole(button) == QDialogButtonBox.RejectRole:
 
113
            sys.exit(0)
 
114
    
 
115
    def set(self, value=None):
 
116
        progress = self.findChild(QProgressBar, 'progress')
 
117
        if not value:
 
118
            progress.setRange(0, 0)
 
119
            progress.setValue(0)
 
120
        else:
 
121
            progress.setRange(0, 1000)
 
122
            progress.setValue(value * 1000)
 
123
 
 
124
class ReportDialog(Dialog):
 
125
    ''' Report dialog wrapper '''
 
126
 
 
127
    def __init__(self, title, heading, text):
 
128
        Dialog.__init__(self, 'bugreport.ui', title, heading, text)
 
129
 
 
130
        self.details = self.addbutton(i18n("&Details..."))
 
131
        self.details.setCheckable(True)
 
132
 
 
133
        self.treeview = self.findChild(QTreeWidget, 'details')
 
134
        self.showtree(False)
 
135
 
 
136
    def on_buttons_clicked(self, button):
 
137
        if self.details == button:
 
138
            self.showtree(button.isChecked())
 
139
        else:
 
140
            Dialog.on_buttons_clicked(self, button)
 
141
            if self.sender().buttonRole(button) == QDialogButtonBox.RejectRole:
 
142
                sys.exit(0)
 
143
    
 
144
    def showtree(self, visible):
 
145
        self.treeview.setVisible(visible)
 
146
        if visible:
 
147
            self.setMaximumSize(16777215, 16777215)
 
148
        else:
 
149
            self.setMaximumSize(1, 1)
 
150
 
 
151
class MainUserInterface(apport.ui.UserInterface):
 
152
    ''' The main user interface presented to the user '''
 
153
 
 
154
    def __init__(self):
 
155
        apport.ui.UserInterface.__init__(self)
 
156
 
 
157
        self.run_argv()
 
158
 
 
159
    #
 
160
    # ui_* implementation of abstract UserInterface classes
 
161
    #
 
162
 
 
163
    def ui_present_crash(self, desktop_entry):
 
164
        # adapt dialog heading and label appropriately
 
165
        if desktop_entry:
 
166
            name = desktop_entry.getName()
 
167
            heading = i18n('Sorry, %s closed unexpectedly') % name
 
168
        elif self.report.has_key('ExecutablePath'):
 
169
            name = os.path.basename(self.report['ExecutablePath'])
 
170
            heading = i18n('Sorry, the program "%s" closed unexpectedly.') % name
 
171
        else:
 
172
            name = self.cur_package
 
173
            heading = i18n('Sorry, %s closed unexpectedly.') % name
 
174
 
 
175
        dialog = ErrorDialog(name, heading,
 
176
                i18n('If you were not doing anything confidential (entering '
 
177
                     'passwords or other private information), you can help '
 
178
                     'to improve the application by reporting the problem.'),
 
179
                i18n('&Ignore future crashes of this program version'))
 
180
 
 
181
        reportbutton = dialog.addbutton(i18n('&Report Problem...'))
 
182
 
 
183
        if desktop_entry and self.report.has_key('ExecutablePath') and \
 
184
                os.path.dirname(self.report['ExecutablePath']) in \
 
185
                os.environ['PATH'].split(':') and subprocess.call(['pgrep',
 
186
                    '-x', os.path.basename(self.report['ExecutablePath']),
 
187
                    '-u', str(os.getuid())], stdout=subprocess.PIPE) != 0:
 
188
            restartbutton = dialog.addbutton(i18n('Restart &Program'))
 
189
 
 
190
        # show crash notification dialog
 
191
        response = dialog.exec_()
 
192
        blacklist = dialog.checked()
 
193
 
 
194
        if response == QDialog.Rejected:
 
195
            return {'action': 'cancel', 'blacklist': blacklist}
 
196
        if dialog.actionbutton == reportbutton:
 
197
            return {'action': 'report', 'blacklist': blacklist}
 
198
        if dialog.actionbutton == restartbutton:
 
199
            return {'action': 'restart', 'blacklist': blacklist}
 
200
        # Fallback
 
201
        return {'action': 'cancel', 'blacklist': blacklist}
 
202
 
 
203
    def ui_present_package_error(self):
 
204
        name = self.report['Package']
 
205
        dialog = ErrorDialog(name,
 
206
                i18n('Sorry, the package "%s" failed to install or upgrade.') %
 
207
                name,
 
208
                i18n('You can help the developers to fix the package by '
 
209
                     'reporting the problem.'))
 
210
        
 
211
        reportbutton = dialog.addbutton(i18n('&Report Problem...'))
 
212
 
 
213
        response = dialog.exec_()
 
214
 
 
215
        if response == QDialog.Rejected:
 
216
            return 'cancel'
 
217
        if dialog.actionbutton == reportbutton:
 
218
            return 'report'
 
219
        # Fallback
 
220
        return 'cancel'
 
221
 
 
222
    def ui_present_kernel_error(self):
 
223
        message = i18n('Your system encountered a serious kernel problem.')
 
224
        annotate = ''
 
225
        if self.report.has_key('Annotation'):
 
226
            annotate = self.report['Annotation'] + '\n\n'
 
227
        annotate += i18n('You can help the developers to fix the problem by '
 
228
                         'reporting it.')
 
229
 
 
230
        dialog = ErrorDialog(i18n('Kernel problem'), message, annotate)
 
231
 
 
232
        reportbutton = dialog.addbutton(i18n('&Report Problem...'))
 
233
 
 
234
        response = dialog.exec_()
 
235
 
 
236
        if response == QDialog.Rejected:
 
237
            return 'cancel'
 
238
        if dialog.actionbutton == reportbutton:
 
239
            return 'report'
 
240
        # Fallback
 
241
        return 'cancel'
 
242
 
 
243
    def ui_present_report_details(self):
 
244
        dialog = ReportDialog(self.report.get('Package',
 
245
            i18n('Generic error')).split()[0],
 
246
            i18n('Send problem report to the developers?'),
 
247
            i18n('After the problem report has been sent, please fill out '
 
248
                 'the form in the automatically opened web browser.'))
 
249
 
 
250
        sendbutton = dialog.addbutton(i18n('&Send'))
 
251
        sendbutton.setDefault(True)
 
252
 
 
253
        # report contents
 
254
        details = dialog.findChild(QTreeWidget, 'details')
 
255
        for key in self.report:
 
256
            keyitem = QTreeWidgetItem([key])
 
257
            details.addTopLevelItem(keyitem)
 
258
 
 
259
            # string value
 
260
            if not hasattr(self.report[key], 'gzipvalue') and \
 
261
                    hasattr(self.report[key], 'isspace') and \
 
262
                    not self.report._is_binary(self.report[key]):
 
263
                lines = self.report[key].splitlines()
 
264
                for line in lines:
 
265
                    QTreeWidgetItem(keyitem, [line])
 
266
                if len(lines) < 4:
 
267
                    keyitem.setExpanded(True)
 
268
            else:
 
269
                QTreeWidgetItem(keyitem, [i18n('(binary data)')])
 
270
 
 
271
        details.header().hide()
 
272
 
 
273
        # complete/reduce radio buttons
 
274
        if self.report.has_key('CoreDump') and \
 
275
                self.report.has_useful_stacktrace():
 
276
            dialog.findChild(QRadioButton, 'complete').setText(
 
277
                    i18n('Complete report (recommended; %s)') %
 
278
                    self.format_filesize(self.get_complete_size()))
 
279
            dialog.findChild(QRadioButton, 'reduced').setText(
 
280
                    i18n('Reduced report (slow Internet connection; %s)') %
 
281
                    self.format_filesize(self.get_reduced_size()))
 
282
        else:
 
283
            dialog.findChild(QGroupBox, 'options').hide()
 
284
 
 
285
        response = dialog.exec_()
 
286
 
 
287
        if response == QDialog.Rejected:
 
288
            return 'cancel'
 
289
        # Fallback
 
290
        if dialog.actionbutton != sendbutton:
 
291
            return 'cancel'
 
292
        if dialog.findChild(QRadioButton, 'reduced').isChecked():
 
293
            return 'reduced'
 
294
        if dialog.findChild(QRadioButton, 'complete').isChecked():
 
295
            return 'full'
 
296
        # Fallback
 
297
        return 'cancel'
 
298
 
 
299
    def ui_info_message(self, title, text):
 
300
        KMessageBox.information(None, i18n(text), i18n(title))
 
301
 
 
302
    def ui_error_message(self, title, text):
 
303
        KMessageBox.information(None, i18n(text), i18n(title))
 
304
 
 
305
    def ui_start_info_collection_progress(self):
 
306
        self.progress = ProgressDialog(
 
307
                i18n('Collecting Problem Information'),
 
308
                i18n('Collecting problem information'),
 
309
                i18n('The collected information can be sent to the developers '
 
310
                     'to improve the application. This might take a few '
 
311
                     'minutes.'))
 
312
        self.progress.set()
 
313
        self.progress.show()
 
314
 
 
315
    def ui_pulse_info_collection_progress(self):
 
316
        self.progress.set()
 
317
        KApplication.processEvents()
 
318
 
 
319
    def ui_stop_info_collection_progress(self):
 
320
        self.progress.hide()
 
321
 
 
322
    def ui_start_upload_progress(self):
 
323
        self.progress = ProgressDialog(
 
324
                i18n('Uploading Problem Information'),
 
325
                i18n('Uploading problem information'),
 
326
                i18n('The collected information is being sent to the bug '
 
327
                     'tracking system. This might take a few minutes.'))
 
328
        self.progress.show()
 
329
 
 
330
    def ui_set_upload_progress(self, progress):
 
331
        if progress:
 
332
            self.progress.set(progress)
 
333
        else:
 
334
            self.progress.set()
 
335
        KApplication.processEvents()
 
336
 
 
337
    def ui_stop_upload_progress(self):
 
338
        self.progress.hide()
 
339
 
 
340
    def ui_question_yesno(self, text):
 
341
        response = KMessageBox.questionYesNoCancel(None, i18n(text), QString(),
 
342
                KStandardGuiItem.yes(), KStandardGuiItem.no(),
 
343
                KStandardGuiItem.cancel())
 
344
        if response == KMessageBox.Yes:
 
345
            return True
 
346
        if response == KMessageBox.No:
 
347
            return False
 
348
        return None
 
349
 
 
350
    def ui_question_choice(self, text, options, multiple):
 
351
        ''' Show a question with predefined choices.
 
352
 
 
353
        @options is a list of strings to present.
 
354
        @multiple - if True, choices should be QCheckBoxes, if False then
 
355
        should be QRadioButtons.
 
356
 
 
357
        Return list of selected option indexes, or None if the user cancelled.
 
358
        If multiple is False, the list will always have one element.
 
359
        '''
 
360
 
 
361
        dialog = ChoicesDialog(i18n("Apport"), text)
 
362
 
 
363
        b = None
 
364
        for option in options:
 
365
            if multiple:
 
366
                b = QCheckBox(option)
 
367
            else:
 
368
                b = QRadioButton(option)
 
369
            dialog.vbox_choices.insertWidget(0, b)
 
370
        
 
371
        response = dialog.exec_()
 
372
 
 
373
        if response == QDialog.Rejected:
 
374
            return 'cancel'
 
375
 
 
376
        response = [c for c in range(0, dialog.vbox_choices.count()) if \
 
377
                dialog.vbox_choices.itemAt(c).widget().isChecked()]
 
378
 
 
379
        return response
 
380
 
 
381
    def ui_question_file(self, text):
 
382
        ''' Show a file selector dialog.
 
383
 
 
384
        Return path if the user selected a file, or None if cancelled.
 
385
        '''
 
386
 
 
387
        response = QFileDialog.getOpenFileName(None, unicode(text, 'UTF-8'))
 
388
        if response.length() == 0:
 
389
            return None
 
390
        return str(response)
 
391
 
 
392
if __name__ == '__main__':
 
393
    appName = "apport-kde"
 
394
    catalog = "apport-kde"
 
395
    programName = ki18n("Apport KDE")
 
396
    version = "1.0"
 
397
    description = ki18n("KDE 4 frontend for the apport crash report system")
 
398
    license = KAboutData.License_GPL
 
399
    copyright = ki18n("2009 Canonical Ltd.")
 
400
    text = KLocalizedString()
 
401
    homePage = "https://wiki.ubuntu.com/AutomatedProblemReports"
 
402
    bugEmail = "kubuntu-devel@lists.ubuntu.com"
 
403
 
 
404
    aboutData = KAboutData(appName, catalog, programName, version, description,
 
405
            license, copyright, text, homePage, bugEmail)
 
406
 
 
407
    aboutData.addAuthor(ki18n("Richard A. Johnson"), ki18n("Author"))
 
408
    aboutData.addAuthor(ki18n("Michael Hofmann"), ki18n("Original Qt4 Author"))
 
409
 
 
410
    KCmdLineArgs.init([""], aboutData)
 
411
 
 
412
    app = KApplication()
 
413
    app.setWindowIcon(KIcon("apport"))
 
414
 
 
415
    UserInterface = MainUserInterface()
 
416
    UserInterface.show()
 
417
    app.exec_()