From dd6f28d2f8ea41131f8e059556de194ed5e8e9f4 Mon Sep 17 00:00:00 2001 From: Sebastian Heinlein Date: Fri, 17 Feb 2006 20:38:33 +0100 Subject: * Allow to open URLs in the release notes using a browser * Change the label of the button ok in the release notes dialog to "Upgrade" --- UpdateManager/ReleaseNotesViewer.py | 117 ++++++++++++++++++++++++++++++++++++ UpdateManager/UpdateManager.py | 5 +- 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 UpdateManager/ReleaseNotesViewer.py (limited to 'UpdateManager') diff --git a/UpdateManager/ReleaseNotesViewer.py b/UpdateManager/ReleaseNotesViewer.py new file mode 100644 index 00000000..9cecc895 --- /dev/null +++ b/UpdateManager/ReleaseNotesViewer.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +import pygtk +import gtk +import pango +import subprocess +import os + +class ReleaseNotesViewer(gtk.TextView): + def __init__(self, notes): + gtk.TextView.__init__(self) + self.hovering = False + self.buffer = gtk.TextBuffer() + self.set_buffer(self.buffer) + self.buffer.set_text(notes) + self.connect("event-after", self.event_after) + self.connect("motion-notify-event", self.motion_notify_event) + self.connect("visibility-notify-event", self.visibility_notify_event) + self.search_links() + self.set_property("editable", False) + self.set_cursor_visible(False) + + def tag_link(self, start, end, url): + tag = self.buffer.create_tag(None, foreground="blue", + underline=pango.UNDERLINE_SINGLE) + tag.set_data("url", url) + self.buffer.apply_tag(tag , start, end) + + def search_links(self): + iter = self.buffer.get_iter_at_offset(0) + while 1: + ret = iter.forward_search("http://", gtk.TEXT_SEARCH_VISIBLE_ONLY, + None) + if not ret: + break + (match_start, match_end) = ret + match_tmp = match_end.copy() + while 1: + if match_tmp.forward_char(): + text = match_end.get_text(match_tmp) + if text in (" ", ")", "]", "\n", "\t"): + break + else: + break + match_end = match_tmp.copy() + url = match_start.get_text(match_end) + self.tag_link(match_start, match_end, url) + iter = match_end + + def event_after(self, text_view, event): + if event.type != gtk.gdk.BUTTON_RELEASE: + return False + if event.button != 1: + return False + + try: + (start, end) = self.buffer.get_selection_bounds() + except ValueError: + pass + else: + if start.get_offset() != end.get_offset(): + return False + + (x, y) = self.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, + int(event.x), int(event.y)) + iter = self.get_iter_at_location(x, y) + + tags = iter.get_tags() + for tag in tags: + url = tag.get_data("url") + if url != "": + self.open_url(url) + break + + def open_url(self, url): + if os.path.exists('usr/bin/gnome-open'): + command = ['gnome-open', url] + else: + command = ['x-www-browser', url] + + if os.getuid() == 0 and os.environ.has_key('SUDO_USER'): + command = ['sudo', '-u', os.environ['SUDO_USER']] + command + + subprocess.Popen(command) + + def motion_notify_event(self, text_view, event): + x, y = text_view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, + int(event.x), int(event.y)) + self.check_hovering(x, y) + self.window.get_pointer() + return False + + def visibility_notify_event(self, text_view, event): + (wx, wy, mod) = text_view.window.get_pointer() + (bx, by) = text_view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, wx, + wy) + self.check_hovering(bx, by) + return False + + def check_hovering(self, x, y): + _hovering = False + iter = self.get_iter_at_location(x, y) + + tags = iter.get_tags() + for tag in tags: + url = tag.get_data("url") + if url != "": + _hovering = True + break + + if _hovering != self.hovering: + self.hovering = _hovering + + if self.hovering: + self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) + else: + self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM)) diff --git a/UpdateManager/UpdateManager.py b/UpdateManager/UpdateManager.py index 6f9e117f..f73f790a 100644 --- a/UpdateManager/UpdateManager.py +++ b/UpdateManager/UpdateManager.py @@ -55,6 +55,7 @@ from gettext import gettext as _ from Common.utils import * from Common.SimpleGladeApp import SimpleGladeApp +from ReleaseNotesViewer import ReleaseNotesViewer import GtkProgress from MetaRelease import Dist, MetaRelease @@ -644,7 +645,9 @@ class UpdateManager(SimpleGladeApp): try: release_notes = urllib2.urlopen(uri) notes = release_notes.read() - self.textview_release_notes.get_buffer().set_text(notes) + textview_release_notes = ReleaseNotesViewer(notes) + textview_release_notes.show() + self.scrolled_notes.add(textview_release_notes) self.dialog_release_notes.set_transient_for(self.window_main) res = self.dialog_release_notes.run() self.dialog_release_notes.hide() -- cgit v1.2.3 From 8abb01fb328c6e0c3da3263af149934b02949b8a Mon Sep 17 00:00:00 2001 From: Sebastian Heinlein Date: Sat, 18 Feb 2006 16:06:37 +0100 Subject: * Do not disable the update dialog during downloading of the changelog * Show step 5 "Restarting the system" in the ui, since users asked me, how and when they should reboot. this should provide a better picture of the upgrade process * Fixed the parent of the change media dialog * Converted the abort DistUpgrade dialog to a nice glade dialog * Disabled the abortion of the DistUpgrade. it is currently not supported. --- DistUpgrade/DistUpgrade.glade | 129 +++++++++++++++++++++++++++++++++++++- DistUpgrade/DistUpgradeViewGtk.py | 24 ++++--- UpdateManager/UpdateManager.py | 2 - 3 files changed, 142 insertions(+), 13 deletions(-) (limited to 'UpdateManager') diff --git a/DistUpgrade/DistUpgrade.glade b/DistUpgrade/DistUpgrade.glade index 5b59fb4d..55b439c1 100644 --- a/DistUpgrade/DistUpgrade.glade +++ b/DistUpgrade/DistUpgrade.glade @@ -12,7 +12,6 @@ False False False - update-manager.png True False False @@ -293,6 +292,7 @@ 18 18 + True 0.5 0.5 0 @@ -309,7 +309,8 @@ - + True + Restarting the system False False GTK_JUSTIFY_LEFT @@ -1285,4 +1286,128 @@ This is you last chance to cancel the upgrade. + + 6 + + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_CENTER_ON_PARENT + False + False + False + True + True + True + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + True + False + + + + True + False + 12 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + _Cancel Upgrade + True + GTK_RELIEF_NORMAL + True + -6 + + + + + + True + True + True + True + _Resume Upgrade + True + GTK_RELIEF_NORMAL + True + -5 + + + + + 0 + False + True + GTK_PACK_END + + + + + + 6 + True + False + 12 + + + + True + gtk-dialog-question + 6 + 0 + 0 + 0 + 0 + + + 0 + False + True + + + + + + True + <b><big>Cancel the running upgrade?</big></b> + +The system could be in an unusable state if you cancel the upgrade. You are strongly adviced to resume the upgrade. + False + True + GTK_JUSTIFY_LEFT + True + False + 0 + 0 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + 0 + True + True + + + + + + diff --git a/DistUpgrade/DistUpgradeViewGtk.py b/DistUpgrade/DistUpgradeViewGtk.py index 6013200b..c36d8dac 100644 --- a/DistUpgrade/DistUpgradeViewGtk.py +++ b/DistUpgrade/DistUpgradeViewGtk.py @@ -74,7 +74,7 @@ class GtkFetchProgressAdapter(apt.progress.FetchProgress): def mediaChange(self, medium, drive): #print "mediaChange %s %s" % (medium, drive) msg = _("Please insert '%s' into the drive '%s'" % (medium,drive)) - dialog = gtk.MessageDialog(parent=self.main, + dialog = gtk.MessageDialog(parent=self.window_main, flags=gtk.DIALOG_MODAL, type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_OK_CANCEL) @@ -182,9 +182,13 @@ class DistUpgradeViewGtk(DistUpgradeView,SimpleGladeApp): # FIXME: i18n must be somewhere relative do this dir bindtextdomain("update-manager",os.path.join(os.getcwd(),"mo")) + icons = gtk.icon_theme_get_default() + gtk.window_set_default_icon(icons.load_icon("update-manager", 32, 0)) SimpleGladeApp.__init__(self, "DistUpgrade.glade", None, domain="update-manager") self.window_main.set_keep_above(True) + self.window_main.realize() + self.window_main.window.set_functions(gtk.gdk.FUNC_MOVE) self._opCacheProgress = GtkOpProgress(self.progressbar_cache) self._fetchProgress = GtkFetchProgressAdapter(self) self._installProgress = GtkInstallProgressAdapter(self) @@ -328,6 +332,8 @@ class DistUpgradeViewGtk(DistUpgradeView,SimpleGladeApp): def confirmRestart(self): self.dialog_restart.set_transient_for(self.window_main) + self.dialog_restart.realize() + self.dialog_restart.window.set_functions(gtk.gdk.FUNC_MOVE) res = self.dialog_restart.run() self.dialog_restart.hide() if res == gtk.RESPONSE_YES: @@ -335,14 +341,14 @@ class DistUpgradeViewGtk(DistUpgradeView,SimpleGladeApp): return False def on_window_main_delete_event(self, widget, event): - #print "on_window_main_delete_event()" - summary = _("Are you sure you want cancel?") - msg = _("Canceling during a upgrade can leave the system in a " - "unstable state. It is strongly adviced to continue " - "the operation. ") - if self.askYesNoQuestion(summary, msg): - self.exit(1) - return True + self.dialog_cancel.set_transient_for(self.window_main) + self.dialog_cancel.realize() + self.dialog_cancel.window.set_functions(gtk.gdk.FUNC_MOVE) + res = self.dialog_cancel.run() + self.dialog_cancel.hide() + if res == gtk.RESPONSE_CANCEL: + self.destroy() + return True if __name__ == "__main__": view = GtkDistUpgradeView() diff --git a/UpdateManager/UpdateManager.py b/UpdateManager/UpdateManager.py index f73f790a..8b757bd1 100644 --- a/UpdateManager/UpdateManager.py +++ b/UpdateManager/UpdateManager.py @@ -356,7 +356,6 @@ class UpdateManager(SimpleGladeApp): self.set_changes_buffer(changes_buffer, changes[0], name, changes[1]) else: if self.expander_details.get_expanded(): - self.hbox_footer.set_sensitive(False) lock = thread.allocate_lock() lock.acquire() t=thread.start_new_thread(self.cache.get_changelog,(name,lock)) @@ -373,7 +372,6 @@ class UpdateManager(SimpleGladeApp): # download finished (or canceld, or time-out) button.hide() button.disconnect(id); - self.hbox_footer.set_sensitive(True) if self.cache.all_changes.has_key(name): changes = self.cache.all_changes[name] -- cgit v1.2.3 From 53e9180e0aaafae7f93d939b5f97d377990aaeb5 Mon Sep 17 00:00:00 2001 From: Sebastian Heinlein Date: Sun, 19 Feb 2006 11:20:21 +0100 Subject: * ReleaseNotesViewer: - Add copyright, license, comments - Use the normal cursor instead of the xterm one - Set the cursor only if the hovering state changed to avoid flickering --- UpdateManager/ReleaseNotesViewer.py | 84 ++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 11 deletions(-) (limited to 'UpdateManager') diff --git a/UpdateManager/ReleaseNotesViewer.py b/UpdateManager/ReleaseNotesViewer.py index 9cecc895..33122be9 100644 --- a/UpdateManager/ReleaseNotesViewer.py +++ b/UpdateManager/ReleaseNotesViewer.py @@ -1,4 +1,28 @@ -#!/usr/bin/env python +# ReleaseNotesViewer.py +# +# Copyright (c) 2006 Sebastian Heinlein +# +# Author: Sebastian Heinlein +# +# This modul provides an inheritance of the gtk.TextView that is +# aware of http URLs and allows to open them in a browser. +# It is based on the pygtk-demo "hypertext". +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + import pygtk import gtk @@ -8,34 +32,49 @@ import os class ReleaseNotesViewer(gtk.TextView): def __init__(self, notes): + """Init the ReleaseNotesViewer as an Inheritance of the gtk.TextView. + Load the notes into the buffer and make links clickable""" + # init the parent gtk.TextView.__init__(self) + # global hovering over link state self.hovering = False + self.first = True + # setup the buffer and signals + self.set_property("editable", False) + self.set_cursor_visible(False) self.buffer = gtk.TextBuffer() self.set_buffer(self.buffer) self.buffer.set_text(notes) self.connect("event-after", self.event_after) self.connect("motion-notify-event", self.motion_notify_event) self.connect("visibility-notify-event", self.visibility_notify_event) + # search for links in the notes and make them clickable self.search_links() - self.set_property("editable", False) - self.set_cursor_visible(False) def tag_link(self, start, end, url): + """Apply the tag that marks links to the specified buffer selection""" tag = self.buffer.create_tag(None, foreground="blue", underline=pango.UNDERLINE_SINGLE) tag.set_data("url", url) self.buffer.apply_tag(tag , start, end) def search_links(self): + """Search for http URLs in the buffer and call the tag_link method + for each one to tag them as links""" + # start at the beginning of the buffer iter = self.buffer.get_iter_at_offset(0) while 1: + # search for the next URL in the buffer ret = iter.forward_search("http://", gtk.TEXT_SEARCH_VISIBLE_ONLY, None) + # if we reach the end break the loop if not ret: break + # get the position of the protocol prefix (match_start, match_end) = ret match_tmp = match_end.copy() while 1: + # extend the selection to the complete URL if match_tmp.forward_char(): text = match_end.get_text(match_tmp) if text in (" ", ")", "]", "\n", "\t"): @@ -43,16 +82,21 @@ class ReleaseNotesViewer(gtk.TextView): else: break match_end = match_tmp.copy() + # call the tagging method for the complete URL url = match_start.get_text(match_end) self.tag_link(match_start, match_end, url) + # set the starting point for the next search iter = match_end def event_after(self, text_view, event): + """callback for mouse click events""" + # we only react on left mouse clicks if event.type != gtk.gdk.BUTTON_RELEASE: return False if event.button != 1: return False + # try to get a selection try: (start, end) = self.buffer.get_selection_bounds() except ValueError: @@ -61,10 +105,12 @@ class ReleaseNotesViewer(gtk.TextView): if start.get_offset() != end.get_offset(): return False + # get the iter at the mouse position (x, y) = self.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, int(event.x), int(event.y)) iter = self.get_iter_at_location(x, y) + # call open_url if an URL is assigned to the iter tags = iter.get_tags() for tag in tags: url = tag.get_data("url") @@ -73,17 +119,22 @@ class ReleaseNotesViewer(gtk.TextView): break def open_url(self, url): + """Open the specified URL in a browser""" + # Find an appropiate browser if os.path.exists('usr/bin/gnome-open'): command = ['gnome-open', url] else: command = ['x-www-browser', url] + # Avoid to run the browser as user root if os.getuid() == 0 and os.environ.has_key('SUDO_USER'): command = ['sudo', '-u', os.environ['SUDO_USER']] + command subprocess.Popen(command) def motion_notify_event(self, text_view, event): + """callback for the mouse movement event, that calls the + check_hovering method with the mouse postition coordiantes""" x, y = text_view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, int(event.x), int(event.y)) self.check_hovering(x, y) @@ -91,6 +142,9 @@ class ReleaseNotesViewer(gtk.TextView): return False def visibility_notify_event(self, text_view, event): + """callback if the widgets gets visible (e.g. moves to the foreground) + that calls the check_hovering method with the mouse position + coordinates""" (wx, wy, mod) = text_view.window.get_pointer() (bx, by) = text_view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, wx, wy) @@ -98,20 +152,28 @@ class ReleaseNotesViewer(gtk.TextView): return False def check_hovering(self, x, y): + """Check if the mouse is above a tagged link and if yes show + a hand cursor""" _hovering = False + # get the iter at the mouse position iter = self.get_iter_at_location(x, y) - + + # set _hovering if the iter has the tag "url" tags = iter.get_tags() for tag in tags: url = tag.get_data("url") if url != "": _hovering = True break - - if _hovering != self.hovering: - self.hovering = _hovering - if self.hovering: - self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) - else: - self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM)) + # change the global hovering state + if _hovering != self.hovering or self.first == True: + self.first = False + self.hovering = _hovering + # Set the appropriate cursur icon + if self.hovering: + self.get_window(gtk.TEXT_WINDOW_TEXT).\ + set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) + else: + self.get_window(gtk.TEXT_WINDOW_TEXT).\ + set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) -- cgit v1.2.3