Ticket #3455: 0001-Show-Sugar-s-palette-when-right-click-is-pressed-SL-.3.patch

File 0001-Show-Sugar-s-palette-when-right-click-is-pressed-SL-.3.patch, 19.6 KB (added by humitos, 10 years ago)

v3 - close the palette after clicking on the MenuItem

  • browser.py

    From be2b10e008aebf7bb2d8ed5f1d782132a7192849 Mon Sep 17 00:00:00 2001
    From: Manuel Kaufmann <humitos@gmail.com>
    Date: Tue, 11 Sep 2012 16:04:58 -0300
    Subject: [PATCH v3 Browse] Show Sugar's palette when right click is pressed
     SL #3455
    
    When the user right clicks over a link, an image or selected text the
    Sugar's Palette is shown with the proper menu's items for each case.
    
    There are 4 possibilities. When the click is done over:
    
     - Link:
    
       - Title and subtitle (if they are availables)
         - title is the content text of the link
         - subtitle is the uri
       - Copy link (copy the link into the clipboard)
       - Keep link (download the .html to the Journal)
       - Follow link in new tab (open a new tab with the url pointed)
       - Follow link (follow the link inside the current tab)
    
     - Image:
    
       - Title and subtitle (if they are availables)
         - title is the "title" property of the <img>
         - subtitle is the uri
       - Keep image (download the image to the Journal)
       - Copy image (copy the image into the clipboard)
    
     - Image with a link:
    
       - Title and subtitle (if they are availables)
         - title is the "title" property of	the <img>
         - subtitle is the uri
    
       Same Items than "Link" option
    
     - Selected Text:
    
       - Title and subtitle (not implemented because the function
         webkit_web_view_get_selected_text was removed:
         https://bugs.webkit.org/show_bug.cgi?id=62512)
       - Copy text (copy the text into the clipboard)
    
    Signed-off-by: Simon Schampijer <simon@laptop.org>
    Signed-off-by: Manuel Kaufmann <humitos@gmail.com>
    ---
     browser.py  |   3 +
     palettes.py | 363 +++++++++++++++++++++++++++---------------------------------
     2 files changed, 166 insertions(+), 200 deletions(-)
    
    diff --git a/browser.py b/browser.py
    index 0d94649..5bf75e5 100644
    a b from sugar3.graphics import style 
    3434from sugar3.graphics.icon import Icon
    3535
    3636from widgets import BrowserNotebook
     37from palettes import ContentInvoker
    3738from filepicker import FilePicker
    3839import globalhistory
    3940import downloadmanager
    class Browser(WebKit.WebView): 
    484485        self.connect('new-window-policy-decision-requested',
    485486                     self.__new_window_policy_cb)
    486487
     488        ContentInvoker(self)
     489
    487490        try:
    488491            self.connect('run-file-chooser', self.__run_file_chooser)
    489492        except TypeError:
  • palettes.py

    diff --git a/palettes.py b/palettes.py
    index 01c34d4..95ec0cd 100644
    a b  
    1515# along with this program; if not, write to the Free Software
    1616# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    1717
     18import logging
    1819import os
    1920import tempfile
    20 import urlparse
     21import urllib2
     22
    2123from gettext import gettext as _
    2224
    2325from gi.repository import Gtk
    24 from gi.repository import GObject
     26from gi.repository import Gdk
     27from gi.repository import WebKit
    2528
    2629from sugar3.graphics.palette import Palette, Invoker
    27 from sugar3.graphics.menuitem import MenuItem
    28 from sugar3.graphics.icon import Icon
    2930from sugar3 import profile
    30 from sugar3.activity import activity
    31 
    32 import downloadmanager
    33 
    34 
    35 class MouseOutListener(GObject.GObject):
    36     _com_interfaces_ = interfaces.nsIDOMEventListener
    3731
    38     __gsignals__ = {
    39         'mouse-out': (GObject.SignalFlags.RUN_FIRST,
    40                       None,
    41                       ([])),
    42     }
    43 
    44     def __init__(self, target):
    45         GObject.GObject.__init__(self)
    46         self.target = target
    47 
    48     def handleEvent(self, event):
    49         self.emit('mouse-out')
     32from sugarmenuitem import SugarMenuItem
    5033
    5134
    5235class ContentInvoker(Invoker):
    53     _com_interfaces_ = interfaces.nsIDOMEventListener
    54 
    5536    def __init__(self, browser):
    5637        Invoker.__init__(self)
    5738        self._position_hint = self.AT_CURSOR
    5839        self._browser = browser
    59         self._mouseout_listener = None
    60         self._popdown_handler_id = None
     40        self._browser.connect('button-press-event', self.__button_press_cb)
     41        self.attach(self._browser)
    6142
    6243    def get_default_position(self):
    6344        return self.AT_CURSOR
    6445
    6546    def get_rect(self):
    66         return ()
     47        allocation = self._browser.get_allocation()
     48        window = self._browser.get_window()
     49        if window is not None:
     50            res, x, y = window.get_origin()
     51        else:
     52            logging.warning(
     53                "Trying to position palette with invoker that's not realized.")
     54            x = 0
     55            y = 0
     56
     57        x += allocation.x
     58        y += allocation.y
     59
     60        width = allocation.width
     61        height = allocation.height
     62
     63        rect = Gdk.Rectangle()
     64        rect.x = x
     65        rect.y = y
     66        rect.width = width
     67        rect.height = height
     68        return rect
    6769
    6870    def get_toplevel(self):
    6971        return None
    7072
    71     def handleEvent(self, event):
    72         if event.button != 2:
    73             return
     73    def __button_press_cb(self, browser, event):
     74        logging.debug('===> event: %s', event.button)
     75        if event.button != 3:
     76            return False
     77        hit_test = self._browser.get_hit_test_result(event)
     78        if hit_test.props.context & WebKit.HitTestResultContext.LINK:
     79            logging.debug('===> click LINK')
     80            link_uri = hit_test.props.link_uri
     81            if isinstance(hit_test.props.inner_node,
     82                            WebKit.DOMHTMLImageElement):
     83                title = hit_test.props.inner_node.get_title()
     84            elif isinstance(hit_test.props.inner_node, WebKit.DOMNode):
     85                title = hit_test.props.inner_node.get_text_content()
     86            self.palette = LinkPalette(self._browser, title, link_uri, None)
     87            self.notify_right_click()
     88        elif hit_test.props.context & WebKit.HitTestResultContext.IMAGE:
     89            logging.debug('===> click IMAGE %s %s',
     90                          hit_test.props.image_uri, hit_test.props.inner_node)
     91            title = hit_test.props.inner_node.get_title()
     92            self.palette = ImagePalette(self._browser, title,
     93                                        hit_test.props.image_uri, '')
     94            self.notify_right_click()
     95        elif hit_test.props.context & WebKit.HitTestResultContext.SELECTION:
     96            # TODO: find a way to get the selected text so we can use
     97            # it as the title of the Palette.
     98            # The function webkit_web_view_get_selected_text was removed
     99            # https://bugs.webkit.org/show_bug.cgi?id=62512
     100            title = None
     101
     102            # text = hit_test.props.inner_node.get_text_content()
     103            # import epdb;epdb.set_trace()
     104            # if len(text) > 20:
     105            #     title = text[:20] + '...'
     106            # else:
     107            #     title = text
     108            self.palette = SelectionPalette(self._browser, title, None, None)
     109            self.notify_right_click()
    74110
    75         target = event.target
     111        return True
    76112
    77         if target.tagName.lower() == 'a':
    78113
    79             if target.firstChild:
    80                 title = target.firstChild.nodeValue
    81             else:
    82                 title = None
     114class SelectionPalette(Palette):
     115    def __init__(self, browser, title, url, owner_document):
     116        Palette.__init__(self)
    83117
    84             self.palette = LinkPalette(self._browser, title, target.href,
    85                                        target.ownerDocument)
    86             self.notify_right_click()
    87         elif target.tagName.lower() == 'img':
    88             if target.title:
    89                 title = target.title
    90             elif target.title:
    91                 title = target.alt
    92             elif target.name:
    93                 title = target.name
    94             else:
    95                 title = os.path.basename(urlparse.urlparse(target.src).path)
    96 
    97             self.palette = ImagePalette(title, target.src,
    98                                         target.ownerDocument)
    99             self.notify_right_click()
    100         else:
    101             return
     118        self._browser = browser
     119        self._title = title
     120        self._url = url
     121        self._owner_document = owner_document
     122
     123        menu_box = Gtk.VBox()
     124        self.set_content(menu_box)
     125        menu_box.show()
     126        self._content.set_border_width(1)
    102127
    103         if self._popdown_handler_id is not None:
    104             self._popdown_handler_id = self.palette.connect( \
    105                 'popdown', self.__palette_popdown_cb)
     128        self.props.primary_text = title
    106129
    107         self._mouseout_listener = MouseOutListener(target)
    108         wrapper = xpcom.server.WrapObject(self._mouseout_listener,
    109                                           interfaces.nsIDOMEventListener)
    110         target.addEventListener('mouseout', wrapper, False)
    111         self._mouseout_listener.connect('mouse-out', self.__moved_out_cb)
     130        menu_item = SugarMenuItem(_('Copy text'), 'edit-copy')
     131        menu_item.icon.props.xo_color = profile.get_color()
     132        menu_item.connect('clicked', self.__copy_activate_cb)
     133        menu_box.pack_end(menu_item, False, False, 0)
     134        menu_item.show()
    112135
    113     def __moved_out_cb(self, listener):
    114         self.palette.popdown()
     136    def __copy_activate_cb(self, menu_item):
     137        self.popdown(immediate=True)
    115138
    116     def __palette_popdown_cb(self, palette):
    117         if self._mouseout_listener is not None:
    118             wrapper = xpcom.server.WrapObject(self._mouseout_listener,
    119                                               interfaces.nsIDOMEventListener)
    120             self._mouseout_listener.target.removeEventListener('mouseout',
    121                                                                wrapper, False)
    122             del self._mouseout_listener
     139        self._browser.copy_clipboard()
    123140
    124141
    125142class LinkPalette(Palette):
    class LinkPalette(Palette): 
    131148        self._url = url
    132149        self._owner_document = owner_document
    133150
    134         if title is not None:
     151        # FIXME: this sometimes fails because Gtk tries to parse it as
     152        # markup text and some URLs has
     153        # "?template=gallery&page=gallery" for example
     154        if title not in (None, ''):
    135155            self.props.primary_text = title
    136156            self.props.secondary_text = url
    137157        else:
    138158            self.props.primary_text = url
    139159
    140         menu_item = MenuItem(_('Follow link'), 'browse-follow-link')
    141         menu_item.connect('activate', self.__follow_activate_cb)
    142         self.menu.append(menu_item)
     160        menu_box = Gtk.VBox()
     161        self.set_content(menu_box)
     162        menu_box.show()
     163        self._content.set_border_width(1)
     164
     165        menu_item = SugarMenuItem(_('Follow link'), 'browse-follow-link')
     166        menu_item.connect('clicked', self.__follow_activate_cb)
     167        menu_box.pack_end(menu_item, False, False, 0)
    143168        menu_item.show()
    144169
    145         menu_item = MenuItem(_('Follow link in new tab'),
    146                              'browse-follow-link-new-tab')
    147         menu_item.connect('activate', self.__follow_activate_cb, True)
    148         self.menu.append(menu_item)
     170        menu_item = SugarMenuItem(_('Follow link in new tab'),
     171                                  'browse-follow-link-new-tab')
     172        menu_item.connect('clicked', self.__follow_activate_cb, True)
     173        menu_box.pack_end(menu_item, False, False, 0)
    149174        menu_item.show()
    150175
    151         menu_item = MenuItem(_('Keep link'))
    152         icon = Icon(icon_name='document-save', xo_color=profile.get_color(),
    153                     icon_size=Gtk.IconSize.MENU)
    154         menu_item.set_image(icon)
    155         menu_item.connect('activate', self.__download_activate_cb)
    156         self.menu.append(menu_item)
     176        menu_item = SugarMenuItem(_('Keep link'), 'document-save')
     177        menu_item.icon.props.xo_color = profile.get_color()
     178        menu_item.connect('clicked', self.__download_activate_cb)
     179        menu_box.pack_end(menu_item, False, False, 0)
    157180        menu_item.show()
    158181
    159         menu_item = MenuItem(_('Copy link'))
    160         icon = Icon(icon_name='edit-copy', xo_color=profile.get_color(),
    161                     icon_size=Gtk.IconSize.MENU)
    162         menu_item.set_image(icon)
    163         menu_item.connect('activate', self.__copy_activate_cb)
    164         self.menu.append(menu_item)
     182        menu_item = SugarMenuItem(_('Copy link'), 'edit-copy')
     183        menu_item.icon.props.xo_color = profile.get_color()
     184        menu_item.connect('clicked', self.__copy_activate_cb)
     185        menu_box.pack_end(menu_item, False, False, 0)
    165186        menu_item.show()
    166187
    167188    def __follow_activate_cb(self, menu_item, new_tab=False):
     189        self.popdown(immediate=True)
     190
    168191        if new_tab:
    169192            new_browser = self._browser.open_new_tab(self._url)
    170193        else:
    class LinkPalette(Palette): 
    172195            self._browser.grab_focus()
    173196
    174197    def __copy_activate_cb(self, menu_item):
    175         clipboard = Gtk.Clipboard()
    176         targets = Gtk.target_list_add_uri_targets()
    177         targets = Gtk.target_list_add_text_targets(targets)
    178         targets.append(('text/x-moz-url', 0, 0))
    179 
    180         clipboard.set_with_data(targets,
    181                                 self.__clipboard_get_func_cb,
    182                                 self.__clipboard_clear_func_cb)
    183 
    184     def __clipboard_get_func_cb(self, clipboard, selection_data, info, data):
    185         uri_targets = \
    186             [target[0] for target in Gtk.target_list_add_uri_targets()]
    187         text_targets = \
    188             [target[0] for target in Gtk.target_list_add_text_targets()]
    189 
    190         if selection_data.target in uri_targets:
    191             selection_data.set_uris([self._url])
    192         elif selection_data.target in text_targets:
    193             selection_data.set_text(self._url)
    194         elif selection_data.target == 'text/x-moz-url':
    195             selection_data.set('text/x-moz-url', 8, self._url)
    196 
    197     def __clipboard_clear_func_cb(self, clipboard, data):
    198         pass
     198        self.popdown(immediate=True)
     199
     200        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
     201        clipboard.set_text(self._url, -1)
    199202
    200203    def __download_activate_cb(self, menu_item):
    201         downloadmanager.save_link(self._url, self._title, self._owner_document)
     204        self.popdown(immediate=True)
     205
     206        nr = WebKit.NetworkRequest()
     207        nr.set_uri(self._url)
     208        download = WebKit.Download(network_request=nr)
     209        self._browser.emit('download-requested', download)
    202210
    203211
    204212class ImagePalette(Palette):
    205     def __init__(self, title, url, owner_document):
     213    def __init__(self, browser, title, url, owner_document):
    206214        Palette.__init__(self)
    207215
     216        self._browser = browser
    208217        self._title = title
    209218        self._url = url
    210219        self._owner_document = owner_document
    211220
    212         self.props.primary_text = title
    213         self.props.secondary_text = url
    214 
    215         menu_item = MenuItem(_('Keep image'))
    216         icon = Icon(icon_name='document-save', xo_color=profile.get_color(),
    217                     icon_size=Gtk.IconSize.MENU)
    218         menu_item.set_image(icon)
    219         menu_item.connect('activate', self.__download_activate_cb)
    220         self.menu.append(menu_item)
    221         menu_item.show()
    222 
    223         menu_item = MenuItem(_('Copy image'))
    224         icon = Icon(icon_name='edit-copy', xo_color=profile.get_color(),
    225                     icon_size=Gtk.IconSize.MENU)
    226         menu_item.set_image(icon)
    227         menu_item.connect('activate', self.__copy_activate_cb)
    228         self.menu.append(menu_item)
    229         menu_item.show()
    230 
    231     def __copy_activate_cb(self, menu_item):
    232         file_name = os.path.basename(urlparse.urlparse(self._url).path)
    233         if '.' in file_name:
    234             base_name, extension = file_name.split('.')
    235             extension = '.' + extension
     221        if title not in (None, ''):
     222            self.props.primary_text = title
     223            self.props.secondary_text = url
    236224        else:
    237             base_name = file_name
    238             extension = ''
    239 
    240         temp_path = os.path.join(activity.get_activity_root(), 'instance')
    241         fd, temp_file = tempfile.mkstemp(dir=temp_path, prefix=base_name,
    242                                                suffix=extension)
    243         os.close(fd)
    244         os.chmod(temp_file, 0664)
    245 
    246         cls = components.classes['@mozilla.org/network/io-service;1']
    247         io_service = cls.getService(interfaces.nsIIOService)
    248         uri = io_service.newURI(self._url, None, None)
    249 
    250         cls = components.classes['@mozilla.org/file/local;1']
    251         target_file = cls.createInstance(interfaces.nsILocalFile)
    252         target_file.initWithPath(temp_file)
    253 
    254         cls = components.classes[ \
    255                 '@mozilla.org/embedding/browser/nsWebBrowserPersist;1']
    256         persist = cls.createInstance(interfaces.nsIWebBrowserPersist)
    257         persist.persistFlags = 1  # PERSIST_FLAGS_FROM_CACHE
    258         listener = xpcom.server.WrapObject(_ImageProgressListener(temp_file),
    259                                            interfaces.nsIWebProgressListener)
    260         persist.progressListener = listener
    261         persist.saveURI(uri, None, None, None, None, target_file)
    262 
    263     def __download_activate_cb(self, menu_item):
    264         downloadmanager.save_link(self._url, self._title, self._owner_document)
    265 
    266 
    267 class _ImageProgressListener(object):
    268     _com_interfaces_ = interfaces.nsIWebProgressListener
    269 
    270     def __init__(self, temp_file):
    271         self._temp_file = temp_file
    272 
    273     def onLocationChange(self, webProgress, request, location):
    274         pass
     225            self.props.primary_text = url
    275226
    276     def onProgressChange(self, webProgress, request, curSelfProgress,
    277                          maxSelfProgress, curTotalProgress, maxTotalProgress):
    278         pass
     227        menu_box = Gtk.VBox()
     228        self.set_content(menu_box)
     229        menu_box.show()
     230        self._content.set_border_width(1)
    279231
    280     def onSecurityChange(self, webProgress, request, state):
    281         pass
     232        menu_item = SugarMenuItem(_('Copy image'), 'edit-copy')
     233        menu_item.icon.props.xo_color = profile.get_color()
     234        menu_item.connect('clicked', self.__copy_activate_cb)
     235        menu_box.pack_end(menu_item, False, False, 0)
     236        menu_item.show()
    282237
    283     def onStatusChange(self, webProgress, request, status, message):
    284         pass
     238        menu_item = SugarMenuItem(_('Keep image'), 'document-save')
     239        menu_item.icon.props.xo_color = profile.get_color()
     240        menu_item.connect('clicked', self.__download_activate_cb)
     241        menu_box.pack_end(menu_item, False, False, 0)
     242        menu_item.show()
    285243
    286     def onStateChange(self, webProgress, request, stateFlags, status):
    287         if (stateFlags & interfaces.nsIWebProgressListener.STATE_IS_REQUEST and
    288             stateFlags & interfaces.nsIWebProgressListener.STATE_STOP):
    289             clipboard = Gtk.Clipboard()
    290             clipboard.set_with_data([('text/uri-list', 0, 0)],
    291                                     _clipboard_get_func_cb,
    292                                     _clipboard_clear_func_cb,
    293                                     self._temp_file)
     244    def __copy_activate_cb(self, menu_item):
     245        self.popdown(immediate=True)
    294246
     247        # Download the image
     248        temp_file = tempfile.NamedTemporaryFile(delete=False)
     249        data = urllib2.urlopen(self._url).read()
     250        temp_file.write(data)
     251        temp_file.close()
    295252
    296 def _clipboard_get_func_cb(clipboard, selection_data, info, temp_file):
    297     selection_data.set_uris(['file://' + temp_file])
     253        # Copy it inside the clipboard
     254        image = Gtk.Image.new_from_file(temp_file.name)
     255        os.unlink(temp_file.name)
     256        clipboard = Gtk.Clipboard()
     257        clipboard.set_image(image.get_pixbuf())
    298258
     259    def __download_activate_cb(self, menu_item):
     260        self.popdown(immediate=True)
    299261
    300 def _clipboard_clear_func_cb(clipboard, temp_file):
    301     if os.path.exists(temp_file):
    302         os.remove(temp_file)
     262        nr = WebKit.NetworkRequest()
     263        nr.set_uri(self._url)
     264        download = WebKit.Download(network_request=nr)
     265        self._browser.emit('download-requested', download)