2
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
4
# Copyright (c) 2004-2013 Canonical
6
# 2005 Martin Willemoes Hansen
7
# 2010 Mohamed Amine IL Idrissi
9
# Author: Michiel Sikkes <michiel@eyesopened.nl>
10
# Michael Vogt <mvo@debian.org>
11
# Martin Willemoes Hansen <mwh@sysrq.dk>
12
# Mohamed Amine IL Idrissi <ilidrissiamine@gmail.com>
13
# Alex Launi <alex.launi@canonical.com>
14
# Michael Terry <michael.terry@canonical.com>
15
# Dylan McCall <dylanmccall@ubuntu.com>
17
# This program is free software; you can redistribute it and/or
18
# modify it under the terms of the GNU General Public License as
19
# published by the Free Software Foundation; either version 2 of the
20
# License, or (at your option) any later version.
22
# This program is distributed in the hope that it will be useful,
23
# but WITHOUT ANY WARRANTY; without even the implied warranty of
24
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
# GNU General Public License for more details.
27
# You should have received a copy of the GNU General Public License
28
# along with this program; if not, write to the Free Software
29
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
32
from __future__ import absolute_import, print_function
35
gi.require_version("Gtk", "3.0")
36
from gi.repository import GLib
37
from gi.repository import Gtk
38
from gi.repository import Gdk
39
from gi.repository import GObject
40
from gi.repository import Gio
41
from gi.repository import Pango
44
warnings.filterwarnings("ignore", "Accessed deprecated property",
55
from gettext import gettext as _
56
from gettext import ngettext
58
from .Core.utils import humanize_size
59
from .Core.AlertWatcher import AlertWatcher
60
from .Core.UpdateList import UpdateSystemGroup
61
from .Dialogs import InternalDialog
63
from DistUpgrade.DistUpgradeCache import NotEnoughFreeSpaceError
65
from .ChangelogViewer import ChangelogViewer
66
from .UnitySupport import UnitySupport
72
# - kill "all_changes" and move the changes into the "Update" class
73
# - screen reader does not read update toggle state
74
# - screen reader does not say "Downloaded" for downloaded updates
77
(LIST_NAME, LIST_UPDATE_DATA, LIST_SIZE, LIST_TOGGLE_ACTIVE,
78
LIST_SENSITIVE) = range(5)
80
# NetworkManager enums
81
from .Core.roam import NetworkManagerHelper
85
def __init__(self, groups, group, item):
86
self.groups = groups if groups else []
91
class CellAreaPackage(Gtk.CellAreaBox):
92
"""This CellArea lays our package cells side by side, without allocating
93
width for a cell if it isn't present (like icons for header labels).
94
We assume that the last cell should be expanded to fill remaining space,
95
and other cells have a fixed width.
98
def __init__(self, indent_toplevel=False):
99
Gtk.CellAreaBox.__init__(self)
100
self.indent_toplevel = indent_toplevel
102
self.cached_cell_size = {}
104
def do_foreach_alloc(self, context, widget, cell_area_in, bg_area_in,
108
def gather(cell, data):
111
self.foreach(gather, None)
115
# Record the space required by each cell
116
for cell_number, cell in enumerate(cells):
117
# Detect if this cell should be allocated space
118
if isinstance(cell, Gtk.CellRendererPixbuf):
119
gicon = cell.get_property("gicon")
120
hide_cell = gicon is None
123
cell_is_hidden[cell_number] = hide_cell
125
if not hide_cell and cell_number not in self.cached_cell_size:
126
min_size, natural_size = cell.get_preferred_width(widget)
127
self.cached_cell_size[cell_number] = natural_size
129
cell_area = cell_area_in.copy()
130
bg_area = bg_area_in.copy()
131
spacing = self.get_property("spacing")
132
cell_start = self.get_cell_start(widget)
133
orig_end = cell_area.width + cell_area.x
134
cur_path = self.get_current_path_string()
135
depth = Gtk.TreePath.new_from_string(cur_path).get_depth()
137
# And finally, start handling each cell
139
cell_area.x = cell_start
142
last_cell_number = len(cells) - 1
143
for cell_number, cell in enumerate(cells):
144
is_last_cell = cell_number == last_cell_number
145
cell_size = self.cached_cell_size.get(cell_number, 0)
147
if cell_area.width > 0 and extra_cell_width == 0:
148
cell_area.x += cell_area.width + spacing
151
# The first cell is affected by its depth in the tree
152
if not cell_is_hidden[1] and self.indent_toplevel:
153
# if not a header, align with header rows
156
indent = max(0, depth - 1)
157
indent_size = cell_size * indent
159
indent_extra = spacing
161
indent_extra = spacing + 1
163
indent_extra = spacing * indent
164
cell_area.x += indent_size + indent_extra
167
cell_size = max(cell_size, orig_end - cell_area.x)
168
if not cell_is_hidden[cell_number]:
169
cell_area.width = cell_size + extra_cell_width
173
extra_cell_width = cell_size + spacing
175
if callback(cell, cell_area.copy(), bg_area.copy()):
178
def do_event(self, context, widget, event, cell_area, flags):
179
# This override is just to trick our parent implementation into
180
# allowing clicks on toggle cells when they are where the expanders
181
# usually are. It doesn't expect that, so we expand the cell_area
182
# here to be equivalent to bg_area.
183
cell_start = self.get_cell_start(widget)
184
cell_area.width = cell_area.width + cell_area.x - cell_start
185
cell_area.x = cell_start
186
return Gtk.CellAreaBox.do_event(self, context, widget, event,
189
def get_cell_start(self, widget):
193
val = GObject.Value()
195
widget.style_get_property("horizontal-separator", val)
196
h_sep = val.get_int()
197
widget.style_get_property("grid-line-width", val)
198
line_width = val.get_int()
199
cell_start = self.column.get_x_offset() - h_sep - line_width
200
if not self.indent_toplevel: # i.e. if no headers
201
widget.style_get_property("expander-size", val)
202
spacing = self.get_property("spacing")
203
# Hardcode 4 because GTK+ hardcodes 4 internally
204
cell_start = cell_start + val.get_int() + 4 + spacing
208
class UpdatesAvailable(InternalDialog):
209
APP_INSTALL_ICONS_PATH = "/usr/share/app-install/icons"
211
def __init__(self, window_main, header=None, desc=None,
213
InternalDialog.__init__(self, window_main)
215
self.window_main = window_main
216
self.datadir = window_main.datadir
217
self.cache = window_main.cache
219
self.custom_header = header
220
self.custom_desc = desc
221
self.need_reboot = need_reboot
223
content_ui_path = os.path.join(self.datadir,
224
"gtkbuilder/UpdateManager.ui")
225
self._load_ui(content_ui_path, "pane_updates_available")
226
self.set_content_widget(self.pane_updates_available)
229
self.connected = True
231
# Used for inhibiting power management
232
self.sleep_cookie = None
234
self.settings = Gio.Settings.new("com.ubuntu.update-manager")
236
# Special icon theme for looking up app-install-data icons
237
self.app_icons = Gtk.IconTheme.get_default()
238
self.app_icons.append_search_path(self.APP_INSTALL_ICONS_PATH)
240
# Create Unity launcher quicklist
241
# FIXME: instead of passing parent we really should just send signals
242
self.unity = UnitySupport(parent=self)
244
# setup the help viewer and disable the help button if there
245
# is no viewer available
246
#self.help_viewer = HelpViewer("update-manager")
247
#if self.help_viewer.check() == False:
248
# self.button_help.set_sensitive(False)
250
self.add_settings_button()
251
self.button_close = self.add_button(Gtk.STOCK_CANCEL,
252
self.window_main.close)
253
self.button_install = self.add_button(_("Install Now"),
254
self.on_button_install_clicked)
255
self.focus_button = self.button_install
258
self.textview_changes = ChangelogViewer()
259
self.textview_changes.show()
260
self.scrolledwindow_changes.add(self.textview_changes)
261
changes_buffer = self.textview_changes.get_buffer()
262
changes_buffer.create_tag("versiontag", weight=Pango.Weight.BOLD)
264
# the treeview (move into it's own code!)
265
self.store = Gtk.TreeStore(str, GObject.TYPE_PYOBJECT, str, bool, bool)
266
self.treeview_update.set_model(None)
268
self.image_restart.set_from_gicon(self.get_restart_icon(),
271
restart_icon_renderer = Gtk.CellRendererPixbuf()
272
restart_icon_renderer.set_property("xpad", 4)
273
restart_icon_renderer.set_property("ypad", 2)
274
restart_icon_renderer.set_property("stock-size", Gtk.IconSize.MENU)
275
restart_icon_renderer.set_property("follow-state", True)
276
restart_column = Gtk.TreeViewColumn(None, restart_icon_renderer)
277
restart_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
278
restart_column.set_fixed_width(20)
279
self.treeview_update.append_column(restart_column)
280
restart_column.set_cell_data_func(restart_icon_renderer,
281
self.restart_icon_renderer_data_func)
283
self.pkg_cell_area = CellAreaPackage(False)
284
pkg_column = Gtk.TreeViewColumn.new_with_area(self.pkg_cell_area)
285
self.pkg_cell_area.column = pkg_column
286
pkg_column.set_title(_("Install or remove"))
287
pkg_column.set_property("spacing", 4)
288
pkg_column.set_expand(True)
289
self.treeview_update.append_column(pkg_column)
291
pkg_toggle_renderer = Gtk.CellRendererToggle()
292
pkg_toggle_renderer.set_property("ypad", 2)
293
pkg_toggle_renderer.connect("toggled", self.on_update_toggled)
294
pkg_column.pack_start(pkg_toggle_renderer, False)
295
pkg_column.add_attribute(pkg_toggle_renderer,
296
'active', LIST_TOGGLE_ACTIVE)
297
pkg_column.add_attribute(pkg_toggle_renderer,
298
'sensitive', LIST_SENSITIVE)
299
pkg_column.set_cell_data_func(pkg_toggle_renderer,
300
self.pkg_toggle_renderer_data_func)
302
pkg_icon_renderer = Gtk.CellRendererPixbuf()
303
pkg_icon_renderer.set_property("ypad", 2)
304
pkg_icon_renderer.set_property("stock-size", Gtk.IconSize.MENU)
305
pkg_column.pack_start(pkg_icon_renderer, False)
306
pkg_column.set_cell_data_func(pkg_icon_renderer,
307
self.pkg_icon_renderer_data_func)
309
pkg_label_renderer = Gtk.CellRendererText()
310
pkg_label_renderer.set_property("ypad", 2)
311
pkg_label_renderer.set_property("ellipsize", Pango.EllipsizeMode.END)
312
pkg_column.pack_start(pkg_label_renderer, True)
313
pkg_column.set_cell_data_func(pkg_label_renderer,
314
self.pkg_label_renderer_data_func)
316
size_renderer = Gtk.CellRendererText()
317
size_renderer.set_property("xpad", 6)
318
size_renderer.set_property("ypad", 0)
319
size_renderer.set_property("xalign", 1)
320
# 1.0/1.2 == PANGO.Scale.SMALL. Constant is not (yet) introspected.
321
size_renderer.set_property("scale", 1.0 / 1.2)
322
size_column = Gtk.TreeViewColumn(_("Download"), size_renderer,
324
size_column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
325
size_column.add_attribute(size_renderer,
326
'sensitive', LIST_SENSITIVE)
327
self.treeview_update.append_column(size_column)
329
self.treeview_update.set_headers_visible(True)
330
self.treeview_update.set_headers_clickable(False)
331
self.treeview_update.set_direction(Gtk.TextDirection.LTR)
332
self.treeview_update.set_fixed_height_mode(False)
333
self.treeview_update.set_expander_column(pkg_column)
334
self.treeview_update.set_search_column(LIST_NAME)
335
self.treeview_update.connect("button-press-event",
336
self.on_treeview_button_press)
339
self.show_versions = self.settings.get_boolean("show-versions")
340
# init summary_before_name
341
self.summary_before_name = self.settings.get_boolean(
342
"summary-before-name")
345
self.expander_details.set_expanded(
346
self.settings.get_boolean("show-details"))
347
self.expander_details.connect("activate", self.pre_activate_details)
348
self.expander_details.connect("notify::expanded",
349
self.activate_details)
350
self.expander_desc.connect("notify::expanded", self.activate_desc)
352
# If auto-updates are on, change cancel label
353
self.notifier_settings = Gio.Settings.new("com.ubuntu.update-notifier")
354
self.notifier_settings.connect(
355
"changed::auto-launch",
356
lambda s, p: self.update_close_button())
357
self.update_close_button()
360
self.alert_watcher = AlertWatcher()
361
self.alert_watcher.connect("network-alert", self._on_network_alert)
362
self.alert_watcher.connect("battery-alert", self._on_battery_alert)
363
self.alert_watcher.connect("network-3g-alert",
364
self._on_network_3g_alert)
367
InternalDialog.stop(self)
371
InternalDialog.start(self)
372
self.set_update_list(self.window_main.update_list)
373
self.alert_watcher.check_alert_state()
374
self._restore_state()
376
def is_auto_update(self):
377
update_days = apt_pkg.config.find_i(
378
"APT::Periodic::Update-Package-Lists")
379
return update_days >= 1
381
def update_close_button(self):
382
if self.is_auto_update():
383
self.button_close.set_label(_("_Remind Me Later"))
384
self.button_close.set_use_stock(False)
385
self.button_close.set_use_underline(True)
387
self.button_close.set_label(Gtk.STOCK_CANCEL)
388
self.button_close.set_use_stock(True)
389
self.button_close.set_use_underline(False)
391
def install_all_updates(self, menu, menuitem, data):
392
self.select_all_upgrades(None)
393
self.on_button_install_clicked()
395
def pkg_requires_restart(self, pkg):
396
if pkg is None or pkg.candidate is None:
398
restart_condition = pkg.candidate.record.get('Restart-Required')
399
return restart_condition == 'system'
401
def get_restart_icon(self):
402
# FIXME: Non-standard, incorrect icon name (from app category).
403
# Theme support for what we want seems to be lacking.
404
restart_icon_names = ['view-refresh-symbolic',
407
return Gio.ThemedIcon.new_from_names(restart_icon_names)
409
def restart_icon_renderer_data_func(self, cell_layout, renderer, model,
411
data = model.get_value(iter, LIST_UPDATE_DATA)
412
sensitive = model.get_value(iter, LIST_SENSITIVE)
413
path = model.get_path(iter)
415
renderer.set_sensitive(sensitive)
417
requires_restart = False
418
if data.item and data.item.pkg:
419
requires_restart = self.pkg_requires_restart(data.item.pkg)
421
if not self.treeview_update.row_expanded(path):
422
# A package in the group requires restart
423
for group_item in data.group.items:
424
if group_item.pkg and self.pkg_requires_restart(
426
requires_restart = True
430
gicon = self.get_restart_icon()
433
renderer.set_property("gicon", gicon)
435
def pkg_toggle_renderer_data_func(self, cell_layout, renderer, model,
437
data = model.get_value(iter, LIST_UPDATE_DATA)
442
activatable = data.item.pkg.name not in self.list.held_back
444
renderer.set_sensitive(data.item.sensitive)
447
inconsistent = data.group.selection_is_inconsistent()
452
for group in data.groups:
453
for item in group.items:
454
this_install = item.is_selected()
455
if saw_install is not None and saw_install != this_install:
458
saw_install = this_install
462
# The "active" attribute is already set via LIST_TOGGLE_ACTIVE in the
463
# tree model, so we don't set it here.
464
renderer.set_property("activatable", activatable)
465
renderer.set_property("inconsistent", inconsistent)
467
def pkg_icon_renderer_data_func(self, cell_layout, renderer, model,
469
data = model.get_value(iter, LIST_UPDATE_DATA)
473
gicon = self.get_app_install_icon(data.group.icon)
475
gicon = self.get_app_install_icon(data.item.icon)
477
renderer.set_property("gicon", gicon)
479
def get_app_install_icon(self, icon):
480
"""Any application icon is coming from app-install-data's desktop
481
files, which refer to icons from app-install-data's icon directory.
482
So we look them up here."""
484
if not isinstance(icon, Gio.ThemedIcon):
485
return icon # shouldn't happen
487
info = self.app_icons.choose_icon(icon.get_names(), 16,
488
Gtk.IconLookupFlags.FORCE_SIZE)
490
return Gio.FileIcon.new(Gio.File.new_for_path(info.get_filename()))
492
return icon # Assume it's in one of the user's themes
494
def pkg_label_renderer_data_func(self, cell_layout, renderer, model,
496
data = model.get_value(iter, LIST_UPDATE_DATA)
497
sensitive = model.get_value(iter, LIST_SENSITIVE)
498
name = GLib.markup_escape_text(model.get_value(iter, LIST_NAME))
500
renderer.set_sensitive(sensitive)
507
markup = "<b>%s</b>" % name
509
renderer.set_property("markup", markup)
511
def set_changes_buffer(self, changes_buffer, text, name, srcpkg):
512
changes_buffer.set_text("")
513
lines = text.split("\n")
515
changes_buffer.set_text(text)
519
end_iter = changes_buffer.get_end_iter()
520
version_match = re.match(
521
r'^%s \((.*)\)(.*)\;.*$' % re.escape(srcpkg), line)
522
#bullet_match = re.match("^.*[\*-]", line)
523
author_match = re.match("^.*--.*<.*@.*>.*$", line)
525
version = version_match.group(1)
526
#upload_archive = version_match.group(2).strip()
527
version_text = _("Version %s: \n") % version
528
changes_buffer.insert_with_tags_by_name(end_iter, version_text,
533
changes_buffer.insert(end_iter, line + "\n")
535
def on_treeview_update_cursor_changed(self, widget):
536
path = widget.get_cursor()[0]
537
# check if we have a path at all
540
model = widget.get_model()
541
iter = model.get_iter(path)
544
data = model.get_value(iter, LIST_UPDATE_DATA)
546
if (item is None and data.group is not None
547
and data.group.core_item is not None):
548
item = data.group.core_item
549
if (item is None or item.pkg is None
550
or item.pkg.candidate is None
551
or item.pkg.candidate.description is None):
552
changes_buffer = self.textview_changes.get_buffer()
553
changes_buffer.set_text("")
554
desc_buffer = self.textview_descr.get_buffer()
555
desc_buffer.set_text("")
556
self.notebook_details.set_sensitive(False)
558
long_desc = item.pkg.candidate.description
559
self.notebook_details.set_sensitive(True)
560
# do some regular expression magic on the description
561
# Add a newline before each bullet
562
p = re.compile(r'^(\s|\t)*(\*|0|-)', re.MULTILINE)
563
long_desc = p.sub('\n*', long_desc)
564
# replace all newlines by spaces
565
p = re.compile(r'\n', re.MULTILINE)
566
long_desc = p.sub(" ", long_desc)
567
# replace all multiple spaces by newlines
568
p = re.compile(r'\s\s+', re.MULTILINE)
569
long_desc = p.sub("\n", long_desc)
570
long_desc = ("Package: %s\n%s" %
571
(item.pkg.name, long_desc))
573
desc_buffer = self.textview_descr.get_buffer()
574
desc_buffer.set_text(long_desc)
576
# now do the changelog
581
changes_buffer = self.textview_changes.get_buffer()
583
# check if we have the changes already and if so, display them
584
# (even if currently disconnected)
585
if name in self.cache.all_changes:
586
changes = self.cache.all_changes[name]
587
srcpkg = self.cache[name].candidate.source_name
588
self.set_changes_buffer(changes_buffer, changes, name, srcpkg)
589
# if not connected, do not even attempt to get the changes
590
elif not self.connected:
591
changes_buffer.set_text(
592
_("No network connection detected, you can not download "
593
"changelog information."))
594
# else, get it from the entwork
595
elif self.expander_details.get_expanded():
596
lock = threading.Lock()
598
changelog_thread = threading.Thread(
599
target=self.cache.get_news_and_changelog, args=(name, lock))
600
changelog_thread.start()
601
changes_buffer.set_text("%s\n" %
602
_("Downloading list of changes..."))
603
iter = changes_buffer.get_iter_at_line(1)
604
anchor = changes_buffer.create_child_anchor(iter)
605
button = Gtk.Button(stock="gtk-cancel")
606
self.textview_changes.add_child_at_anchor(button, anchor)
608
id = button.connect("clicked",
609
lambda w, lock: lock.release(), lock)
610
# wait for the dl-thread
613
while Gtk.events_pending():
615
# download finished (or canceld, or time-out)
617
if button.handler_is_connected(id):
618
button.disconnect(id)
619
# check if we still are in the right pkg (the download may have taken
620
# some time and the user may have clicked on a new pkg)
621
now_path = widget.get_cursor()[0]
626
# display NEWS.Debian first, then the changelog
628
srcpkg = self.cache[name].candidate.source_name
629
if name in self.cache.all_news:
630
changes += self.cache.all_news[name]
631
if name in self.cache.all_changes:
632
changes += self.cache.all_changes[name]
634
self.set_changes_buffer(changes_buffer, changes, name, srcpkg)
636
def on_treeview_button_press(self, widget, event):
638
Show a context menu if a right click was performed on an update entry
640
if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
641
# need to keep a reference here of menu, otherwise it gets
642
# deleted when it goes out of scope and no menu is visible
644
self.menu = menu = Gtk.Menu()
646
Gtk.MenuItem.new_with_mnemonic(_("_Deselect All"))
647
item_select_none.connect("activate", self.select_none_upgrades)
648
menu.append(item_select_none)
649
num_updates = self.cache.install_count + self.cache.del_count
651
item_select_none.set_property("sensitive", False)
652
item_select_all = Gtk.MenuItem.new_with_mnemonic(_("Select _All"))
653
item_select_all.connect("activate", self.select_all_upgrades)
654
menu.append(item_select_all)
656
menu.popup_for_device(
657
None, None, None, None, None, event.button, event.time)
661
# we need this for select all/unselect all
662
def _toggle_group_headers(self, new_selection_value):
663
""" small helper that will set/unset the group headers
665
model = self.treeview_update.get_model()
667
data = model.get_value(row.iter, LIST_UPDATE_DATA)
668
if data.groups is not None or data.group is not None:
669
model.set_value(row.iter, LIST_TOGGLE_ACTIVE,
672
def select_all_upgrades(self, widget):
677
self.cache.saveDistUpgrade()
678
self._toggle_group_headers(True)
679
self.treeview_update.queue_draw()
680
self.updates_changed()
683
def select_none_upgrades(self, widget):
689
self._toggle_group_headers(False)
690
self.treeview_update.queue_draw()
691
self.updates_changed()
694
def setBusy(self, flag):
695
""" Show a watch cursor if the app is busy for more than 0.3 sec.
696
Furthermore provide a loop to handle user interface events """
697
if self.window_main.get_window() is None:
700
self.window_main.get_window().set_cursor(
701
Gdk.Cursor.new(Gdk.CursorType.WATCH))
703
self.window_main.get_window().set_cursor(None)
704
while Gtk.events_pending():
707
def _mark_selected_updates(self):
708
def foreach_cb(model, path, iter, data):
709
data = model.get_value(iter, LIST_UPDATE_DATA)
712
active = data.item.is_selected()
714
active = data.group.packages_are_selected()
716
active = any([g.packages_are_selected() for g in data.groups])
717
model.set_value(iter, LIST_TOGGLE_ACTIVE, active)
718
self.store.foreach(foreach_cb, None)
720
def _check_for_required_restart(self):
721
requires_restart = False
723
def foreach_cb(model, path, iter, data):
724
data = model.get_value(iter, LIST_UPDATE_DATA)
725
active = model.get_value(iter, LIST_TOGGLE_ACTIVE)
731
elif data.group and data.group.core_item:
732
pkg = data.group.core_item.pkg
733
if pkg and self.pkg_requires_restart(pkg):
734
nonlocal requires_restart
735
requires_restart = True
737
self.store.foreach(foreach_cb, None)
738
self.hbox_restart.set_visible(requires_restart)
740
def _refresh_updates_count(self):
741
self.button_install.set_sensitive(self.cache.install_count
742
+ self.cache.del_count)
744
inst_count = self.cache.install_count + self.cache.del_count
745
self.dl_size = self.cache.required_download
747
if self.dl_size != 0:
748
download_str = _("%s will be downloaded.") % (
749
humanize_size(self.dl_size))
750
self.image_downsize.set_sensitive(True)
751
# do not set the buttons to sensitive/insensitive until NM
752
# can deal with dialup connections properly
753
#if self.alert_watcher.network_state != NM_STATE_CONNECTED:
754
# self.button_install.set_sensitive(False)
756
# self.button_install.set_sensitive(True)
757
self.button_install.set_sensitive(True)
758
self.unity.set_install_menuitem_visible(True)
761
download_str = ngettext(
762
"The update has already been downloaded.",
763
"The updates have already been downloaded.",
765
self.button_install.set_sensitive(True)
766
self.unity.set_install_menuitem_visible(True)
768
download_str = _("There are no updates to install.")
769
self.button_install.set_sensitive(False)
770
self.unity.set_install_menuitem_visible(False)
771
self.image_downsize.set_sensitive(False)
772
self.label_downsize.set_text(download_str)
773
self.hbox_downsize.show()
774
self.vbox_alerts.show()
775
except SystemError as e:
776
print("required_download could not be calculated: %s" % e)
777
self.label_downsize.set_markup(_("Unknown download size."))
778
self.image_downsize.set_sensitive(False)
779
self.hbox_downsize.show()
780
self.vbox_alerts.show()
782
def updates_changed(self):
783
self._mark_selected_updates()
784
self._check_for_required_restart()
785
self._refresh_updates_count()
787
def update_count(self):
788
"""activate or disable widgets and show dialog texts corresponding to
789
the number of available updates"""
790
self.updates_changed()
795
if self.custom_header is not None:
796
text_header = self.custom_header
797
if self.custom_desc is not None:
798
text_desc = self.custom_desc
800
# show different text on first run (UX team suggestion)
801
elif self.settings.get_boolean("first-run"):
802
flavor = self.window_main.meta_release.flavor_name
803
version = self.window_main.meta_release.current_dist_version
804
text_header = _("Updated software has been issued since %s %s "
805
"was released. Do you want to install "
806
"it now?") % (flavor, version)
807
self.settings.set_boolean("first-run", False)
809
text_header = _("Updated software is available for this "
810
"computer. Do you want to install it now?")
811
if not self.hbox_restart.get_visible() and self.need_reboot:
812
text_desc = _("The computer also needs to restart "
813
"to finish installing previous updates.")
815
self.notebook_details.set_sensitive(True)
816
self.treeview_update.set_sensitive(True)
817
self.set_header(text_header)
818
self.set_desc(text_desc)
822
# Before we shrink the window, capture the size
823
def pre_activate_details(self, expander):
824
expanded = self.expander_details.get_expanded()
828
def activate_details(self, expander, data):
829
expanded = self.expander_details.get_expanded()
830
self.settings.set_boolean("show-details", expanded)
832
self.on_treeview_update_cursor_changed(self.treeview_update)
833
self._restore_state()
835
def activate_desc(self, expander, data):
836
expanded = self.expander_desc.get_expanded()
837
self.expander_desc.set_vexpand(expanded)
839
def on_button_install_clicked(self):
840
self.unity.set_install_menuitem_visible(False)
841
# print("on_button_install_clicked")
842
err_sum = _("Not enough free disk space")
843
err_msg = _("The upgrade needs a total of %s free space on "
845
"Please free at least an additional %s of disk "
847
# specific ways to resolve lack of free space
848
remedy_archivedir = _("Remove temporary packages of former "
849
"installations using 'sudo apt clean'.")
850
remedy_boot = _("You can remove old kernels using "
851
"'sudo apt autoremove', and you could also "
852
"set COMPRESS=xz in "
853
"/etc/initramfs-tools/initramfs.conf to "
854
"reduce the size of your initramfs.")
855
remedy_root = _("Empty your trash and remove temporary "
856
"packages of former installations using "
858
remedy_tmp = _("Reboot to clean up files in /tmp.")
860
# check free space and error if its not enough
862
self.cache.checkFreeSpace()
863
except NotEnoughFreeSpaceError as e:
864
# CheckFreeSpace examines where packages are cached
865
archivedir = apt_pkg.config.find_dir("Dir::Cache::archives")
867
for req in e.free_space_required_list:
870
if req.dir == archivedir:
871
err_long += err_msg % (req.size_total, req.dir,
872
req.size_needed, req.dir,
874
elif req.dir == "/boot":
875
err_long += err_msg % (req.size_total, req.dir,
876
req.size_needed, req.dir,
879
err_long += err_msg % (req.size_total, req.dir,
880
req.size_needed, req.dir,
882
elif req.dir == "/tmp":
883
err_long += err_msg % (req.size_total, req.dir,
884
req.size_needed, req.dir,
886
elif req.dir == "/usr":
887
err_long += err_msg % (req.size_total, req.dir,
888
req.size_needed, req.dir,
890
self.window_main.start_error(False, err_sum, err_long)
893
logging.exception("free space check failed")
894
self.window_main.start_install()
896
def _on_network_alert(self, watcher, state):
897
# do not set the buttons to sensitive/insensitive until NM
898
# can deal with dialup connections properly
899
if state in NetworkManagerHelper.NM_STATE_CONNECTING_LIST:
900
self.label_offline.set_text(_("Connecting..."))
901
self.updates_changed()
902
self.hbox_offline.show()
903
self.vbox_alerts.show()
904
self.connected = False
905
# in doubt (STATE_UNKNOWN), assume connected
906
elif (state in NetworkManagerHelper.NM_STATE_CONNECTED_LIST
907
or state == NetworkManagerHelper.NM_STATE_UNKNOWN):
908
self.updates_changed()
909
self.hbox_offline.hide()
910
self.connected = True
911
# trigger re-showing the current app to get changelog info (if
913
self.on_treeview_update_cursor_changed(self.treeview_update)
915
self.connected = False
916
self.label_offline.set_text(_("You may not be able to check for "
917
"updates or download new updates."))
918
self.updates_changed()
919
self.hbox_offline.show()
920
self.vbox_alerts.show()
922
def _on_battery_alert(self, watcher, on_battery):
924
self.hbox_battery.show()
925
self.vbox_alerts.show()
927
self.hbox_battery.hide()
929
def _on_network_3g_alert(self, watcher, on_3g, is_roaming):
930
#print("on 3g: %s; roaming: %s" % (on_3g, is_roaming))
932
self.hbox_roaming.show()
933
self.hbox_on_3g.hide()
935
self.hbox_on_3g.show()
936
self.hbox_roaming.hide()
938
self.hbox_on_3g.hide()
939
self.hbox_roaming.hide()
941
def on_update_toggled(self, renderer, path):
942
""" a toggle button in the listview was toggled """
943
iter = self.store.get_iter(path)
944
data = self.store.get_value(iter, LIST_UPDATE_DATA)
945
# make sure that we don't allow to toggle deactivated updates
946
# this is needed for the call by the row activation callback
948
self.toggle_from_items([item for group in data.groups
949
for item in group.items])
951
self.toggle_from_items(data.group.items)
953
self.toggle_from_items([data.item])
955
def on_treeview_update_row_activated(self, treeview, path, column, *args):
957
If an update row was activated (by pressing space), toggle the
960
self.on_update_toggled(None, path)
962
def toggle_from_items(self, items):
964
actiongroup = apt_pkg.ActionGroup(self.cache._depcache)
966
# Deselect all updates if any are selected
967
keep_packages = any([item.is_selected() for item in items])
972
elif item.pkg.name not in self.list.held_back:
973
if not item.to_remove:
974
item.pkg.mark_install()
976
item.pkg.mark_delete()
980
# check if we left breakage
981
if self.cache._depcache.broken_count:
982
Fix = apt_pkg.ProblemResolver(self.cache._depcache)
983
Fix.resolve_by_keep()
984
self.updates_changed()
985
self.treeview_update.queue_draw()
989
def _save_state(self):
990
""" save the state (window-size for now) """
991
if self.expander_details.get_expanded():
992
(w, h) = self.window_main.get_size()
993
self.settings.set_int("window-width", w)
994
self.settings.set_int("window-height", h)
996
def _restore_state(self):
997
""" restore the state (window-size for now) """
998
w = self.settings.get_int("window-width")
999
h = self.settings.get_int("window-height")
1000
expanded = self.expander_details.get_expanded()
1002
self.window_main.begin_user_resizable(w, h)
1004
self.window_main.end_user_resizable()
1007
def _add_header(self, name, groups):
1009
for group in groups:
1010
total_size = total_size + group.get_total_size()
1013
UpdateData(groups, None, None),
1014
humanize_size(total_size),
1018
return self.store.append(None, header_row)
1020
def _add_groups(self, groups):
1021
# Each row contains:
1022
# row label (for screen reader),
1023
# update data tuple (is_toplevel, group object, package object),
1025
# update selection state
1026
for group in groups:
1030
group_is_item = None
1031
if not isinstance(group, UpdateSystemGroup) and \
1032
len(group.items) == 1:
1033
group_is_item = group.items[0]
1037
UpdateData(None, group, group_is_item),
1038
humanize_size(group.get_total_size()),
1042
group_iter = self.store.append(None, group_row)
1046
for item in group.items:
1049
UpdateData(None, None, item),
1050
humanize_size(getattr(item.pkg.candidate, "size", 0)),
1054
self.store.append(group_iter, item_row)
1056
def set_update_list(self, update_list):
1057
self.list = update_list
1059
# use the watch cursor
1061
# disconnect the view first
1062
self.treeview_update.set_model(None)
1064
# clean most objects
1067
self.scrolledwindow_update.show()
1069
# add security and update groups to self.store
1070
if self.list.oem_groups:
1071
self._add_header(_("Improved hardware support"),
1072
self.list.oem_groups)
1073
self._add_groups(self.list.oem_groups)
1074
if self.list.security_groups:
1075
self._add_header(_("Security updates"), self.list.security_groups)
1076
self._add_groups(self.list.security_groups)
1077
if ((self.list.security_groups or self.list.oem_groups)
1078
and self.list.update_groups):
1079
self._add_header(_("Other updates"), self.list.update_groups)
1080
elif self.list.update_groups and (self.list.kernel_autoremove_groups
1081
or self.list.duplicate_groups):
1082
self._add_header(_("Updates"), self.list.update_groups)
1083
if self.list.update_groups:
1084
self._add_groups(self.list.update_groups)
1085
if self.list.kernel_autoremove_groups:
1087
_("Unused kernel updates to be removed"),
1088
self.list.kernel_autoremove_groups)
1089
self._add_groups(self.list.kernel_autoremove_groups)
1090
if self.list.duplicate_groups:
1092
_("Duplicate packages to be removed"),
1093
self.list.duplicate_groups)
1094
self._add_groups(self.list.duplicate_groups)
1095
if self.list.ubuntu_pro_groups:
1096
self._add_groups(self.list.ubuntu_pro_groups)
1098
self.treeview_update.set_model(self.store)
1099
self.pkg_cell_area.indent_toplevel = (
1100
bool(self.list.security_groups)
1101
or bool(self.list.kernel_autoremove_groups)
1102
or bool(self.list.duplicate_groups))
1103
self.update_close_button()
1106
while Gtk.events_pending():
1107
Gtk.main_iteration()
1108
self.updates_changed()