Merge lp:~gary-lasker/software-center/launcher-integration-for-p into lp:software-center

Proposed by Gary Lasker
Status: Merged
Merged at revision: 2575
Proposed branch: lp:~gary-lasker/software-center/launcher-integration-for-p
Merge into: lp:software-center
Diff against target: 748 lines (+266/-243)
6 files modified
data/ui/gtk3/SoftwareCenter.ui (+20/-4)
softwarecenter/backend/unitylauncher.py (+98/-0)
softwarecenter/ui/gtk3/app.py (+35/-4)
softwarecenter/ui/gtk3/panes/availablepane.py (+91/-2)
softwarecenter/ui/gtk3/panes/softwarepane.py (+2/-193)
test/gtk3/test_unity_launcher_integration.py (+20/-40)
To merge this branch: bzr merge lp:~gary-lasker/software-center/launcher-integration-for-p
Reviewer Review Type Date Requested Status
Michael Vogt Pending
Review via email: mp+83953@code.launchpad.net

Description of the change

This branch implements the Software Center side of mpt's updated design for launcher integration functionality for Precise (https://wiki.ubuntu.com/SoftwareCenter#Learning_how_to_launch_an_application). It removes the explicit slide-in prompt in the details view for adding an item to the launcher, and instead always adds the item (or not) depending on the state of the new menu item "View->Add Applications in Launcher". A nice side-effect of the new design is that it now supports adding items to the launcher when they are installed from the details view.

There is also some refactoring to clean things up a bit and to set the stage for an easy update when the full Unity side implementation is ready. There's a new UnityLauncher class and you'll notice that I migrated the UI-side launcher integration code out of SoftwarePane and down into AvailablePane (there's no longer a reason for it to be in the superclass since we no longer use the ChannelPane).

The current implementation in the branch is designed to work using the Unity launcher dbus capabilities as they exist now. As such, it adds the application to the launcher once it has been installed, and does not yet display the install progress in the launcher. That functionality will come after the Unity side implementation is completed.

Finally, the supporting unit tests are updated and working.

To post a comment you must log in.
Revision history for this message
Gary Lasker (gary-lasker) wrote :

Btw, the code in availablepane simplifies *a lot* once we get the full Unity implementation. Most of that code is just to queue up simultaneous installs to be able to do the desktop file and icon location updates as required for the current Unity dbus implementation.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'data/ui/gtk3/SoftwareCenter.ui'
--- data/ui/gtk3/SoftwareCenter.ui 2011-11-07 09:41:23 +0000
+++ data/ui/gtk3/SoftwareCenter.ui 2011-11-30 14:34:40 +0000
@@ -381,7 +381,7 @@
381 <property name="use_underline">True</property>381 <property name="use_underline">True</property>
382 <signal name="activate" handler="on_menu_view_activate" swapped="no"/>382 <signal name="activate" handler="on_menu_view_activate" swapped="no"/>
383 <child type="submenu">383 <child type="submenu">
384 <object class="GtkMenu" id="menu4">384 <object class="GtkMenu" id="menu_view">
385 <property name="visible">True</property>385 <property name="visible">True</property>
386 <property name="can_focus">False</property>386 <property name="can_focus">False</property>
387 <child>387 <child>
@@ -420,9 +420,9 @@
420 <property name="visible">True</property>420 <property name="visible">True</property>
421 <property name="can_focus">False</property>421 <property name="can_focus">False</property>
422 <property name="related_action">navhistory_back_action</property>422 <property name="related_action">navhistory_back_action</property>
423 <property name="use_action_appearance">False</property>
423 <property name="use_underline">True</property>424 <property name="use_underline">True</property>
424 <property name="use_stock">False</property>425 <property name="use_stock">False</property>
425 <property name="use_action_appearance">False</property>
426 <accelerator key="bracketleft" signal="activate" modifiers="GDK_CONTROL_MASK"/>426 <accelerator key="bracketleft" signal="activate" modifiers="GDK_CONTROL_MASK"/>
427 </object>427 </object>
428 </child>428 </child>
@@ -432,12 +432,28 @@
432 <property name="visible">True</property>432 <property name="visible">True</property>
433 <property name="can_focus">False</property>433 <property name="can_focus">False</property>
434 <property name="related_action">navhistory_forward_action</property>434 <property name="related_action">navhistory_forward_action</property>
435 <property name="use_action_appearance">False</property>
435 <property name="use_underline">True</property>436 <property name="use_underline">True</property>
436 <property name="use_stock">False</property>437 <property name="use_stock">False</property>
437 <property name="use_action_appearance">False</property>
438 <accelerator key="bracketright" signal="activate" modifiers="GDK_CONTROL_MASK"/>438 <accelerator key="bracketright" signal="activate" modifiers="GDK_CONTROL_MASK"/>
439 </object>439 </object>
440 </child>440 </child>
441 <child>
442 <object class="GtkSeparatorMenuItem" id="add_to_launcher_separator">
443 <property name="visible">True</property>
444 <property name="can_focus">False</property>
445 </object>
446 </child>
447 <child>
448 <object class="GtkCheckMenuItem" id="menuitem_add_to_launcher">
449 <property name="visible">True</property>
450 <property name="can_focus">False</property>
451 <property name="use_action_appearance">False</property>
452 <property name="label" translatable="yes">_New Applications in Launcher</property>
453 <property name="use_underline">True</property>
454 <signal name="toggled" handler="on_menuitem_add_to_launcher_toggled" swapped="no"/>
455 </object>
456 </child>
441 </object>457 </object>
442 </child>458 </child>
443 </object>459 </object>
@@ -476,7 +492,7 @@
476 <property name="visible">True</property>492 <property name="visible">True</property>
477 <property name="can_focus">False</property>493 <property name="can_focus">False</property>
478 <property name="use_action_appearance">False</property>494 <property name="use_action_appearance">False</property>
479 <property name="use_underline">False</property>495 <property name="use_stock">False</property>
480 <signal name="activate" handler="on_menuitem_developer_activate" swapped="no"/>496 <signal name="activate" handler="on_menuitem_developer_activate" swapped="no"/>
481 </object>497 </object>
482 </child>498 </child>
483499
=== added file 'softwarecenter/backend/unitylauncher.py'
--- softwarecenter/backend/unitylauncher.py 1970-01-01 00:00:00 +0000
+++ softwarecenter/backend/unitylauncher.py 2011-11-30 14:34:40 +0000
@@ -0,0 +1,98 @@
1# Copyright (C) 2011 Canonical
2#
3# Authors:
4# Gary Lasker
5#
6# This program is free software; you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation; version 3.
9#
10# This program is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13# details.
14#
15# You should have received a copy of the GNU General Public License along with
16# this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19import dbus
20import logging
21
22LOG = logging.getLogger(__name__)
23
24class UnityLauncherInfo(object):
25 """ Simple class to keep track of application details needed for
26 Unity launcher integration
27 """
28 def __init__(self,
29 name,
30 icon_name,
31 icon_file_path,
32 icon_x,
33 icon_y,
34 icon_size,
35 app_install_desktop_file_path,
36 installed_desktop_file_path,
37 trans_id):
38 self.name = name
39 self.icon_name = icon_name
40 self.icon_file_path = icon_file_path
41 self.icon_x = icon_x
42 self.icon_y = icon_y
43 self.icon_size = icon_size
44 self.app_install_desktop_file_path = app_install_desktop_file_path
45 self.installed_desktop_file_path = installed_desktop_file_path
46 self.trans_id = trans_id
47
48class UnityLauncher(object):
49 """ Implements the integration between Software Center and the Unity
50 launcher
51 """
52 def __init__(self):
53 # keep track of applications that are candidates for adding
54 # to the Unity launcher
55 self.launcher_queue = {}
56
57 def add_to_launcher_queue(self, pkgname, launcher_info):
58 """ add an application and its associated info to the set of candidates
59 for adding to the Unity launcher
60 """
61 self.launcher_queue[pkgname] = launcher_info
62
63 def remove_from_launcher_queue(self, pkgname):
64 """ remove an application and its associated info from the set of
65 candidates for adding to the Unity launcher
66 """
67 if pkgname in self.launcher_queue:
68 return self.launcher_queue.pop(pkgname)
69
70 def send_application_to_launcher(self, pkgname, launcher_info):
71 LOG.debug("sending dbus signal to Unity launcher for application: ",
72 launcher_info.name)
73 LOG.debug(" launcher_info.icon_file_path: ",
74 launcher_info.icon_file_path)
75 LOG.debug(" launcher_info.installed_desktop_file_path: ",
76 launcher_info.installed_desktop_file_path)
77 LOG.debug(" launcher_info.trans_id: ", launcher_info.trans_id)
78 # the application is being added to the launcher, so clear it from the
79 # list of candidates in the launcher queue
80 self.remove_from_launcher_queue(pkgname)
81 # add the application by sending a dbus signal to the Unity launcher
82 try:
83 bus = dbus.SessionBus()
84 launcher_obj = bus.get_object('com.canonical.Unity.Launcher',
85 '/com/canonical/Unity/Launcher')
86 launcher_iface = dbus.Interface(launcher_obj,
87 'com.canonical.Unity.Launcher')
88 launcher_iface.AddLauncherItemFromPosition(
89 launcher_info.name,
90 launcher_info.icon_file_path,
91 launcher_info.icon_x,
92 launcher_info.icon_y,
93 launcher_info.icon_size,
94 launcher_info.installed_desktop_file_path,
95 launcher_info.trans_id)
96 except Exception as e:
97 LOG.warn("could not send dbus signal to the Unity launcher: (%s)",
98 e)
099
=== modified file 'softwarecenter/ui/gtk3/app.py'
--- softwarecenter/ui/gtk3/app.py 2011-11-24 14:48:07 +0000
+++ softwarecenter/ui/gtk3/app.py 2011-11-30 14:34:40 +0000
@@ -61,7 +61,8 @@
61 MOUSE_EVENT_BACK_BUTTON)61 MOUSE_EVENT_BACK_BUTTON)
62from softwarecenter.utils import (clear_token_from_ubuntu_sso,62from softwarecenter.utils import (clear_token_from_ubuntu_sso,
63 get_http_proxy_string_from_gsettings,63 get_http_proxy_string_from_gsettings,
64 wait_for_apt_cache_ready)64 wait_for_apt_cache_ready,
65 is_unity_running)
65from softwarecenter.ui.gtk3.utils import (get_sc_icon_theme,66from softwarecenter.ui.gtk3.utils import (get_sc_icon_theme,
66 init_sc_css_provider)67 init_sc_css_provider)
67from softwarecenter.version import VERSION68from softwarecenter.version import VERSION
@@ -350,7 +351,7 @@
350 supported_menuitem = self.builder.get_object("menuitem_view_supported_only")351 supported_menuitem = self.builder.get_object("menuitem_view_supported_only")
351 supported_menuitem.set_label(self.distro.get_supported_filter_name())352 supported_menuitem.set_label(self.distro.get_supported_filter_name())
352 file_menu = self.builder.get_object("menu1")353 file_menu = self.builder.get_object("menu1")
353354
354 if not self.distro.DEVELOPER_URL:355 if not self.distro.DEVELOPER_URL:
355 help_menu = self.builder.get_object("menu_help")356 help_menu = self.builder.get_object("menu_help")
356 developer_separator = self.builder.get_object("separator_developer")357 developer_separator = self.builder.get_object("separator_developer")
@@ -362,6 +363,20 @@
362 och = is_oneconf_available()363 och = is_oneconf_available()
363 if not och:364 if not och:
364 file_menu.remove(self.builder.get_object("menuitem_sync_between_computers"))365 file_menu.remove(self.builder.get_object("menuitem_sync_between_computers"))
366
367 # restore the state of the add to launcher menu item, or remove the menu
368 # item if Unity is not currently running
369 add_to_launcher_menuitem = self.builder.get_object(
370 "menuitem_add_to_launcher")
371 if is_unity_running():
372 add_to_launcher_menuitem.set_active(
373 self.available_pane.add_to_launcher_enabled)
374 else:
375 view_menu = self.builder.get_object("menu_view")
376 add_to_launcher_separator = self.builder.get_object(
377 "add_to_launcher_separator")
378 view_menu.remove(add_to_launcher_separator)
379 view_menu.remove(add_to_launcher_menuitem)
365380
366 # run s-c-agent update381 # run s-c-agent update
367 if options.disable_buy or not self.distro.PURCHASE_APP_URL:382 if options.disable_buy or not self.distro.PURCHASE_APP_URL:
@@ -963,6 +978,9 @@
963 def on_navhistory_forward_action_activate(self, navhistory_forward_action=None):978 def on_navhistory_forward_action_activate(self, navhistory_forward_action=None):
964 vm = get_viewmanager()979 vm = get_viewmanager()
965 vm.nav_forward()980 vm.nav_forward()
981
982 def on_menuitem_add_to_launcher_toggled(self, menu_item):
983 self.available_pane.add_to_launcher_enabled = menu_item.get_active()
966984
967# Help Menu985# Help Menu
968 def on_menuitem_about_activate(self, widget):986 def on_menuitem_about_activate(self, widget):
@@ -1154,11 +1172,20 @@
1154 # in case of a crazy-huge monitor)1172 # in case of a crazy-huge monitor)
1155 screen_height = Gdk.Screen.height()1173 screen_height = Gdk.Screen.height()
1156 screen_width = Gdk.Screen.width()1174 screen_width = Gdk.Screen.width()
1157 self.window_main.set_default_size(min(int(.85 * screen_width), 1200),1175 self.window_main.set_default_size(
1158 min(int(.85 * screen_height), 800))1176 min(int(.85 * screen_width), 1200),
1177 min(int(.85 * screen_height), 800))
1159 if (self.config.has_option("general", "maximized") and1178 if (self.config.has_option("general", "maximized") and
1160 self.config.getboolean("general", "maximized")):1179 self.config.getboolean("general", "maximized")):
1161 self.window_main.maximize()1180 self.window_main.maximize()
1181 if self.config.has_option("general", "add_to_launcher"):
1182 self.available_pane.add_to_launcher_enabled = (
1183 self.config.getboolean(
1184 "general",
1185 "add_to_launcher"))
1186 else:
1187 # initial default state is to add to launcher, per spec
1188 self.available_pane.add_to_launcher_enabled = True
11621189
1163 def save_state(self):1190 def save_state(self):
1164 LOG.debug("save_state")1191 LOG.debug("save_state")
@@ -1176,6 +1203,10 @@
1176 # size only matters when non-maximized1203 # size only matters when non-maximized
1177 size = self.window_main.get_size() 1204 size = self.window_main.get_size()
1178 self.config.set("general","size", "%s, %s" % (size[0], size[1]))1205 self.config.set("general","size", "%s, %s" % (size[0], size[1]))
1206 if self.available_pane.add_to_launcher_enabled:
1207 self.config.set("general", "add_to_launcher", "True")
1208 else:
1209 self.config.set("general", "add_to_launcher", "False")
1179 self.config.write()1210 self.config.write()
11801211
1181 def run(self, args):1212 def run(self, args):
11821213
=== modified file 'softwarecenter/ui/gtk3/panes/availablepane.py'
--- softwarecenter/ui/gtk3/panes/availablepane.py 2011-11-17 00:28:02 +0000
+++ softwarecenter/ui/gtk3/panes/availablepane.py 2011-11-30 14:34:40 +0000
@@ -21,7 +21,9 @@
21from gi.repository import Gtk21from gi.repository import Gtk
22import logging22import logging
23import xapian23import xapian
24import os
2425
26import softwarecenter.utils
25import softwarecenter.ui.gtk3.dialogs as dialogs27import softwarecenter.ui.gtk3.dialogs as dialogs
26from softwarecenter.ui.gtk3.models.appstore2 import AppListStore28from softwarecenter.ui.gtk3.models.appstore2 import AppListStore
2729
@@ -30,9 +32,13 @@
30from softwarecenter.enums import (ActionButtons,32from softwarecenter.enums import (ActionButtons,
31 NavButtons,33 NavButtons,
32 NonAppVisibility,34 NonAppVisibility,
33 DEFAULT_SEARCH_LIMIT)35 DEFAULT_SEARCH_LIMIT,
36 TransactionTypes)
34from softwarecenter.paths import APP_INSTALL_PATH37from softwarecenter.paths import APP_INSTALL_PATH
35from softwarecenter.utils import wait_for_apt_cache_ready38from softwarecenter.utils import (wait_for_apt_cache_ready,
39 is_no_display_desktop_file,
40 convert_desktop_file_to_installed_location,
41 get_file_path_from_iconname)
36from softwarecenter.db.appfilter import AppFilter42from softwarecenter.db.appfilter import AppFilter
37from softwarecenter.db.database import Application43from softwarecenter.db.database import Application
38from softwarecenter.ui.gtk3.views.purchaseview import PurchaseView44from softwarecenter.ui.gtk3.views.purchaseview import PurchaseView
@@ -42,6 +48,8 @@
42from softwarepane import SoftwarePane48from softwarepane import SoftwarePane
43from softwarecenter.ui.gtk3.session.viewmanager import get_viewmanager49from softwarecenter.ui.gtk3.session.viewmanager import get_viewmanager
44from softwarecenter.ui.gtk3.session.appmanager import get_appmanager50from softwarecenter.ui.gtk3.session.appmanager import get_appmanager
51from softwarecenter.backend.unitylauncher import (UnityLauncher,
52 UnityLauncherInfo)
4553
46LOG = logging.getLogger(__name__)54LOG = logging.getLogger(__name__)
4755
@@ -91,6 +99,14 @@
91 # views to be created in init_view99 # views to be created in init_view
92 self.cat_view = None100 self.cat_view = None
93 self.subcategories_view = None101 self.subcategories_view = None
102
103 # integrate with the Unity launcher
104 self.unity_launcher = UnityLauncher()
105
106 # flag to indicate whether applications should be added to the
107 # unity launcher when installed (this value is initialized by
108 # the config load in app.py)
109 self.add_to_launcher_enabled = True
94110
95 def init_view(self):111 def init_view(self):
96 if self.view_initialized: 112 if self.view_initialized:
@@ -179,6 +195,10 @@
179195
180 # install backend196 # install backend
181 self.backend.connect("transactions-changed", self._on_transactions_changed)197 self.backend.connect("transactions-changed", self._on_transactions_changed)
198 self.backend.connect("transaction-started", self.on_transaction_started)
199 self.backend.connect("transaction-finished", self.on_transaction_finished)
200 self.backend.connect("transaction-stopped", self.on_transaction_stopped)
201
182 # now we are initialized202 # now we are initialized
183 self.searchentry.set_sensitive(True)203 self.searchentry.set_sensitive(True)
184 self.emit("available-pane-created")204 self.emit("available-pane-created")
@@ -322,6 +342,75 @@
322 """342 """
323 if self._is_custom_list_search(self.state.search_term):343 if self._is_custom_list_search(self.state.search_term):
324 self._update_action_bar()344 self._update_action_bar()
345
346 def on_transaction_started(self, backend, pkgname, appname, trans_id,
347 trans_type):
348 self._register_unity_launcher_transaction_started(
349 backend, pkgname, appname, trans_id, trans_type)
350
351 def _register_unity_launcher_transaction_started(self, backend, pkgname,
352 appname, trans_id,
353 trans_type):
354 if not self.add_to_launcher_enabled:
355 return
356 # mvo: use use softwarecenter.utils explictly so that we can monkey
357 # patch it in the test
358 if not softwarecenter.utils.is_unity_running():
359 return
360 # we only care about getting the launcher information on an install
361 if not trans_type == TransactionTypes.INSTALL:
362 return
363 # gather details for this transaction and create the launcher_info object
364 app = Application(pkgname=pkgname, appname=appname)
365 appdetails = app.get_details(self.db)
366 # we only add items to the launcher that have a desktop file
367 if not appdetails.desktop_file:
368 return
369 # do not add apps without a exec line (like wine, see #848437)
370 if (os.path.exists(appdetails.desktop_file) and
371 is_no_display_desktop_file(appdetails.desktop_file)):
372 return
373
374 (icon_size, icon_x, icon_y) = self._get_onscreen_icon_details_for_launcher_service(app)
375 launcher_info = UnityLauncherInfo(app.name,
376 appdetails.icon,
377 "", # we set the icon_file_path value *after* install
378 icon_x,
379 icon_y,
380 icon_size,
381 appdetails.desktop_file,
382 "", # we set the installed_desktop_file_path *after* install
383 trans_id)
384 self.unity_launcher.add_to_launcher_queue(app.pkgname, launcher_info)
385
386 def _get_onscreen_icon_details_for_launcher_service(self, app):
387 if self.is_app_details_view_showing():
388 return self.app_details_view.get_app_icon_details()
389 else:
390 # TODO: implement the app list view case once it has been specified
391 return (0, 0, 0)
392
393 def on_transaction_finished(self, backend, result):
394 self._check_unity_launcher_transaction_finished(result)
395
396 def _check_unity_launcher_transaction_finished(self, result):
397 # add the completed transaction details to the corresponding
398 # launcher_item
399 if result.pkgname in self.unity_launcher.launcher_queue:
400 launcher_info = self.unity_launcher.launcher_queue[result.pkgname]
401 launcher_info.icon_file_path = get_file_path_from_iconname(
402 self.icons, launcher_info.icon_name)
403 installed_path = convert_desktop_file_to_installed_location(
404 launcher_info.app_install_desktop_file_path, result.pkgname)
405 launcher_info.installed_desktop_file_path = installed_path
406 if result.success:
407 self.unity_launcher.send_application_to_launcher(
408 result.pkgname, launcher_info)
409 else:
410 self.unity_launcher.remove_from_launcher_queue(result.pkgname)
411
412 def on_transaction_stopped(self, backend, result):
413 self.unity_launcher.remove_from_launcher_queue(result.pkgname)
325414
326 def on_app_list_changed(self, pane, length):415 def on_app_list_changed(self, pane, length):
327 """internal helper that keeps the status text and the action416 """internal helper that keeps the status text and the action
328417
=== modified file 'softwarecenter/ui/gtk3/panes/softwarepane.py'
--- softwarecenter/ui/gtk3/panes/softwarepane.py 2011-10-25 18:38:08 +0000
+++ softwarecenter/ui/gtk3/panes/softwarepane.py 2011-11-30 14:34:40 +0000
@@ -17,7 +17,6 @@
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1818
19from gi.repository import Atk19from gi.repository import Atk
20import dbus
21import gettext20import gettext
22from gi.repository import GObject21from gi.repository import GObject
23from gi.repository import Gtk, Gdk22from gi.repository import Gtk, Gdk
@@ -26,21 +25,14 @@
26import os25import os
27import xapian26import xapian
2827
29from gettext import gettext as _
30
31import softwarecenter.utils
32from softwarecenter.backend import get_install_backend28from softwarecenter.backend import get_install_backend
33from softwarecenter.db.database import Application29from softwarecenter.db.database import Application
34from softwarecenter.db.enquire import AppEnquire30from softwarecenter.db.enquire import AppEnquire
35from softwarecenter.enums import (ActionButtons,31from softwarecenter.enums import (SortMethods,
36 SortMethods,
37 TransactionTypes,
38 DEFAULT_SEARCH_LIMIT,32 DEFAULT_SEARCH_LIMIT,
39 NonAppVisibility)33 NonAppVisibility)
4034
41from softwarecenter.utils import (ExecutionTime,35from softwarecenter.utils import (ExecutionTime,
42 convert_desktop_file_to_installed_location,
43 get_file_path_from_iconname,
44 wait_for_apt_cache_ready,36 wait_for_apt_cache_ready,
45 utf837 utf8
46 )38 )
@@ -55,39 +47,10 @@
55 AppDetailsViewGtk as47 AppDetailsViewGtk as
56 AppDetailsView)48 AppDetailsView)
5749
58from softwarecenter.utils import is_no_display_desktop_file
59
60from basepane import BasePane50from basepane import BasePane
6151
62LOG = logging.getLogger(__name__)52LOG = logging.getLogger(__name__)
6353
64
65class UnityLauncherInfo(object):
66 """ Simple class to keep track of application details needed for
67 Unity launcher integration
68 """
69 def __init__(self,
70 name,
71 icon_name,
72 icon_file_path,
73 icon_x,
74 icon_y,
75 icon_size,
76 app_install_desktop_file_path,
77 installed_desktop_file_path,
78 trans_id):
79 self.name = name
80 self.icon_name = icon_name
81 self.icon_file_path = icon_file_path
82 self.icon_x = icon_x
83 self.icon_y = icon_y
84 self.icon_size = icon_size
85 self.app_install_desktop_file_path = app_install_desktop_file_path
86 self.installed_desktop_file_path = installed_desktop_file_path
87 self.trans_id = trans_id
88 self.add_to_launcher_requested = False
89
90
91# for DisplayState attribute type-checking54# for DisplayState attribute type-checking
92from softwarecenter.db.categories import Category55from softwarecenter.db.categories import Category
93from softwarecenter.backend.channel import SoftwareChannel56from softwarecenter.backend.channel import SoftwareChannel
@@ -202,9 +165,6 @@
202 # request (e.g. people click on ubuntu channel, get impatient, click165 # request (e.g. people click on ubuntu channel, get impatient, click
203 # on partner channel)166 # on partner channel)
204 self.refresh_seq_nr = 0167 self.refresh_seq_nr = 0
205 # keep track of applications that are candidates to be added
206 # to the Unity launcher
207 self.unity_launcher_items = {}
208 # this should be initialized168 # this should be initialized
209 self.apps_search_term = ""169 self.apps_search_term = ""
210 # Create the basic frame for the common view170 # Create the basic frame for the common view
@@ -282,14 +242,9 @@
282 # when the cache changes, refresh the app list242 # when the cache changes, refresh the app list
283 self.cache.connect("cache-ready", self.on_cache_ready)243 self.cache.connect("cache-ready", self.on_cache_ready)
284244
285 # aptdaemon
286 self.backend.connect("transaction-started", self.on_transaction_started)
287 self.backend.connect("transaction-finished", self.on_transaction_finished)
288 self.backend.connect("transaction-stopped", self.on_transaction_stopped)
289
290 # connect signals245 # connect signals
291 self.connect("app-list-changed", self.on_app_list_changed)246 self.connect("app-list-changed", self.on_app_list_changed)
292 247
293 # db reopen248 # db reopen
294 if self.db:249 if self.db:
295 self.db.connect("reopen", self.on_db_reopen)250 self.db.connect("reopen", self.on_db_reopen)
@@ -335,81 +290,6 @@
335 vm = get_viewmanager()290 vm = get_viewmanager()
336 vm.nav_forward()291 vm.nav_forward()
337292
338 def on_transaction_started(self, backend, pkgname, appname, trans_id,
339 trans_type):
340 self._register_unity_launcher_transaction_started(
341 backend, pkgname, appname, trans_id, trans_type)
342
343
344 def _get_onscreen_icon_details_for_launcher_service(self, app):
345 if self.is_app_details_view_showing():
346 return self.app_details_view.get_app_icon_details()
347 else:
348 # TODO: implement the app list view case once it has been specified
349 return (0, 0, 0)
350
351 def _register_unity_launcher_transaction_started(self, backend, pkgname,
352 appname, trans_id,
353 trans_type):
354 # mvo: use use softwarecenter.utils explictely so that we can monkey
355 # patch it in the test
356 if not softwarecenter.utils.is_unity_running():
357 return
358 # add to launcher only applies in the details view currently
359 if not self.is_app_details_view_showing():
360 return
361 # we only care about getting the launcher information on an install
362 if not trans_type == TransactionTypes.INSTALL:
363 if pkgname in self.unity_launcher_items:
364 self.unity_launcher_items.pop(pkgname)
365 self.action_bar.clear()
366 return
367 # gather details for this transaction and create the launcher_info object
368 app = Application(pkgname=pkgname, appname=appname)
369 appdetails = app.get_details(self.db)
370 (icon_size, icon_x, icon_y) = self._get_onscreen_icon_details_for_launcher_service(app)
371 launcher_info = UnityLauncherInfo(app.name,
372 appdetails.icon,
373 "", # we set the icon_file_path value *after* install
374 icon_x,
375 icon_y,
376 icon_size,
377 appdetails.desktop_file,
378 "", # we set the installed_desktop_file_path *after* install
379 trans_id)
380 self.unity_launcher_items[app.pkgname] = launcher_info
381 self.show_add_to_launcher_panel(backend, pkgname, appname, app, appdetails, trans_id, trans_type)
382
383 def show_add_to_launcher_panel(self, backend, pkgname, appname, app, appdetails, trans_id, trans_type):
384 """
385 if Unity is currently running, display a panel to allow the user
386 the choose whether to add a newly-installed application to the
387 launcher
388 """
389 # TODO: handle local deb install case
390 # TODO: implement the list view case (once it is specified)
391 # only show the panel if unity is running and this is a package install
392 #
393 # we only show the prompt for apps with a desktop file
394 if not appdetails.desktop_file:
395 return
396 # do not add apps without a exec line (like wine, see #848437)
397 if (os.path.exists(appdetails.desktop_file) and
398 is_no_display_desktop_file(appdetails.desktop_file)):
399 return
400 self.action_bar.add_button(ActionButtons.CANCEL_ADD_TO_LAUNCHER,
401 _("Not Now"),
402 self.on_cancel_add_to_launcher,
403 pkgname)
404 self.action_bar.add_button(ActionButtons.ADD_TO_LAUNCHER,
405 _("Add to Launcher"),
406 self.on_add_to_launcher,
407 pkgname,
408 app,
409 appdetails,
410 trans_id)
411 self.action_bar.set_label(utf8(_("Add %s to the launcher?")) % utf8(app.name))
412
413 def on_query_complete(self, enquirer):293 def on_query_complete(self, enquirer):
414 self.emit("app-list-changed", len(enquirer.matches))294 self.emit("app-list-changed", len(enquirer.matches))
415 self.app_view.display_matches(enquirer.matches,295 self.app_view.display_matches(enquirer.matches,
@@ -427,81 +307,10 @@
427 self._refresh_apps_with_apt_cache(query)307 self._refresh_apps_with_apt_cache(query)
428 return308 return
429309
430 def on_add_to_launcher(self, pkgname, app, appdetails, trans_id):
431 """
432 callback indicating the user has chosen to add the indicated application
433 to the launcher
434 """
435 if pkgname in self.unity_launcher_items:
436 launcher_info = self.unity_launcher_items[pkgname]
437 if launcher_info.installed_desktop_file_path:
438 # package install is complete, we can add to the launcher immediately
439 self.unity_launcher_items.pop(pkgname)
440 self.action_bar.clear()
441 self._send_dbus_signal_to_unity_launcher(launcher_info)
442 else:
443 # package is not yet installed, it will be added to the launcher
444 # once the installation is complete
445 LOG.debug("the application '%s' will be added to the Unity launcher when installation is complete" % app.name)
446 launcher_info.add_to_launcher_requested = True
447 self.action_bar.set_label(_("%s will be added to the launcher when installation completes.") % app.name)
448 self.action_bar.remove_button(ActionButtons.CANCEL_ADD_TO_LAUNCHER)
449 self.action_bar.remove_button(ActionButtons.ADD_TO_LAUNCHER)
450
451 def on_cancel_add_to_launcher(self, pkgname):
452 if pkgname in self.unity_launcher_items:
453 self.unity_launcher_items.pop(pkgname)
454 self.action_bar.clear()
455
456 def on_transaction_finished(self, backend, result):
457 self._check_unity_launcher_transaction_finished(result)
458
459 def _is_in_search_mode(self):310 def _is_in_search_mode(self):
460 return (self.state.search_term and311 return (self.state.search_term and
461 len(self.state.search_term) >= 2)312 len(self.state.search_term) >= 2)
462313
463 def _check_unity_launcher_transaction_finished(self, result):
464 # add the completed transaction details to the corresponding
465 # launcher_item
466 if result.pkgname in self.unity_launcher_items:
467 launcher_info = self.unity_launcher_items[result.pkgname]
468 launcher_info.icon_file_path = get_file_path_from_iconname(
469 self.icons, launcher_info.icon_name)
470 installed_path = convert_desktop_file_to_installed_location(
471 launcher_info.app_install_desktop_file_path, result.pkgname)
472 launcher_info.installed_desktop_file_path = installed_path
473 # if the request to add to launcher has already been made, do it now
474 if launcher_info.add_to_launcher_requested:
475 if result.success:
476 self._send_dbus_signal_to_unity_launcher(launcher_info)
477 self.unity_launcher_items.pop(result.pkgname)
478 self.action_bar.clear()
479
480 def _send_dbus_signal_to_unity_launcher(self, launcher_info):
481 LOG.debug("sending dbus signal to Unity launcher for application: ", launcher_info.name)
482 LOG.debug(" launcher_info.icon_file_path: ", launcher_info.icon_file_path)
483 LOG.debug(" launcher_info.installed_desktop_file_path: ", launcher_info.installed_desktop_file_path)
484 LOG.debug(" launcher_info.trans_id: ", launcher_info.trans_id)
485 try:
486 bus = dbus.SessionBus()
487 launcher_obj = bus.get_object('com.canonical.Unity.Launcher',
488 '/com/canonical/Unity/Launcher')
489 launcher_iface = dbus.Interface(launcher_obj, 'com.canonical.Unity.Launcher')
490 launcher_iface.AddLauncherItemFromPosition(launcher_info.name,
491 launcher_info.icon_file_path,
492 launcher_info.icon_x,
493 launcher_info.icon_y,
494 launcher_info.icon_size,
495 launcher_info.installed_desktop_file_path,
496 launcher_info.trans_id)
497 except Exception as e:
498 LOG.warn("could not send dbus signal to the Unity launcher: (%s)", e)
499
500 def on_transaction_stopped(self, backend, result):
501 if result.pkgname in self.unity_launcher_items:
502 self.unity_launcher_items.pop(result.pkgname)
503 self.action_bar.clear()
504
505 def show_appview_spinner(self):314 def show_appview_spinner(self):
506 """ display the spinner in the appview panel """315 """ display the spinner in the appview panel """
507 if not self.state.search_term:316 if not self.state.search_term:
508317
=== modified file 'test/gtk3/test_unity_launcher_integration.py'
--- test/gtk3/test_unity_launcher_integration.py 2011-11-17 03:17:37 +0000
+++ test/gtk3/test_unity_launcher_integration.py 2011-11-30 14:34:40 +0000
@@ -54,36 +54,10 @@
54 TransactionTypes.INSTALL)54 TransactionTypes.INSTALL)
55 # wait a wee bit55 # wait a wee bit
56 self._zzz()56 self._zzz()
57
58 def test_unity_launcher_stays_after_install_finished(self):
59 test_pkgname = "gl-117"
60 mock_result = Mock()
61 mock_result.pkgname = test_pkgname
62 mock_result.success = True
63 # now pretend
64 # now pretend
65 self._navigate_to_appdetails_and_install(test_pkgname)
66 # pretend we are done
67 available_pane.backend.emit("transaction-finished", mock_result)
68 # this is normally set in the transaction-finished call but our
69 # app is not really installed so we need to mock it here
70 available_pane.unity_launcher_items[test_pkgname].installed_desktop_file_path = "/some/path"
71 # wait a wee bit
72 self._zzz()
73 # ensure we still have the button
74 button = available_pane.action_bar.get_button(
75 ActionButtons.ADD_TO_LAUNCHER)
76 self.assertNotEqual(button, None)
77 self.assertTrue(button.get_property("visible"))
78 # now click it even though the transaction is over
79 button.clicked()
80 self._zzz()
81 # ensure the add to launcher button is now hidden
82 button = available_pane.action_bar.get_button(
83 ActionButtons.ADD_TO_LAUNCHER)
84 self.assertEqual(button, None)
8557
86 def test_unity_launcher_integration(self):58 def test_unity_launcher_integration(self):
59 # test the automatic add to launcher enabled functionality
60 available_pane.add_to_launcher_enabled = True
87 test_pkgname = "lincity-ng"61 test_pkgname = "lincity-ng"
88 mock_result = Mock()62 mock_result = Mock()
89 mock_result.pkgname = test_pkgname63 mock_result.pkgname = test_pkgname
@@ -91,18 +65,10 @@
91 # now pretend65 # now pretend
92 self._navigate_to_appdetails_and_install(test_pkgname)66 self._navigate_to_appdetails_and_install(test_pkgname)
93 67
94 # verify that the panel is shown offering to add the app to the launcher
95 self.assertTrue(available_pane.action_bar.get_property("visible"))
96 button = available_pane.action_bar.get_button(
97 ActionButtons.ADD_TO_LAUNCHER)
98 self.assertTrue(button is not None)
99 # click the button
100 button.clicked()
101
102 # check that a correct UnityLauncherInfo object has been created and68 # check that a correct UnityLauncherInfo object has been created and
103 # added to the queue69 # added to the queue
104 self.assertTrue(test_pkgname in available_pane.unity_launcher_items)70 self.assertTrue(test_pkgname in available_pane.unity_launcher.launcher_queue)
105 launcher_info = available_pane.unity_launcher_items.pop(test_pkgname)71 launcher_info = available_pane.unity_launcher.remove_from_launcher_queue(test_pkgname)
106 # check the UnityLauncherInfo values themselves72 # check the UnityLauncherInfo values themselves
107 self.assertEqual(launcher_info.name, "lincity-ng")73 self.assertEqual(launcher_info.name, "lincity-ng")
108 self.assertEqual(launcher_info.icon_name, "lincity-ng")74 self.assertEqual(launcher_info.icon_name, "lincity-ng")
@@ -114,8 +80,22 @@
114 self.assertEqual(launcher_info.trans_id, "testid101")80 self.assertEqual(launcher_info.trans_id, "testid101")
115 # finally, make sure the the app has been removed from the launcher81 # finally, make sure the the app has been removed from the launcher
116 # queue 82 # queue
117 self.assertFalse(test_pkgname in available_pane.unity_launcher_items)83 self.assertFalse(test_pkgname in available_pane.unity_launcher.launcher_queue)
118 84
85 def test_unity_launcher_integration_disabled(self):
86 # test the case where automatic add to launcher is disabled
87 available_pane.add_to_launcher_enabled = False
88 test_pkgname = "lincity-ng"
89 mock_result = Mock()
90 mock_result.pkgname = test_pkgname
91 mock_result.success = True
92 # now pretend
93 self._navigate_to_appdetails_and_install(test_pkgname)
94
95 # check that no corresponding unity_launcher info object has been added
96 # to the queue
97 self.assertFalse(test_pkgname in available_pane.unity_launcher.launcher_queue)
98
119 def test_desktop_file_path_conversion(self):99 def test_desktop_file_path_conversion(self):
120 # test 'normal' case100 # test 'normal' case
121 app_install_desktop_path = ("./data/app-install/desktop/" +101 app_install_desktop_path = ("./data/app-install/desktop/" +

Subscribers

People subscribed via source and target branches