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

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

v2 - "Keep link" implemented (and Simon added)

  • browser.py

    From 49cc697d20c6084770301f45c54a8f36b64c7293 Mon Sep 17 00:00:00 2001
    From: Manuel Kaufmann <humitos@gmail.com>
    Date: Tue, 11 Sep 2012 16:04:58 -0300
    Subject: [PATCH 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 | 354 ++++++++++++++++++++++++++----------------------------------
     2 files changed, 156 insertions(+), 201 deletions(-)
    
    diff --git a/browser.py b/browser.py
    index 7379d2b..386f6f7 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): 
    462463        self.connect('new-window-policy-decision-requested',
    463464                     self.__new_window_policy_cb)
    464465
     466        ContentInvoker(self)
     467
    465468        try:
    466469            self.connect('run-file-chooser', self.__run_file_chooser)
    467470        except TypeError:
  • palettes.py

    diff --git a/palettes.py b/palettes.py
    index 01c34d4..9d30679 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
    6040        self._popdown_handler_id = None
     41        self._browser.connect('button-press-event', self.__button_press_cb)
     42        self.attach(self._browser)
    6143
    6244    def get_default_position(self):
    6345        return self.AT_CURSOR
    6446
    6547    def get_rect(self):
    66         return ()
     48        allocation = self._browser.get_allocation()
     49        window = self._browser.get_window()
     50        if window is not None:
     51            res, x, y = window.get_origin()
     52        else:
     53            logging.warning(
     54                "Trying to position palette with invoker that's not realized.")
     55            x = 0
     56            y = 0
     57
     58        x += allocation.x
     59        y += allocation.y
     60
     61        width = allocation.width
     62        height = allocation.height
     63
     64        rect = Gdk.Rectangle()
     65        rect.x = x
     66        rect.y = y
     67        rect.width = width
     68        rect.height = height
     69        return rect
    6770
    6871    def get_toplevel(self):
    6972        return None
    7073
    71     def handleEvent(self, event):
    72         if event.button != 2:
    73             return
     74    def __button_press_cb(self, browser, event):
     75        logging.debug('===> event: %s', event.button)
     76        if event.button != 3:
     77            return False
     78        hit_test = self._browser.get_hit_test_result(event)
     79        if hit_test.props.context & WebKit.HitTestResultContext.LINK:
     80            logging.debug('===> click LINK')
     81            link_uri = hit_test.props.link_uri
     82            if isinstance(hit_test.props.inner_node,
     83                            WebKit.DOMHTMLImageElement):
     84                title = hit_test.props.inner_node.get_title()
     85            elif isinstance(hit_test.props.inner_node, WebKit.DOMNode):
     86                title = hit_test.props.inner_node.get_text_content()
     87            self.palette = LinkPalette(self._browser, title, link_uri, None)
     88            self.notify_right_click()
     89        elif hit_test.props.context & WebKit.HitTestResultContext.IMAGE:
     90            logging.debug('===> click IMAGE %s %s',
     91                          hit_test.props.image_uri, hit_test.props.inner_node)
     92            title = hit_test.props.inner_node.get_title()
     93            self.palette = ImagePalette(self._browser, title,
     94                                        hit_test.props.image_uri, '')
     95            self.notify_right_click()
     96        elif hit_test.props.context & WebKit.HitTestResultContext.SELECTION:
     97            # TODO: find a way to get the selected text so we can use
     98            # it as the title of the Palette.
     99            # The function webkit_web_view_get_selected_text was removed
     100            # https://bugs.webkit.org/show_bug.cgi?id=62512
     101            title = None
     102
     103            # text = hit_test.props.inner_node.get_text_content()
     104            # import epdb;epdb.set_trace()
     105            # if len(text) > 20:
     106            #     title = text[:20] + '...'
     107            # else:
     108            #     title = text
     109            self.palette = SelectionPalette(self._browser, title, None, None)
     110            self.notify_right_click()
    74111
    75         target = event.target
     112        return True
    76113
    77         if target.tagName.lower() == 'a':
    78114
    79             if target.firstChild:
    80                 title = target.firstChild.nodeValue
    81             else:
    82                 title = None
     115class SelectionPalette(Palette):
     116    def __init__(self, browser, title, url, owner_document):
     117        Palette.__init__(self)
    83118
    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
     119        self._browser = browser
     120        self._title = title
     121        self._url = url
     122        self._owner_document = owner_document
    102123
    103         if self._popdown_handler_id is not None:
    104             self._popdown_handler_id = self.palette.connect( \
    105                 'popdown', self.__palette_popdown_cb)
     124        menu_box = Gtk.VBox()
     125        self.set_content(menu_box)
     126        menu_box.show()
     127        self._content.set_border_width(1)
    106128
    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)
     129        self.props.primary_text = title
    112130
    113     def __moved_out_cb(self, listener):
    114         self.palette.popdown()
     131        menu_item = SugarMenuItem(_('Copy text'), 'edit-copy')
     132        menu_item.icon.props.xo_color = profile.get_color()
     133        menu_item.connect('clicked', self.__copy_activate_cb)
     134        menu_box.pack_end(menu_item, False, False, 0)
     135        menu_item.show()
    115136
    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
     137    def __copy_activate_cb(self, menu_item):
     138        self._browser.copy_clipboard()
    123139
    124140
    125141class LinkPalette(Palette):
    class LinkPalette(Palette): 
    131147        self._url = url
    132148        self._owner_document = owner_document
    133149
    134         if title is not None:
     150        # FIXME: this sometimes fails because Gtk tries to parse it as
     151        # markup text and some URLs has
     152        # "?template=gallery&page=gallery" for example
     153        if title not in (None, ''):
    135154            self.props.primary_text = title
    136155            self.props.secondary_text = url
    137156        else:
    138157            self.props.primary_text = url
    139158
    140         menu_item = MenuItem(_('Follow link'), 'browse-follow-link')
    141         menu_item.connect('activate', self.__follow_activate_cb)
    142         self.menu.append(menu_item)
     159        menu_box = Gtk.VBox()
     160        self.set_content(menu_box)
     161        menu_box.show()
     162        self._content.set_border_width(1)
     163
     164        menu_item = SugarMenuItem(_('Follow link'), 'browse-follow-link')
     165        menu_item.connect('clicked', self.__follow_activate_cb)
     166        menu_box.pack_end(menu_item, False, False, 0)
    143167        menu_item.show()
    144168
    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)
     169        menu_item = SugarMenuItem(_('Follow link in new tab'),
     170                                  'browse-follow-link-new-tab')
     171        menu_item.connect('clicked', self.__follow_activate_cb, True)
     172        menu_box.pack_end(menu_item, False, False, 0)
    149173        menu_item.show()
    150174
    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)
     175        menu_item = SugarMenuItem(_('Keep link'), 'document-save')
     176        menu_item.icon.props.xo_color = profile.get_color()
     177        menu_item.connect('clicked', self.__download_activate_cb)
     178        menu_box.pack_end(menu_item, False, False, 0)
    157179        menu_item.show()
    158180
    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)
     181        menu_item = SugarMenuItem(_('Copy link'), 'edit-copy')
     182        menu_item.icon.props.xo_color = profile.get_color()
     183        menu_item.connect('clicked', self.__copy_activate_cb)
     184        menu_box.pack_end(menu_item, False, False, 0)
    165185        menu_item.show()
    166186
    167187    def __follow_activate_cb(self, menu_item, new_tab=False):
    class LinkPalette(Palette): 
    172192            self._browser.grab_focus()
    173193
    174194    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
     195        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
     196        clipboard.set_text(self._url, -1)
    199197
    200198    def __download_activate_cb(self, menu_item):
    201         downloadmanager.save_link(self._url, self._title, self._owner_document)
     199        nr = WebKit.NetworkRequest()
     200        nr.set_uri(self._url)
     201        download = WebKit.Download(network_request=nr)
     202        self._browser.emit('download-requested', download)
    202203
    203204
    204205class ImagePalette(Palette):
    205     def __init__(self, title, url, owner_document):
     206    def __init__(self, browser, title, url, owner_document):
    206207        Palette.__init__(self)
    207208
     209        self._browser = browser
    208210        self._title = title
    209211        self._url = url
    210212        self._owner_document = owner_document
    211213
    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)
     214        if title not in (None, ''):
     215            self.props.primary_text = title
     216            self.props.secondary_text = url
     217        else:
     218            self.props.primary_text = url
     219
     220        menu_box = Gtk.VBox()
     221        self.set_content(menu_box)
     222        menu_box.show()
     223        self._content.set_border_width(1)
     224
     225        menu_item = SugarMenuItem(_('Copy image'), 'edit-copy')
     226        menu_item.icon.props.xo_color = profile.get_color()
     227        menu_item.connect('clicked', self.__copy_activate_cb)
     228        menu_box.pack_end(menu_item, False, False, 0)
    221229        menu_item.show()
    222230
    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)
     231        menu_item = SugarMenuItem(_('Keep image'), 'document-save')
     232        menu_item.icon.props.xo_color = profile.get_color()
     233        menu_item.connect('clicked', self.__download_activate_cb)
     234        menu_box.pack_end(menu_item, False, False, 0)
    229235        menu_item.show()
    230236
    231237    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
    236         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)
     238        # Download the image
     239        temp_file = tempfile.NamedTemporaryFile(delete=False)
     240        data = urllib2.urlopen(self._url).read()
     241        temp_file.write(data)
     242        temp_file.close()
     243
     244        # Copy it inside the clipboard
     245        image = Gtk.Image.new_from_file(temp_file.name)
     246        os.unlink(temp_file.name)
     247        clipboard = Gtk.Clipboard()
     248        clipboard.set_image(image.get_pixbuf())
    262249
    263250    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
    275 
    276     def onProgressChange(self, webProgress, request, curSelfProgress,
    277                          maxSelfProgress, curTotalProgress, maxTotalProgress):
    278         pass
    279 
    280     def onSecurityChange(self, webProgress, request, state):
    281         pass
    282 
    283     def onStatusChange(self, webProgress, request, status, message):
    284         pass
    285 
    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)
    294 
    295 
    296 def _clipboard_get_func_cb(clipboard, selection_data, info, temp_file):
    297     selection_data.set_uris(['file://' + temp_file])
    298 
    299 
    300 def _clipboard_clear_func_cb(clipboard, temp_file):
    301     if os.path.exists(temp_file):
    302         os.remove(temp_file)
     251        nr = WebKit.NetworkRequest()
     252        nr.set_uri(self._url)
     253        download = WebKit.Download(network_request=nr)
     254        self._browser.emit('download-requested', download)