def get_view (self, compact=False): """Generate a view widget for editing HTML.""" vbox=gtk.VBox() self.editor=HTMLEditor() self.editor.custom_url_loader=self.custom_url_loader self.editor.register_class_parser(self.class_parser) try: self.editor.set_text(self.element.data) except Exception, e: self.controller.log(_("HTML editor: cannot parse content (%s)") % unicode(e))
def get_view (self, compact=False): """Generate a view widget for editing HTML.""" vbox=Gtk.VBox() self.editor=HTMLEditor() self.editor.custom_url_loader=self.custom_url_loader self.editor.register_class_parser(self.class_parser) try: self.editor.set_text(self.element.data) except Exception as e: self.controller.log(_("HTML editor: cannot parse content (%s)") % str(e)) self.editor.connect('drag-data-received', self.editor_drag_received) self.editor.drag_dest_set(Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.ALL, config.data.get_target_types('annotation', 'annotation-type', 'timestamp'), Gdk.DragAction.COPY | Gdk.DragAction.LINK | Gdk.DragAction.ASK ) self.editor.connect('button-press-event', self.button_press_cb) self.view = Gtk.VBox() def sel_copy(i): self.editor.get_buffer().copy_clipboard(get_clipboard()) return True def sel_cut(i): self.editor.get_buffer().cut_clipboard(get_clipboard()) return True def sel_paste(i): self.editor.get_buffer().paste_clipboard(get_clipboard()) return True def refresh(i): self.editor.refresh() return True def display_header_menu(i): m=Gtk.Menu() for h in (1, 2, 3): i=Gtk.MenuItem(_("Heading %d") % h) i.connect('activate', lambda w, level: self.editor.apply_html_tag('h%d' % level), h) m.append(i) m.show_all() m.popup(None, i, None, 1, Gtk.get_current_event_time()) return True tb=Gtk.Toolbar() vbox.toolbar=tb tb.set_style(Gtk.ToolbarStyle.ICONS) for (icon, tooltip, action) in ( (Gtk.STOCK_BOLD, _("Bold"), lambda i: self.editor.apply_html_tag('b')), (Gtk.STOCK_ITALIC, _("Italic"), lambda i: self.editor.apply_html_tag('i')), ("title_icon.png", _("Header"), display_header_menu), (None, None, None), (Gtk.STOCK_COPY, _("Copy"), sel_copy), (Gtk.STOCK_CUT, _("Cut"), sel_cut), (Gtk.STOCK_PASTE, _("Paste"), sel_paste), (None, None, None), (Gtk.STOCK_REFRESH, _("Refresh"), refresh), ): if not config.data.preferences['expert-mode'] and icon == Gtk.STOCK_REFRESH: continue if not icon: b=Gtk.SeparatorToolItem() else: b=get_pixmap_toolbutton(icon, action) b.set_tooltip_text(tooltip) tb.insert(b, -1) b.show() if self.editor.can_undo(): b=Gtk.ToolButton(Gtk.STOCK_UNDO) b.connect('clicked', lambda i: self.editor.undo()) b.set_tooltip_text(_("Undo")) tb.insert(b, -1) b.show() self.view.pack_start(tb, False, True, 0) sw=Gtk.ScrolledWindow() sw.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.add(self.editor) context_data=ContextDisplay() def cursor_moved(buf, it, mark): if mark.get_name() == 'insert': context_data.set_context(self.editor.get_current_context(it)) return True self.editor.get_buffer().connect('mark-set', cursor_moved) sw2=Gtk.ScrolledWindow() sw2.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw2.add(context_data) p=Gtk.HPaned() p.add1(sw2) p.add2(sw) # Hide by default p.set_position(0) p.show_all() self.view.add(p) def edit_wysiwyg(*p): vbox.foreach(vbox.remove) vbox.add(self.view) self.editing_source=False vbox.show_all() return True def edit_source(*p): if self.sourceview is None: self.sourceview=TextContentHandler(element=self.element, controller=self.controller, parent=self.parent) self.sourceview.widget = self.sourceview.get_view() b=get_pixmap_toolbutton('xml.png', edit_wysiwyg) b.set_tooltip_text(_("WYSIWYG editor")) self.sourceview.toolbar.insert(b, 0) vbox.foreach(vbox.remove) vbox.add(self.sourceview.widget) self.editing_source=True vbox.show_all() return True b=get_pixmap_toolbutton('xml.png', edit_source) b.set_tooltip_text(_("Edit HTML source")) tb.insert(b, 0) if config.data.preferences['prefer-wysiwyg']: edit_wysiwyg() self.editor.set_modified(False) else: edit_source() self.sourceview.set_modified(False) return vbox
class HTMLContentHandler (ContentHandler): """Create a HTML edit form for the given element.""" def can_handle(mimetype): res=0 if mimetype == 'text/html': res=90 return res can_handle=staticmethod(can_handle) def __init__ (self, element, controller=None, parent=None, **kw): self.element = element self.controller=controller self.parent=parent self.editable = True self.fname=None self.last_dndtime=None self.last_x = None self.last_y = None # HTMLEditor component (Gtk.Textview subclass) self.editor = None # Widgets holding editors (basic and html) self.view = None self.sourceview=None self.placeholders=[] self.editing_source=False def close(self): for p in self.placeholders: p.cleanup() def set_editable (self, boolean): self.editable = boolean if self.sourceview: self.sourceview.set_editable(boolean) def get_modified(self): if self.editing_source: return self.sourceview.get_modified() else: return self.editor.get_modified() def update_element (self): """Update the element fields according to the values in the view.""" if not self.editable: return False if self.editing_source: self.sourceview.update_element() # We applied our modifications to the HTML source, so # parse the source again in the HTML editor if self.editor is not None: self.editor.set_text(self.element.data) self.editor.set_modified(False) return True if self.editor is None: return True self.element.data = self.editor.get_html() # Update the HTML source representation if self.sourceview is not None: self.sourceview.content_set(self.element.data) self.sourceview.set_modified(False) return True def open_link(self, link=None): if link: pos=re.findall('/media/play/(\d+)', link) if pos: # A position was specified. Directly use it. self.controller.update_status('seek', int(pos[0])) else: self.controller.open_url(link) return True def insert_annotation_content(self, choice, annotation, focus=False): """ choice: list of one or more strings: 'snapshot', 'timestamp', 'content', 'overlay' """ a=AnnotationPlaceholder(annotation, self.controller, choice, self.editor.update_pixbuf) self.placeholders.append(a) self.editor.insert_pixbuf(a.pixbuf) if focus: self.grab_focus() return True def insert_annotationtype_content(self, choice, annotationtype, focus=False): """ choice: list of one or more strings: 'list', 'table', 'transcription' """ a=AnnotationTypePlaceholder(annotationtype, self.controller, choice, self.editor.update_pixbuf) self.placeholders.append(a) self.editor.insert_pixbuf(a.pixbuf) if focus: self.grab_focus() return True def grab_focus(self, *p): self.editor.grab_focus() return True def editor_drag_received(self, widget, context, x, y, selection, targetType, time): """Handle the drop from an annotation to the editor. """ # FIXME: Upon DND, TextView receives the event twice. Some # posts from 2004 signal the same problem, some hacks can be # found in existing code : # widget.emit_stop_by_name ("drag-data-received") # context.finish(False, False, time) # widget.stop_emission("drag-data-received") # but none of them seems to work here. Just use a basic approach, # imagining that nobody is fast enough to really do two DNDs # at the same time. # But on win32, timestamp is always 0. So we must use x and y information as well. if time == self.last_dndtime and x == self.last_x and y == self.last_y: return True self.last_dndtime=time self.last_x=x self.last_y=y x, y = self.editor.window_to_buffer_coords(Gtk.TextWindowType.TEXT, *widget.get_pointer()) it = self.editor.get_iter_at_location(x, y) self.editor.get_buffer().place_cursor(it.iter) if targetType == config.data.target_type['annotation']: for uri in str(selection.get_data(), 'utf8').split('\n'): source=self.controller.package.annotations.get(uri) if source is None: return True m=Gtk.Menu() for (title, choice) in ( (_("Snapshot only"), ['link', 'snapshot', ]), (_("Overlayed snapshot only"), ['link', 'overlay', ]), (_("Timestamp only"), ['link', 'timestamp', ]), (_("Snapshot+timestamp"), ['link', 'snapshot', 'timestamp']), (_("Annotation content"), ['link', 'content']), ): i=Gtk.MenuItem(title) i.connect('activate', (lambda it, ann, data: self.insert_annotation_content(data, ann, focus=True)), source, choice) m.append(i) m.show_all() m.popup(None, None, None, 0, Gtk.get_current_event_time()) return True elif targetType == config.data.target_type['annotation-type']: for uri in str(selection.get_data(), 'utf8').split('\n'): source = self.controller.package.annotationTypes.get(uri) if source is None: return True m=Gtk.Menu() for (title, choice) in ( (_("as a list"), [ 'list' ]), (_("as a grid"), [ 'grid' ]), (_("as a table"), [ 'table' ]), (_("as a transcription"), ['transcription' ]), ): i=Gtk.MenuItem(title) i.connect('activate', (lambda it, at, data: self.insert_annotationtype_content(data, at, focus=True)), source, choice) m.append(i) m.show_all() m.popup(None, None, None, 0, Gtk.get_current_event_time()) return True elif targetType == config.data.target_type['timestamp']: data=decode_drop_parameters(selection.get_data()) t=int(data['timestamp']) # FIXME: propose various choices (insert timestamp, insert snapshot, etc) self.editor.get_buffer().insert_at_cursor(helper.format_time(t)) return True else: logger.warn("Unknown target type for drop: %d" % targetType) return False def class_parser(self, tag, attr): if attr['class'] == 'advene:annotation': a=AnnotationPlaceholder(annotation=None, controller=self.controller, update_pixbuf=self.editor.update_pixbuf) self.placeholders.append(a) return a.parse_html(tag, attr) elif attr['class'] == 'advene:annotationtype': a=AnnotationTypePlaceholder(annotationtype=None, controller=self.controller, update_pixbuf=self.editor.update_pixbuf) self.placeholders.append(a) return a.parse_html(tag, attr) return None, None def custom_url_loader(self, url): """Custom URL loader. This method processes URLs internally when possible, instead of going through the webserver. This is at the cost of some code, and possible discrepancies with the original webcherry code. It is absolutely unnecessary on linux/macosx. However, win32 (svg?) pixbuf loader is broken wrt. threads, and this is the solution to have the overlay() code (and thus the pixbuf loader) execute in the main thread. """ m=re.search('/media/overlay/(.+?)/(.+)', url) if m: (alias, element)=m.groups() if '/' in element: # There is a TALES expression specifying the overlayed # content aid, path = element.split('/', 1) else: aid, path = element, None p=self.controller.packages.get(alias) if not p: return None a=p.get_element_by_id(aid) if a is None: return None if path: # There is a TALES expression path='here/'+path ctx=self.controller.build_context(here=a) svg_data=str( ctx.evaluateValue(path) ) elif 'svg' in a.content.mimetype: # Overlay svg svg_data=a.content.data else: # Overlay annotation title svg_data=self.controller.get_title(a) png_data=str(p.imagecache[a.fragment.begin]) return self.controller.gui.overlay(png_data, svg_data) m=re.search('/packages/(.+?)/imagecache/(\d+)', url) if m: alias, timestamp = m.groups() p=self.controller.packages.get(alias) if p is None: return None return str(p.imagecache[int(timestamp)]) return None def contextual_popup(self, ctx=None, menu=None): """Popup a contextual menu for the given context. """ if menu is None: menu=Gtk.Menu() def open_link(i, l): self.open_link(l) return True def goto_position(i, pos): self.controller.update_status('seek', pos) return True def select_presentation(i, ap, modes): ap.presentation = modes[:] ap.refresh() return True def new_menuitem(label, action, *params): item=Gtk.MenuItem(label) if action is not None: item.connect('activate', action, *params) item.show() menu.append(item) return item if ctx is None: ctx=self.editor.get_current_context() if ctx: if hasattr(ctx[-1], '_placeholder'): ap=ctx[-1]._placeholder if getattr(ap, 'annotation', None) is not None: new_menuitem(_("Annotation %s") % self.controller.get_title(ap.annotation), None) new_menuitem(_("Play video"), goto_position, ap.annotation.fragment.begin) new_menuitem(_("Show timestamp only"), select_presentation, ap, ['timestamp', 'link']) new_menuitem(_("Show content only"), select_presentation, ap, ['content', 'link']) new_menuitem(_("Show snapshot only"), select_presentation, ap, ['snapshot', 'link']) new_menuitem(_("Show overlayed timestamp"), select_presentation, ap, ['timestamp', 'snapshot', 'link']) new_menuitem(_("Show overlayed content"), select_presentation, ap, ['overlay', 'link']) elif getattr(ap, 'annotationtype', None) is not None: new_menuitem(_("Annotation type %s") % self.controller.get_title(ap.annotationtype), None) new_menuitem(_("display as list"), select_presentation, ap, ['list']) new_menuitem(_("display as grid"), select_presentation, ap, ['grid']) new_menuitem(_("display as table"), select_presentation, ap, ['table']) new_menuitem(_("display as transcription"), select_presentation, ap, ['transcription']) l=[ m for m in ctx if m._tag == 'a' ] if l: link=dict(l[0]._attr).get('href', None) if link: if '/media/play' in link: new_menuitem(_("Play video"), open_link, link) else: new_menuitem(_("Open link"), open_link, link) return menu def button_press_cb(self, textview, event): if not (event.button == 3 or (event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS)): return False textwin=textview.get_window(Gtk.TextWindowType.TEXT) if event.get_window() != textwin: return False (x, y) = textview.window_to_buffer_coords(Gtk.TextWindowType.TEXT, int(event.x), int(event.y)) it=textview.get_iter_at_location(x, y) if it is None: logger.warn("Error in get_iter_at_location") return False ctx=self.editor.get_current_context(it.iter) if not ctx: return False if event.button == 3: # Right button if hasattr(ctx[-1], '_placeholder'): # An annotation placeholder is here. Display popup menu. menu=self.contextual_popup(ctx) menu.popup_at_pointer(None) return True else: # Double click with left button if hasattr(ctx[-1], '_placeholder'): # There is an placeholder p = ctx[-1]._placeholder if isinstance(p, AnnotationPlaceholder): a=ctx[-1]._placeholder.annotation if a is not None: self.controller.update_status('seek', a.fragment.begin) return False l=[ m for m in ctx if m._tag == 'a' ] if l: link=dict(l[0]._attr).get('href', None) self.open_link(link) return False return False def get_view (self, compact=False): """Generate a view widget for editing HTML.""" vbox=Gtk.VBox() self.editor=HTMLEditor() self.editor.custom_url_loader=self.custom_url_loader self.editor.register_class_parser(self.class_parser) try: self.editor.set_text(self.element.data) except Exception as e: self.controller.log(_("HTML editor: cannot parse content (%s)") % str(e)) self.editor.connect('drag-data-received', self.editor_drag_received) self.editor.drag_dest_set(Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.ALL, config.data.get_target_types('annotation', 'annotation-type', 'timestamp'), Gdk.DragAction.COPY | Gdk.DragAction.LINK | Gdk.DragAction.ASK ) self.editor.connect('button-press-event', self.button_press_cb) self.view = Gtk.VBox() def sel_copy(i): self.editor.get_buffer().copy_clipboard(get_clipboard()) return True def sel_cut(i): self.editor.get_buffer().cut_clipboard(get_clipboard()) return True def sel_paste(i): self.editor.get_buffer().paste_clipboard(get_clipboard()) return True def refresh(i): self.editor.refresh() return True def display_header_menu(i): m=Gtk.Menu() for h in (1, 2, 3): i=Gtk.MenuItem(_("Heading %d") % h) i.connect('activate', lambda w, level: self.editor.apply_html_tag('h%d' % level), h) m.append(i) m.show_all() m.popup(None, i, None, 1, Gtk.get_current_event_time()) return True tb=Gtk.Toolbar() vbox.toolbar=tb tb.set_style(Gtk.ToolbarStyle.ICONS) for (icon, tooltip, action) in ( (Gtk.STOCK_BOLD, _("Bold"), lambda i: self.editor.apply_html_tag('b')), (Gtk.STOCK_ITALIC, _("Italic"), lambda i: self.editor.apply_html_tag('i')), ("title_icon.png", _("Header"), display_header_menu), (None, None, None), (Gtk.STOCK_COPY, _("Copy"), sel_copy), (Gtk.STOCK_CUT, _("Cut"), sel_cut), (Gtk.STOCK_PASTE, _("Paste"), sel_paste), (None, None, None), (Gtk.STOCK_REFRESH, _("Refresh"), refresh), ): if not config.data.preferences['expert-mode'] and icon == Gtk.STOCK_REFRESH: continue if not icon: b=Gtk.SeparatorToolItem() else: b=get_pixmap_toolbutton(icon, action) b.set_tooltip_text(tooltip) tb.insert(b, -1) b.show() if self.editor.can_undo(): b=Gtk.ToolButton(Gtk.STOCK_UNDO) b.connect('clicked', lambda i: self.editor.undo()) b.set_tooltip_text(_("Undo")) tb.insert(b, -1) b.show() self.view.pack_start(tb, False, True, 0) sw=Gtk.ScrolledWindow() sw.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.add(self.editor) context_data=ContextDisplay() def cursor_moved(buf, it, mark): if mark.get_name() == 'insert': context_data.set_context(self.editor.get_current_context(it)) return True self.editor.get_buffer().connect('mark-set', cursor_moved) sw2=Gtk.ScrolledWindow() sw2.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw2.add(context_data) p=Gtk.HPaned() p.add1(sw2) p.add2(sw) # Hide by default p.set_position(0) p.show_all() self.view.add(p) def edit_wysiwyg(*p): vbox.foreach(vbox.remove) vbox.add(self.view) self.editing_source=False vbox.show_all() return True def edit_source(*p): if self.sourceview is None: self.sourceview=TextContentHandler(element=self.element, controller=self.controller, parent=self.parent) self.sourceview.widget = self.sourceview.get_view() b=get_pixmap_toolbutton('xml.png', edit_wysiwyg) b.set_tooltip_text(_("WYSIWYG editor")) self.sourceview.toolbar.insert(b, 0) vbox.foreach(vbox.remove) vbox.add(self.sourceview.widget) self.editing_source=True vbox.show_all() return True b=get_pixmap_toolbutton('xml.png', edit_source) b.set_tooltip_text(_("Edit HTML source")) tb.insert(b, 0) if config.data.preferences['prefer-wysiwyg']: edit_wysiwyg() self.editor.set_modified(False) else: edit_source() self.sourceview.set_modified(False) return vbox
def get_view(self, compact=False): """Generate a view widget for editing HTML.""" vbox = Gtk.VBox() self.editor = HTMLEditor() self.editor.custom_url_loader = self.custom_url_loader self.editor.register_class_parser(self.class_parser) try: self.editor.set_text(self.element.data) except Exception as e: self.controller.log( _("HTML editor: cannot parse content (%s)") % str(e)) self.editor.connect('drag-data-received', self.editor_drag_received) self.editor.drag_dest_set( Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.ALL, config.data.get_target_types('annotation', 'annotation-type', 'timestamp'), Gdk.DragAction.COPY | Gdk.DragAction.LINK | Gdk.DragAction.ASK) self.editor.connect('button-press-event', self.button_press_cb) self.view = Gtk.VBox() def sel_copy(i): self.editor.get_buffer().copy_clipboard(get_clipboard()) return True def sel_cut(i): self.editor.get_buffer().cut_clipboard(get_clipboard()) return True def sel_paste(i): self.editor.get_buffer().paste_clipboard(get_clipboard()) return True def refresh(i): self.editor.refresh() return True def display_header_menu(i): m = Gtk.Menu() for h in (1, 2, 3): i = Gtk.MenuItem(_("Heading %d") % h) i.connect( 'activate', lambda w, level: self.editor.apply_html_tag('h%d' % level), h) m.append(i) m.show_all() m.popup(None, i, None, 1, Gtk.get_current_event_time()) return True tb = Gtk.Toolbar() vbox.toolbar = tb tb.set_style(Gtk.ToolbarStyle.ICONS) for (icon, tooltip, action) in ( (Gtk.STOCK_BOLD, _("Bold"), lambda i: self.editor.apply_html_tag('b')), (Gtk.STOCK_ITALIC, _("Italic"), lambda i: self.editor.apply_html_tag('i')), ("title_icon.png", _("Header"), display_header_menu), (None, None, None), (Gtk.STOCK_COPY, _("Copy"), sel_copy), (Gtk.STOCK_CUT, _("Cut"), sel_cut), (Gtk.STOCK_PASTE, _("Paste"), sel_paste), (None, None, None), (Gtk.STOCK_REFRESH, _("Refresh"), refresh), ): if not config.data.preferences[ 'expert-mode'] and icon == Gtk.STOCK_REFRESH: continue if not icon: b = Gtk.SeparatorToolItem() else: b = get_pixmap_toolbutton(icon, action) b.set_tooltip_text(tooltip) tb.insert(b, -1) b.show() if self.editor.can_undo(): b = Gtk.ToolButton(Gtk.STOCK_UNDO) b.connect('clicked', lambda i: self.editor.undo()) b.set_tooltip_text(_("Undo")) tb.insert(b, -1) b.show() self.view.pack_start(tb, False, True, 0) sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.add(self.editor) context_data = ContextDisplay() def cursor_moved(buf, it, mark): if mark.get_name() == 'insert': context_data.set_context(self.editor.get_current_context(it)) return True self.editor.get_buffer().connect('mark-set', cursor_moved) sw2 = Gtk.ScrolledWindow() sw2.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw2.add(context_data) p = Gtk.HPaned() p.add1(sw2) p.add2(sw) # Hide by default p.set_position(0) p.show_all() self.view.add(p) def edit_wysiwyg(*p): vbox.foreach(vbox.remove) vbox.add(self.view) self.editing_source = False vbox.show_all() return True def edit_source(*p): if self.sourceview is None: self.sourceview = TextContentHandler( element=self.element, controller=self.controller, parent=self.parent) self.sourceview.widget = self.sourceview.get_view() b = get_pixmap_toolbutton('xml.png', edit_wysiwyg) b.set_tooltip_text(_("WYSIWYG editor")) self.sourceview.toolbar.insert(b, 0) vbox.foreach(vbox.remove) vbox.add(self.sourceview.widget) self.editing_source = True vbox.show_all() return True b = get_pixmap_toolbutton('xml.png', edit_source) b.set_tooltip_text(_("Edit HTML source")) tb.insert(b, 0) if config.data.preferences['prefer-wysiwyg']: edit_wysiwyg() self.editor.set_modified(False) else: edit_source() self.sourceview.set_modified(False) return vbox
class HTMLContentHandler(ContentHandler): """Create a HTML edit form for the given element.""" def can_handle(mimetype): res = 0 if mimetype == 'text/html': res = 90 return res can_handle = staticmethod(can_handle) def __init__(self, element, controller=None, parent=None, **kw): self.element = element self.controller = controller self.parent = parent self.editable = True self.fname = None self.last_dndtime = None self.last_x = None self.last_y = None # HTMLEditor component (Gtk.Textview subclass) self.editor = None # Widgets holding editors (basic and html) self.view = None self.sourceview = None self.placeholders = [] self.editing_source = False def close(self): for p in self.placeholders: p.cleanup() def set_editable(self, boolean): self.editable = boolean if self.sourceview: self.sourceview.set_editable(boolean) def get_modified(self): if self.editing_source: return self.sourceview.get_modified() else: return self.editor.get_modified() def update_element(self): """Update the element fields according to the values in the view.""" if not self.editable: return False if self.editing_source: self.sourceview.update_element() # We applied our modifications to the HTML source, so # parse the source again in the HTML editor if self.editor is not None: self.editor.set_text(self.element.data) self.editor.set_modified(False) return True if self.editor is None: return True self.element.data = self.editor.get_html() # Update the HTML source representation if self.sourceview is not None: self.sourceview.content_set(self.element.data) self.sourceview.set_modified(False) return True def open_link(self, link=None): if link: pos = re.findall('/media/play/(\d+)', link) if pos: # A position was specified. Directly use it. self.controller.update_status('seek', int(pos[0])) else: self.controller.open_url(link) return True def insert_annotation_content(self, choice, annotation, focus=False): """ choice: list of one or more strings: 'snapshot', 'timestamp', 'content', 'overlay' """ a = AnnotationPlaceholder(annotation, self.controller, choice, self.editor.update_pixbuf) self.placeholders.append(a) self.editor.insert_pixbuf(a.pixbuf) if focus: self.grab_focus() return True def insert_annotationtype_content(self, choice, annotationtype, focus=False): """ choice: list of one or more strings: 'list', 'table', 'transcription' """ a = AnnotationTypePlaceholder(annotationtype, self.controller, choice, self.editor.update_pixbuf) self.placeholders.append(a) self.editor.insert_pixbuf(a.pixbuf) if focus: self.grab_focus() return True def grab_focus(self, *p): self.editor.grab_focus() return True def editor_drag_received(self, widget, context, x, y, selection, targetType, time): """Handle the drop from an annotation to the editor. """ # FIXME: Upon DND, TextView receives the event twice. Some # posts from 2004 signal the same problem, some hacks can be # found in existing code : # widget.emit_stop_by_name ("drag-data-received") # context.finish(False, False, time) # widget.stop_emission("drag-data-received") # but none of them seems to work here. Just use a basic approach, # imagining that nobody is fast enough to really do two DNDs # at the same time. # But on win32, timestamp is always 0. So we must use x and y information as well. if time == self.last_dndtime and x == self.last_x and y == self.last_y: return True self.last_dndtime = time self.last_x = x self.last_y = y x, y = self.editor.window_to_buffer_coords(Gtk.TextWindowType.TEXT, *widget.get_pointer()) it = self.editor.get_iter_at_location(x, y) self.editor.get_buffer().place_cursor(it.iter) if targetType == config.data.target_type['annotation']: for uri in str(selection.get_data(), 'utf8').split('\n'): source = self.controller.package.annotations.get(uri) if source is None: return True m = Gtk.Menu() for (title, choice) in ( (_("Snapshot only"), [ 'link', 'snapshot', ]), (_("Overlayed snapshot only"), [ 'link', 'overlay', ]), (_("Timestamp only"), [ 'link', 'timestamp', ]), (_("Snapshot+timestamp"), ['link', 'snapshot', 'timestamp']), (_("Annotation content"), ['link', 'content']), ): i = Gtk.MenuItem(title) i.connect( 'activate', (lambda it, ann, data: self.insert_annotation_content( data, ann, focus=True)), source, choice) m.append(i) m.show_all() m.popup(None, None, None, 0, Gtk.get_current_event_time()) return True elif targetType == config.data.target_type['annotation-type']: for uri in str(selection.get_data(), 'utf8').split('\n'): source = self.controller.package.annotationTypes.get(uri) if source is None: return True m = Gtk.Menu() for (title, choice) in ( (_("as a list"), ['list']), (_("as a grid"), ['grid']), (_("as a table"), ['table']), (_("as a transcription"), ['transcription']), ): i = Gtk.MenuItem(title) i.connect( 'activate', (lambda it, at, data: self. insert_annotationtype_content(data, at, focus=True)), source, choice) m.append(i) m.show_all() m.popup(None, None, None, 0, Gtk.get_current_event_time()) return True elif targetType == config.data.target_type['timestamp']: data = decode_drop_parameters(selection.get_data()) t = int(data['timestamp']) # FIXME: propose various choices (insert timestamp, insert snapshot, etc) self.editor.get_buffer().insert_at_cursor(helper.format_time(t)) return True else: logger.warn("Unknown target type for drop: %d" % targetType) return False def class_parser(self, tag, attr): if attr['class'] == 'advene:annotation': a = AnnotationPlaceholder(annotation=None, controller=self.controller, update_pixbuf=self.editor.update_pixbuf) self.placeholders.append(a) return a.parse_html(tag, attr) elif attr['class'] == 'advene:annotationtype': a = AnnotationTypePlaceholder( annotationtype=None, controller=self.controller, update_pixbuf=self.editor.update_pixbuf) self.placeholders.append(a) return a.parse_html(tag, attr) return None, None def custom_url_loader(self, url): """Custom URL loader. This method processes URLs internally when possible, instead of going through the webserver. This is at the cost of some code, and possible discrepancies with the original webcherry code. It is absolutely unnecessary on linux/macosx. However, win32 (svg?) pixbuf loader is broken wrt. threads, and this is the solution to have the overlay() code (and thus the pixbuf loader) execute in the main thread. """ m = re.search('/media/overlay/(.+?)/(.+)', url) if m: (alias, element) = m.groups() if '/' in element: # There is a TALES expression specifying the overlayed # content aid, path = element.split('/', 1) else: aid, path = element, None p = self.controller.packages.get(alias) if not p: return None a = p.get_element_by_id(aid) if a is None: return None if path: # There is a TALES expression path = 'here/' + path ctx = self.controller.build_context(here=a) svg_data = str(ctx.evaluateValue(path)) elif 'svg' in a.content.mimetype: # Overlay svg svg_data = a.content.data else: # Overlay annotation title svg_data = self.controller.get_title(a) png_data = str(p.imagecache[a.fragment.begin]) return self.controller.gui.overlay(png_data, svg_data) m = re.search('/packages/(.+?)/imagecache/(\d+)', url) if m: alias, timestamp = m.groups() p = self.controller.packages.get(alias) if p is None: return None return str(p.imagecache[int(timestamp)]) return None def contextual_popup(self, ctx=None, menu=None): """Popup a contextual menu for the given context. """ if menu is None: menu = Gtk.Menu() def open_link(i, l): self.open_link(l) return True def goto_position(i, pos): self.controller.update_status('seek', pos) return True def select_presentation(i, ap, modes): ap.presentation = modes[:] ap.refresh() return True def new_menuitem(label, action, *params): item = Gtk.MenuItem(label) if action is not None: item.connect('activate', action, *params) item.show() menu.append(item) return item if ctx is None: ctx = self.editor.get_current_context() if ctx: if hasattr(ctx[-1], '_placeholder'): ap = ctx[-1]._placeholder if getattr(ap, 'annotation', None) is not None: new_menuitem( _("Annotation %s") % self.controller.get_title(ap.annotation), None) new_menuitem(_("Play video"), goto_position, ap.annotation.fragment.begin) new_menuitem(_("Show timestamp only"), select_presentation, ap, ['timestamp', 'link']) new_menuitem(_("Show content only"), select_presentation, ap, ['content', 'link']) new_menuitem(_("Show snapshot only"), select_presentation, ap, ['snapshot', 'link']) new_menuitem(_("Show overlayed timestamp"), select_presentation, ap, ['timestamp', 'snapshot', 'link']) new_menuitem(_("Show overlayed content"), select_presentation, ap, ['overlay', 'link']) elif getattr(ap, 'annotationtype', None) is not None: new_menuitem( _("Annotation type %s") % self.controller.get_title(ap.annotationtype), None) new_menuitem(_("display as list"), select_presentation, ap, ['list']) new_menuitem(_("display as grid"), select_presentation, ap, ['grid']) new_menuitem(_("display as table"), select_presentation, ap, ['table']) new_menuitem(_("display as transcription"), select_presentation, ap, ['transcription']) l = [m for m in ctx if m._tag == 'a'] if l: link = dict(l[0]._attr).get('href', None) if link: if '/media/play' in link: new_menuitem(_("Play video"), open_link, link) else: new_menuitem(_("Open link"), open_link, link) return menu def button_press_cb(self, textview, event): if not (event.button == 3 or (event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS)): return False textwin = textview.get_window(Gtk.TextWindowType.TEXT) if event.get_window() != textwin: return False (x, y) = textview.window_to_buffer_coords(Gtk.TextWindowType.TEXT, int(event.x), int(event.y)) it = textview.get_iter_at_location(x, y) if it is None: logger.warn("Error in get_iter_at_location") return False ctx = self.editor.get_current_context(it.iter) if not ctx: return False if event.button == 3: # Right button if hasattr(ctx[-1], '_placeholder'): # An annotation placeholder is here. Display popup menu. menu = self.contextual_popup(ctx) menu.popup_at_pointer(None) return True else: # Double click with left button if hasattr(ctx[-1], '_placeholder'): # There is an placeholder p = ctx[-1]._placeholder if isinstance(p, AnnotationPlaceholder): a = ctx[-1]._placeholder.annotation if a is not None: self.controller.update_status('seek', a.fragment.begin) return False l = [m for m in ctx if m._tag == 'a'] if l: link = dict(l[0]._attr).get('href', None) self.open_link(link) return False return False def get_view(self, compact=False): """Generate a view widget for editing HTML.""" vbox = Gtk.VBox() self.editor = HTMLEditor() self.editor.custom_url_loader = self.custom_url_loader self.editor.register_class_parser(self.class_parser) try: self.editor.set_text(self.element.data) except Exception as e: self.controller.log( _("HTML editor: cannot parse content (%s)") % str(e)) self.editor.connect('drag-data-received', self.editor_drag_received) self.editor.drag_dest_set( Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.ALL, config.data.get_target_types('annotation', 'annotation-type', 'timestamp'), Gdk.DragAction.COPY | Gdk.DragAction.LINK | Gdk.DragAction.ASK) self.editor.connect('button-press-event', self.button_press_cb) self.view = Gtk.VBox() def sel_copy(i): self.editor.get_buffer().copy_clipboard(get_clipboard()) return True def sel_cut(i): self.editor.get_buffer().cut_clipboard(get_clipboard()) return True def sel_paste(i): self.editor.get_buffer().paste_clipboard(get_clipboard()) return True def refresh(i): self.editor.refresh() return True def display_header_menu(i): m = Gtk.Menu() for h in (1, 2, 3): i = Gtk.MenuItem(_("Heading %d") % h) i.connect( 'activate', lambda w, level: self.editor.apply_html_tag('h%d' % level), h) m.append(i) m.show_all() m.popup(None, i, None, 1, Gtk.get_current_event_time()) return True tb = Gtk.Toolbar() vbox.toolbar = tb tb.set_style(Gtk.ToolbarStyle.ICONS) for (icon, tooltip, action) in ( (Gtk.STOCK_BOLD, _("Bold"), lambda i: self.editor.apply_html_tag('b')), (Gtk.STOCK_ITALIC, _("Italic"), lambda i: self.editor.apply_html_tag('i')), ("title_icon.png", _("Header"), display_header_menu), (None, None, None), (Gtk.STOCK_COPY, _("Copy"), sel_copy), (Gtk.STOCK_CUT, _("Cut"), sel_cut), (Gtk.STOCK_PASTE, _("Paste"), sel_paste), (None, None, None), (Gtk.STOCK_REFRESH, _("Refresh"), refresh), ): if not config.data.preferences[ 'expert-mode'] and icon == Gtk.STOCK_REFRESH: continue if not icon: b = Gtk.SeparatorToolItem() else: b = get_pixmap_toolbutton(icon, action) b.set_tooltip_text(tooltip) tb.insert(b, -1) b.show() if self.editor.can_undo(): b = Gtk.ToolButton(Gtk.STOCK_UNDO) b.connect('clicked', lambda i: self.editor.undo()) b.set_tooltip_text(_("Undo")) tb.insert(b, -1) b.show() self.view.pack_start(tb, False, True, 0) sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.add(self.editor) context_data = ContextDisplay() def cursor_moved(buf, it, mark): if mark.get_name() == 'insert': context_data.set_context(self.editor.get_current_context(it)) return True self.editor.get_buffer().connect('mark-set', cursor_moved) sw2 = Gtk.ScrolledWindow() sw2.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw2.add(context_data) p = Gtk.HPaned() p.add1(sw2) p.add2(sw) # Hide by default p.set_position(0) p.show_all() self.view.add(p) def edit_wysiwyg(*p): vbox.foreach(vbox.remove) vbox.add(self.view) self.editing_source = False vbox.show_all() return True def edit_source(*p): if self.sourceview is None: self.sourceview = TextContentHandler( element=self.element, controller=self.controller, parent=self.parent) self.sourceview.widget = self.sourceview.get_view() b = get_pixmap_toolbutton('xml.png', edit_wysiwyg) b.set_tooltip_text(_("WYSIWYG editor")) self.sourceview.toolbar.insert(b, 0) vbox.foreach(vbox.remove) vbox.add(self.sourceview.widget) self.editing_source = True vbox.show_all() return True b = get_pixmap_toolbutton('xml.png', edit_source) b.set_tooltip_text(_("Edit HTML source")) tb.insert(b, 0) if config.data.preferences['prefer-wysiwyg']: edit_wysiwyg() self.editor.set_modified(False) else: edit_source() self.sourceview.set_modified(False) return vbox