def addColumnCategory(self, name, columns, visible=True): qa = QAction(name, self) qa.setCheckable(True) qa.setChecked(visible) if not visible: self._showColumnCategory(columns, False) QObject.connect(qa, SIGNAL("toggled(bool)"), self._currier.curry(self._showColumnCategory, columns)) self._column_views.append((name, qa, columns))
class DocumentView(QWebView): # {{{ magnification_changed = pyqtSignal(object) DISABLED_BRUSH = QBrush(Qt.lightGray, Qt.Dense5Pattern) def initialize_view(self, debug_javascript=False): self.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform) self.flipper = SlideFlip(self) self.is_auto_repeat_event = False self.debug_javascript = debug_javascript self.shortcuts = Shortcuts(SHORTCUTS, 'shortcuts/viewer') self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self._size_hint = QSize(510, 680) self.initial_pos = 0.0 self.to_bottom = False self.document = Document(self.shortcuts, parent=self, debug_javascript=debug_javascript) self.setPage(self.document) self.inspector = WebInspector(self, self.document) self.manager = None self._reference_mode = False self._ignore_scrollbar_signals = False self.loading_url = None self.loadFinished.connect(self.load_finished) self.connect(self.document, SIGNAL('linkClicked(QUrl)'), self.link_clicked) self.connect(self.document, SIGNAL('linkHovered(QString,QString,QString)'), self.link_hovered) self.connect(self.document, SIGNAL('selectionChanged()'), self.selection_changed) self.connect(self.document, SIGNAL('animated_scroll_done()'), self.animated_scroll_done, Qt.QueuedConnection) self.document.page_turn.connect(self.page_turn_requested) copy_action = self.pageAction(self.document.Copy) copy_action.setIcon(QIcon(I('convert.png'))) d = self.document self.unimplemented_actions = list(map(self.pageAction, [d.DownloadImageToDisk, d.OpenLinkInNewWindow, d.DownloadLinkToDisk, d.OpenImageInNewWindow, d.OpenLink, d.Reload, d.InspectElement])) self.search_online_action = QAction(QIcon(I('search.png')), '', self) self.search_online_action.triggered.connect(self.search_online) self.addAction(self.search_online_action) self.dictionary_action = QAction(QIcon(I('dictionary.png')), _('&Lookup in dictionary'), self) self.dictionary_action.triggered.connect(self.lookup) self.addAction(self.dictionary_action) self.image_popup = ImagePopup(self) self.table_popup = TablePopup(self) self.view_image_action = QAction(QIcon(I('view-image.png')), _('View &image...'), self) self.view_image_action.triggered.connect(self.image_popup) self.view_table_action = QAction(QIcon(I('view.png')), _('View &table...'), self) self.view_table_action.triggered.connect(self.popup_table) self.search_action = QAction(QIcon(I('dictionary.png')), _('&Search for next occurrence'), self) self.search_action.triggered.connect(self.search_next) self.addAction(self.search_action) self.goto_location_action = QAction(_('Go to...'), self) self.goto_location_menu = m = QMenu(self) self.goto_location_actions = a = { 'Next Page': self.next_page, 'Previous Page': self.previous_page, 'Section Top' : partial(self.scroll_to, 0), 'Document Top': self.goto_document_start, 'Section Bottom':partial(self.scroll_to, 1), 'Document Bottom': self.goto_document_end, 'Next Section': self.goto_next_section, 'Previous Section': self.goto_previous_section, } for name, key in [(_('Next Section'), 'Next Section'), (_('Previous Section'), 'Previous Section'), (None, None), (_('Document Start'), 'Document Top'), (_('Document End'), 'Document Bottom'), (None, None), (_('Section Start'), 'Section Top'), (_('Section End'), 'Section Bottom'), (None, None), (_('Next Page'), 'Next Page'), (_('Previous Page'), 'Previous Page')]: if key is None: m.addSeparator() else: m.addAction(name, a[key], self.shortcuts.get_sequences(key)[0]) self.goto_location_action.setMenu(self.goto_location_menu) self.grabGesture(Qt.SwipeGesture) self.restore_fonts_action = QAction(_('Default font size'), self) self.restore_fonts_action.setCheckable(True) self.restore_fonts_action.triggered.connect(self.restore_font_size) def goto_next_section(self, *args): if self.manager is not None: self.manager.goto_next_section() def goto_previous_section(self, *args): if self.manager is not None: self.manager.goto_previous_section() def goto_document_start(self, *args): if self.manager is not None: self.manager.goto_start() def goto_document_end(self, *args): if self.manager is not None: self.manager.goto_end() @property def copy_action(self): return self.pageAction(self.document.Copy) def animated_scroll_done(self): if self.manager is not None: self.manager.scrolled(self.document.scroll_fraction) def reference_mode(self, enable): self._reference_mode = enable self.document.reference_mode(enable) def goto(self, ref): self.document.goto(ref) def goto_bookmark(self, bm): self.document.goto_bookmark(bm) def config(self, parent=None): self.document.do_config(parent) if self.document.in_fullscreen_mode: self.document.switch_to_fullscreen_mode() self.setFocus(Qt.OtherFocusReason) def load_theme(self, theme_id): themes = load_themes() theme = themes[theme_id] opts = config(theme).parse() self.document.apply_settings(opts) if self.document.in_fullscreen_mode: self.document.switch_to_fullscreen_mode() self.setFocus(Qt.OtherFocusReason) def bookmark(self): return self.document.bookmark() def selection_changed(self): if self.manager is not None: self.manager.selection_changed(unicode(self.document.selectedText())) def _selectedText(self): t = unicode(self.selectedText()).strip() if not t: return u'' if len(t) > 40: t = t[:40] + u'...' t = t.replace(u'&', u'&&') return _("S&earch Google for '%s'")%t def popup_table(self): html = self.document.extract_node() self.table_popup(html, QUrl.fromLocalFile(self.last_loaded_path), self.document.font_magnification_step) def contextMenuEvent(self, ev): mf = self.document.mainFrame() r = mf.hitTestContent(ev.pos()) img = r.pixmap() elem = r.element() if elem.isNull(): elem = r.enclosingBlockElement() table = None parent = elem while not parent.isNull(): if (unicode(parent.tagName()) == u'table' or unicode(parent.localName()) == u'table'): table = parent break parent = parent.parent() self.image_popup.current_img = img self.image_popup.current_url = r.imageUrl() menu = self.document.createStandardContextMenu() for action in self.unimplemented_actions: menu.removeAction(action) if not img.isNull(): menu.addAction(self.view_image_action) if table is not None: self.document.mark_element.emit(table) menu.addAction(self.view_table_action) text = self._selectedText() if text and img.isNull(): self.search_online_action.setText(text) for x, sc in (('search_online', 'Search online'), ('dictionary', 'Lookup word'), ('search', 'Next occurrence')): ac = getattr(self, '%s_action' % x) menu.addAction(ac.icon(), '%s [%s]' % (unicode(ac.text()), ','.join(self.shortcuts.get_shortcuts(sc))), ac.trigger) if not text and img.isNull(): menu.addSeparator() if self.manager.action_back.isEnabled(): menu.addAction(self.manager.action_back) if self.manager.action_forward.isEnabled(): menu.addAction(self.manager.action_forward) menu.addAction(self.goto_location_action) if self.manager is not None: menu.addSeparator() menu.addAction(self.manager.action_table_of_contents) menu.addSeparator() menu.addAction(self.manager.action_font_size_larger) self.restore_fonts_action.setChecked(self.multiplier == 1) menu.addAction(self.restore_fonts_action) menu.addAction(self.manager.action_font_size_smaller) menu.addSeparator() menu.addAction(_('Inspect'), self.inspect) if not text and img.isNull() and self.manager is not None: menu.addSeparator() if (not self.document.show_controls or self.document.in_fullscreen_mode) and self.manager is not None: menu.addAction(self.manager.toggle_toolbar_action) menu.addAction(self.manager.action_full_screen) menu.addSeparator() menu.addAction(self.manager.action_quit) menu.exec_(ev.globalPos()) def inspect(self): self.inspector.show() self.inspector.raise_() self.pageAction(self.document.InspectElement).trigger() def lookup(self, *args): if self.manager is not None: t = unicode(self.selectedText()).strip() if t: self.manager.lookup(t.split()[0]) def search_next(self): if self.manager is not None: t = unicode(self.selectedText()).strip() if t: self.manager.search.set_search_string(t) def search_online(self): t = unicode(self.selectedText()).strip() if t: url = 'https://www.google.com/search?q=' + QUrl().toPercentEncoding(t) open_url(QUrl.fromEncoded(url)) def set_manager(self, manager): self.manager = manager self.scrollbar = manager.horizontal_scrollbar self.connect(self.scrollbar, SIGNAL('valueChanged(int)'), self.scroll_horizontally) def scroll_horizontally(self, amount): self.document.scroll_to(y=self.document.ypos, x=amount) @property def scroll_pos(self): return (self.document.ypos, self.document.ypos + self.document.window_height) @property def viewport_rect(self): # (left, top, right, bottom) of the viewport in document co-ordinates # When in paged mode, left and right are the numbers of the columns # at the left edge and *after* the right edge of the viewport d = self.document if d.in_paged_mode: try: l, r = d.column_boundaries except ValueError: l, r = (0, 1) else: l, r = d.xpos, d.xpos + d.window_width return (l, d.ypos, r, d.ypos + d.window_height) def link_hovered(self, link, text, context): link, text = unicode(link), unicode(text) if link: self.setCursor(Qt.PointingHandCursor) else: self.unsetCursor() def link_clicked(self, url): if self.manager is not None: self.manager.link_clicked(url) def sizeHint(self): return self._size_hint @dynamic_property def scroll_fraction(self): def fget(self): return self.document.scroll_fraction def fset(self, val): self.document.scroll_fraction = float(val) return property(fget=fget, fset=fset) @property def hscroll_fraction(self): return self.document.hscroll_fraction @property def content_size(self): return self.document.width, self.document.height @dynamic_property def current_language(self): def fget(self): return self.document.current_language def fset(self, val): self.document.current_language = val return property(fget=fget, fset=fset) def search(self, text, backwards=False): flags = self.document.FindBackward if backwards else self.document.FindFlags(0) found = self.document.findText(text, flags) if found and self.document.in_paged_mode: self.document.javascript('paged_display.snap_to_selection()') return found def path(self): return os.path.abspath(unicode(self.url().toLocalFile())) def load_path(self, path, pos=0.0): self.initial_pos = pos self.last_loaded_path = path def callback(lu): self.loading_url = lu if self.manager is not None: self.manager.load_started() load_html(path, self, codec=getattr(path, 'encoding', 'utf-8'), mime_type=getattr(path, 'mime_type', 'text/html'), pre_load_callback=callback) entries = set() for ie in getattr(path, 'index_entries', []): if ie.start_anchor: entries.add(ie.start_anchor) if ie.end_anchor: entries.add(ie.end_anchor) self.document.index_anchors = entries def initialize_scrollbar(self): if getattr(self, 'scrollbar', None) is not None: if self.document.in_paged_mode: self.scrollbar.setVisible(False) return delta = self.document.width - self.size().width() if delta > 0: self._ignore_scrollbar_signals = True self.scrollbar.blockSignals(True) self.scrollbar.setRange(0, delta) self.scrollbar.setValue(0) self.scrollbar.setSingleStep(1) self.scrollbar.setPageStep(int(delta/10.)) self.scrollbar.setVisible(delta > 0) self.scrollbar.blockSignals(False) self._ignore_scrollbar_signals = False def load_finished(self, ok): if self.loading_url is None: # An <iframe> finished loading return self.loading_url = None self.document.load_javascript_libraries() self.document.after_load(self.last_loaded_path) self._size_hint = self.document.mainFrame().contentsSize() scrolled = False if self.to_bottom: self.to_bottom = False self.initial_pos = 1.0 if self.initial_pos > 0.0: scrolled = True self.scroll_to(self.initial_pos, notify=False) self.initial_pos = 0.0 self.update() self.initialize_scrollbar() self.document.reference_mode(self._reference_mode) if self.manager is not None: spine_index = self.manager.load_finished(bool(ok)) if spine_index > -1: self.document.set_reference_prefix('%d.'%(spine_index+1)) if scrolled: self.manager.scrolled(self.document.scroll_fraction, onload=True) if self.flipper.isVisible(): if self.flipper.running: self.flipper.setVisible(False) else: self.flipper(self.current_page_image(), duration=self.document.page_flip_duration) @classmethod def test_line(cls, img, y): 'Test if line contains pixels of exactly the same color' start = img.pixel(0, y) for i in range(1, img.width()): if img.pixel(i, y) != start: return False return True def current_page_image(self, overlap=-1): if overlap < 0: overlap = self.height() img = QImage(self.width(), overlap, QImage.Format_ARGB32_Premultiplied) painter = QPainter(img) painter.setRenderHints(self.renderHints()) self.document.mainFrame().render(painter, QRegion(0, 0, self.width(), overlap)) painter.end() return img def find_next_blank_line(self, overlap): img = self.current_page_image(overlap) for i in range(overlap-1, -1, -1): if self.test_line(img, i): self.scroll_by(y=i, notify=False) return self.scroll_by(y=overlap) def previous_page(self): if self.flipper.running and not self.is_auto_repeat_event: return if self.loading_url is not None: return epf = self.document.enable_page_flip and not self.is_auto_repeat_event if self.document.in_paged_mode: loc = self.document.javascript( 'paged_display.previous_screen_location()', typ='int') if loc < 0: if self.manager is not None: if epf: self.flipper.initialize(self.current_page_image(), forwards=False) self.manager.previous_document() else: if epf: self.flipper.initialize(self.current_page_image(), forwards=False) self.document.scroll_to(x=loc, y=0) if epf: self.flipper(self.current_page_image(), duration=self.document.page_flip_duration) if self.manager is not None: self.manager.scrolled(self.scroll_fraction) return delta_y = self.document.window_height - 25 if self.document.at_top: if self.manager is not None: self.to_bottom = True if epf: self.flipper.initialize(self.current_page_image(), False) self.manager.previous_document() else: opos = self.document.ypos upper_limit = opos - delta_y if upper_limit < 0: upper_limit = 0 if upper_limit < opos: if epf: self.flipper.initialize(self.current_page_image(), forwards=False) self.document.scroll_to(self.document.xpos, upper_limit) if epf: self.flipper(self.current_page_image(), duration=self.document.page_flip_duration) if self.manager is not None: self.manager.scrolled(self.scroll_fraction) def next_page(self): if self.flipper.running and not self.is_auto_repeat_event: return if self.loading_url is not None: return epf = self.document.enable_page_flip and not self.is_auto_repeat_event if self.document.in_paged_mode: loc = self.document.javascript( 'paged_display.next_screen_location()', typ='int') if loc < 0: if self.manager is not None: if epf: self.flipper.initialize(self.current_page_image()) self.manager.next_document() else: if epf: self.flipper.initialize(self.current_page_image()) self.document.scroll_to(x=loc, y=0) if epf: self.flipper(self.current_page_image(), duration=self.document.page_flip_duration) if self.manager is not None: self.manager.scrolled(self.scroll_fraction) return window_height = self.document.window_height document_height = self.document.height ddelta = document_height - window_height # print '\nWindow height:', window_height # print 'Document height:', self.document.height delta_y = window_height - 25 if self.document.at_bottom or ddelta <= 0: if self.manager is not None: if epf: self.flipper.initialize(self.current_page_image()) self.manager.next_document() elif ddelta < 25: self.scroll_by(y=ddelta) return else: oopos = self.document.ypos # print 'Original position:', oopos self.document.set_bottom_padding(0) opos = self.document.ypos # print 'After set padding=0:', self.document.ypos if opos < oopos: if self.manager is not None: if epf: self.flipper.initialize(self.current_page_image()) self.manager.next_document() return # oheight = self.document.height lower_limit = opos + delta_y # Max value of top y co-ord after scrolling max_y = self.document.height - window_height # The maximum possible top y co-ord if max_y < lower_limit: padding = lower_limit - max_y if padding == window_height: if self.manager is not None: if epf: self.flipper.initialize(self.current_page_image()) self.manager.next_document() return # print 'Setting padding to:', lower_limit - max_y self.document.set_bottom_padding(lower_limit - max_y) if epf: self.flipper.initialize(self.current_page_image()) # print 'Document height:', self.document.height # print 'Height change:', (self.document.height - oheight) max_y = self.document.height - window_height lower_limit = min(max_y, lower_limit) # print 'Scroll to:', lower_limit if lower_limit > opos: self.document.scroll_to(self.document.xpos, lower_limit) actually_scrolled = self.document.ypos - opos # print 'After scroll pos:', self.document.ypos # print 'Scrolled by:', self.document.ypos - opos self.find_next_blank_line(window_height - actually_scrolled) # print 'After blank line pos:', self.document.ypos if epf: self.flipper(self.current_page_image(), duration=self.document.page_flip_duration) if self.manager is not None: self.manager.scrolled(self.scroll_fraction) # print 'After all:', self.document.ypos def page_turn_requested(self, backwards): if backwards: self.previous_page() else: self.next_page() def scroll_by(self, x=0, y=0, notify=True): old_pos = (self.document.xpos if self.document.in_paged_mode else self.document.ypos) self.document.scroll_by(x, y) new_pos = (self.document.xpos if self.document.in_paged_mode else self.document.ypos) if notify and self.manager is not None and new_pos != old_pos: self.manager.scrolled(self.scroll_fraction) def scroll_to(self, pos, notify=True): if self._ignore_scrollbar_signals: return old_pos = (self.document.xpos if self.document.in_paged_mode else self.document.ypos) if self.document.in_paged_mode: if isinstance(pos, basestring): self.document.jump_to_anchor(pos) else: self.document.scroll_fraction = pos else: if isinstance(pos, basestring): self.document.jump_to_anchor(pos) else: if pos >= 1: self.document.scroll_to(0, self.document.height) else: y = int(math.ceil( pos*(self.document.height-self.document.window_height))) self.document.scroll_to(0, y) new_pos = (self.document.xpos if self.document.in_paged_mode else self.document.ypos) if notify and self.manager is not None and new_pos != old_pos: self.manager.scrolled(self.scroll_fraction) @dynamic_property def multiplier(self): def fget(self): return self.zoomFactor() def fset(self, val): self.setZoomFactor(val) self.magnification_changed.emit(val) return property(fget=fget, fset=fset) def magnify_fonts(self, amount=None): if amount is None: amount = self.document.font_magnification_step with self.document.page_position: self.multiplier += amount return self.document.scroll_fraction def shrink_fonts(self, amount=None): if amount is None: amount = self.document.font_magnification_step if self.multiplier >= amount: with self.document.page_position: self.multiplier -= amount return self.document.scroll_fraction def restore_font_size(self): with self.document.page_position: self.multiplier = 1 return self.document.scroll_fraction def changeEvent(self, event): if event.type() == event.EnabledChange: self.update() return QWebView.changeEvent(self, event) def paintEvent(self, event): painter = QPainter(self) painter.setRenderHints(self.renderHints()) self.document.mainFrame().render(painter, event.region()) if not self.isEnabled(): painter.fillRect(event.region().boundingRect(), self.DISABLED_BRUSH) painter.end() def wheelEvent(self, event): mods = event.modifiers() if mods & Qt.CTRL: if self.manager is not None and event.delta() != 0: (self.manager.font_size_larger if event.delta() > 0 else self.manager.font_size_smaller)() return if self.document.in_paged_mode: if abs(event.delta()) < 15: return typ = 'screen' if self.document.wheel_flips_pages else 'col' direction = 'next' if event.delta() < 0 else 'previous' loc = self.document.javascript('paged_display.%s_%s_location()'%( direction, typ), typ='int') if loc > -1: self.document.scroll_to(x=loc, y=0) if self.manager is not None: self.manager.scrolled(self.scroll_fraction) event.accept() elif self.manager is not None: if direction == 'next': self.manager.next_document() else: self.manager.previous_document() event.accept() return if event.delta() < -14: if self.document.wheel_flips_pages: self.next_page() event.accept() return if self.document.at_bottom: self.scroll_by(y=15) # at_bottom can lie on windows if self.manager is not None: self.manager.next_document() event.accept() return elif event.delta() > 14: if self.document.wheel_flips_pages: self.previous_page() event.accept() return if self.document.at_top: if self.manager is not None: self.manager.previous_document() event.accept() return ret = QWebView.wheelEvent(self, event) scroll_amount = (event.delta() / 120.0) * .2 * -1 if event.orientation() == Qt.Vertical: self.scroll_by(0, self.document.viewportSize().height() * scroll_amount) else: self.scroll_by(self.document.viewportSize().width() * scroll_amount, 0) if self.manager is not None: self.manager.scrolled(self.scroll_fraction) return ret def keyPressEvent(self, event): if not self.handle_key_press(event): return QWebView.keyPressEvent(self, event) def paged_col_scroll(self, forward=True, scroll_past_end=True): dir = 'next' if forward else 'previous' loc = self.document.javascript( 'paged_display.%s_col_location()'%dir, typ='int') if loc > -1: self.document.scroll_to(x=loc, y=0) self.manager.scrolled(self.document.scroll_fraction) elif scroll_past_end: (self.manager.next_document() if forward else self.manager.previous_document()) def handle_key_press(self, event): handled = True key = self.shortcuts.get_match(event) func = self.goto_location_actions.get(key, None) if func is not None: self.is_auto_repeat_event = event.isAutoRepeat() try: func() finally: self.is_auto_repeat_event = False elif key == 'Down': if self.document.in_paged_mode: self.paged_col_scroll(scroll_past_end=not self.document.line_scrolling_stops_on_pagebreaks) else: if (not self.document.line_scrolling_stops_on_pagebreaks and self.document.at_bottom): self.manager.next_document() else: self.scroll_by(y=15) elif key == 'Up': if self.document.in_paged_mode: self.paged_col_scroll(forward=False, scroll_past_end=not self.document.line_scrolling_stops_on_pagebreaks) else: if (not self.document.line_scrolling_stops_on_pagebreaks and self.document.at_top): self.manager.previous_document() else: self.scroll_by(y=-15) elif key == 'Left': if self.document.in_paged_mode: self.paged_col_scroll(forward=False) else: self.scroll_by(x=-15) elif key == 'Right': if self.document.in_paged_mode: self.paged_col_scroll() else: self.scroll_by(x=15) elif key == 'Back': if self.manager is not None: self.manager.back(None) elif key == 'Forward': if self.manager is not None: self.manager.forward(None) else: handled = False return handled def resizeEvent(self, event): if self.manager is not None: self.manager.viewport_resize_started(event) return QWebView.resizeEvent(self, event) def event(self, ev): if ev.type() == ev.Gesture: swipe = ev.gesture(Qt.SwipeGesture) if swipe is not None: self.handle_swipe(swipe) return True return QWebView.event(self, ev) def handle_swipe(self, swipe): if swipe.state() == Qt.GestureFinished: if swipe.horizontalDirection() == QSwipeGesture.Left: self.previous_page() elif swipe.horizontalDirection() == QSwipeGesture.Right: self.next_page() elif swipe.verticalDirection() == QSwipeGesture.Up: self.goto_previous_section() elif swipe.horizontalDirection() == QSwipeGesture.Down: self.goto_next_section() def mouseReleaseEvent(self, ev): opos = self.document.ypos ret = QWebView.mouseReleaseEvent(self, ev) if self.manager is not None and opos != self.document.ypos: self.manager.internal_link_clicked(opos) self.manager.scrolled(self.scroll_fraction) return ret
class Ui_DnaSequenceEditor(PM_DockWidget): """ The Ui_DnaSequenceEditor class defines UI elements for the Sequence Editor object. The sequence editor is usually visible while in while editing a DnaStrand. It is a DockWidget that is docked at the bottom of the MainWindow. """ _title = "Sequence Editor" _groupBoxCount = 0 _lastGroupBox = None def __init__(self, win): """ Constructor for the Ui_DnaSequenceEditor @param win: The parentWidget (MainWindow) for the sequence editor """ self.win = win # Should parentWidget for a docwidget always be win? #Not necessary but most likely it will be the case. parentWidget = win _superclass.__init__(self, parentWidget, title=self._title) #A flag used to restore the state of the Reports dock widget #(which can be accessed through View > Reports) see self.show() and #self.closeEvent() for more details. self._reportsDockWidget_closed_in_show_method = False self.setFixedHeight(90) return def show(self): """ Shows the sequence editor. While doing this, it also closes the reports dock widget (if visible) the state of the reports dockwidget will be restored when the sequence editor is closed. @see:self.closeEvent() """ self._reportsDockWidget_closed_in_show_method = False #hide the history widget first #(It will be shown back during self.close) #The history widget is hidden or shown only when both # 'View > Full Screen' and View > Semi Full Screen actions # are *unchecked* #Thus show or close methods won't do anything to history widget # if either of the above mentioned actions is checked. if self.win.viewFullScreenAction.isChecked() or \ self.win.viewSemiFullScreenAction.isChecked(): pass else: if self.win.reportsDockWidget.isVisible(): self.win.reportsDockWidget.close() self._reportsDockWidget_closed_in_show_method = True _superclass.show(self) return def closeEvent(self, event): """ Overrides close event. Makes sure that the visible state of the reports widgetis restored when the sequence editor is closed. @see: self.show() """ _superclass.closeEvent(self, event) if self.win.viewFullScreenAction.isChecked() or \ self.win.viewSemiFullScreenAction.isChecked(): pass else: if self._reportsDockWidget_closed_in_show_method: self.win.viewReportsAction.setChecked(True) self._reportsDockWidget_closed_in_show_method = False return def _loadWidgets(self): """ Overrides PM.PM_DockWidget._loadWidgets. Loads the widget in this dockwidget. """ self._loadMenuWidgets() self._loadTextEditWidget() return def _loadMenuWidgets(self): """ Load the various menu widgets (e.g. Open, save sequence options, Find and replace widgets etc. """ #Note: Find and replace widgets might be moved to their own class. self.loadSequenceButton = PM_ToolButton( self, iconPath="ui/actions/Properties Manager/Open.png") self.saveSequenceButton = PM_ToolButton( self, iconPath="ui/actions/Properties Manager/Save_Strand_Sequence.png") self.loadSequenceButton.setAutoRaise(True) self.saveSequenceButton.setAutoRaise(True) # Only supporting 5' to 3' direction until bug 2956 is fixed. # Mark 2008-12-19 editDirectionChoices = ["5' to 3'"] # , "3' to 5'"] self.baseDirectionChoiceComboBox = \ PM_ComboBox( self, choices = editDirectionChoices, index = 0, spanWidth = False ) #Find and replace widgets -- self.findLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.findLineEdit.setMaximumWidth(60) self.replaceLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.replaceLineEdit.setMaximumWidth(60) self.findOptionsToolButton = PM_ToolButton(self) self.findOptionsToolButton.setMaximumWidth(12) self.findOptionsToolButton.setAutoRaise(True) self.findOptionsToolButton.setPopupMode(QToolButton.MenuButtonPopup) self._setFindOptionsToolButtonMenu() self.findNextToolButton = PM_ToolButton( self, iconPath="ui/actions/Properties Manager/Find_Next.png") self.findNextToolButton.setAutoRaise(True) self.findPreviousToolButton = PM_ToolButton( self, iconPath="ui/actions/Properties Manager/Find_Previous.png") self.findPreviousToolButton.setAutoRaise(True) self.replacePushButton = PM_PushButton(self, text="Replace") self.warningSign = QLabel(self) self.warningSign.setPixmap( getpixmap('ui/actions/Properties Manager/Warning.png')) self.warningSign.hide() self.phraseNotFoundLabel = QLabel(self) self.phraseNotFoundLabel.setText("Sequence Not Found") self.phraseNotFoundLabel.hide() # NOTE: Following needs cleanup in the PM_WidgetRow/ PM_WidgetGrid # but this explanation is sufficient until thats done -- # When the widget type starts with the word 'PM_' , the # PM_WidgetRow treats it as a well defined widget and thus doesn't try # to create a QWidget object (or its subclasses) # This is the reason why qLabels such as self.warningSign and # self.phraseNotFoundLabel are defined as PM_Labels and not 'QLabels' # If they were defined as 'QLabel'(s) then PM_WidgetRow would have # recreated the label. Since we want to show/hide the above mentioned # labels (and if they were recreated as mentioned above), # we would have needed to define those something like this: # self.phraseNotFoundLabel = widgetRow._widgetList[-2] #Cleanup in PM_widgetGrid could be to check if the widget starts with #'Q' instead of 'PM_' #Widgets to include in the widget row. widgetList = [('PM_ToolButton', self.loadSequenceButton, 0), ('PM_ToolButton', self.saveSequenceButton, 1), ('QLabel', " Sequence direction:", 2), ('PM_ComboBox', self.baseDirectionChoiceComboBox, 3), ('QLabel', " Find:", 4), ('PM_LineEdit', self.findLineEdit, 5), ('PM_ToolButton', self.findOptionsToolButton, 6), ('PM_ToolButton', self.findPreviousToolButton, 7), ('PM_ToolButton', self.findNextToolButton, 8), ('QLabel', " Replace:", 9), ('PM_TextEdit', self.replaceLineEdit, 10), ('PM_PushButton', self.replacePushButton, 11), ('PM_Label', self.warningSign, 12), ('PM_Label', self.phraseNotFoundLabel, 13), ('QSpacerItem', 5, 5, 14)] widgetRow = PM_WidgetRow(self, title='', widgetList=widgetList, label="", spanWidth=True) return def _loadTextEditWidget(self): """ Load the SequenceTexteditWidgets. """ self.sequenceTextEdit = \ PM_TextEdit( self, label = " Sequence: ", spanWidth = False, permit_enter_keystroke = False) self.sequenceTextEdit.setCursorWidth(2) self.sequenceTextEdit.setWordWrapMode(QTextOption.WrapAnywhere) self.sequenceTextEdit.setFixedHeight(20) #The StrandSequence 'Mate' it is a read only etxtedit that shows #the complementary strand sequence. self.sequenceTextEdit_mate = \ PM_TextEdit(self, label = "", spanWidth = False, permit_enter_keystroke = False ) palette = getPalette(None, QPalette.Base, sequenceEditStrandMateBaseColor) self.sequenceTextEdit_mate.setPalette(palette) self.sequenceTextEdit_mate.setFixedHeight(20) self.sequenceTextEdit_mate.setReadOnly(True) self.sequenceTextEdit_mate.setWordWrapMode(QTextOption.WrapAnywhere) #Important to make sure that the horizontal and vertical scrollbars #for these text edits are never displayed. for textEdit in (self.sequenceTextEdit, self.sequenceTextEdit_mate): textEdit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) textEdit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) return def _getFindLineEditStyleSheet(self): """ Return the style sheet for the findLineEdit. This sets the following properties only: - background-color This style is set whenever the searchStrig can't be found (sets a light red color background to the lineedit when this happens) @return: The line edit style sheet. @rtype: str """ styleSheet = "QLineEdit {"\ "background-color: rgb(255, 102, 102)"\ "}" #Not used: # background-color: rgb(217, 255, 216)\ return styleSheet def _setFindOptionsToolButtonMenu(self): """ Sets the menu for the findOptionstoolbutton that appears a small menu button next to the findLineEdit. """ self.findOptionsMenu = QMenu(self.findOptionsToolButton) self.caseSensitiveFindAction = QAction(self.findOptionsToolButton) self.caseSensitiveFindAction.setText('Match Case') self.caseSensitiveFindAction.setCheckable(True) self.caseSensitiveFindAction.setChecked(False) self.findOptionsMenu.addAction(self.caseSensitiveFindAction) self.findOptionsMenu.addSeparator() self.findOptionsToolButton.setMenu(self.findOptionsMenu) return def _addToolTipText(self): """ What's Tool Tip text for widgets in this Property Manager. """ from ne1_ui.ToolTipText_for_PropertyManagers import ToolTip_DnaSequenceEditor ToolTip_DnaSequenceEditor(self) return def _addWhatsThisText(self): """ What's This text for widgets in this Property Manager. """ from ne1_ui.WhatsThisText_for_PropertyManagers import whatsThis_DnaSequenceEditor whatsThis_DnaSequenceEditor(self) return
class Ui_DnaSequenceEditor(PM_DockWidget): """ The Ui_DnaSequenceEditor class defines UI elements for the Sequence Editor object. The sequence editor is usually visible while in DNA edit mode. It is a DockWidget that is doced at the bottom of the MainWindow """ _title = "Sequence Editor" _groupBoxCount = 0 _lastGroupBox = None def __init__(self, win): """ Constructor for the Ui_DnaSequenceEditor @param win: The parentWidget (MainWindow) for the sequence editor """ self.win = win # Should parentWidget for a docwidget always be win? #Not necessary but most likely it will be the case. parentWidget = win _superclass.__init__(self, parentWidget, title = self._title) #A flag used to restore the state of the Reports dock widget #(which can be accessed through View > Reports) see self.show() and #self.closeEvent() for more details. self._reportsDockWidget_closed_in_show_method = False self.setFixedHeight(90) def show(self): """ Shows the sequence editor. While doing this, it also closes the reports dock widget (if visible) the state of the reports dockwidget will be restored when the sequence editor is closed. @see:self.closeEvent() """ self._reportsDockWidget_closed_in_show_method = False #hide the history widget first #(It will be shown back during self.close) #The history widget is hidden or shown only when both # 'View > Full Screen' and View > Semi Full Screen actions # are *unchecked* #Thus show or close methods won't do anything to history widget # if either of the above mentioned actions is checked. if self.win.viewFullScreenAction.isChecked() or \ self.win.viewSemiFullScreenAction.isChecked(): pass else: if self.win.reportsDockWidget.isVisible(): self.win.reportsDockWidget.close() self._reportsDockWidget_closed_in_show_method = True _superclass.show(self) def closeEvent(self, event): """ Overrides close event. Makes sure that the visible state of the reports widgetis restored when the sequence editor is closed. @see: self.show() """ _superclass.closeEvent(self, event) if self.win.viewFullScreenAction.isChecked() or \ self.win.viewSemiFullScreenAction.isChecked(): pass else: if self._reportsDockWidget_closed_in_show_method: self.win.viewReportsAction.setChecked(True) self._reportsDockWidget_closed_in_show_method = False def _loadWidgets(self): """ Overrides PM.PM_DockWidget._loadWidgets. Loads the widget in this dockwidget. """ self._loadMenuWidgets() self._loadTextEditWidget() def _loadMenuWidgets(self): """ Load the various menu widgets (e.g. Open, save sequence options, Find and replace widgets etc. """ #Note: Find and replace widgets might be moved to their own class. self.loadSequenceButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Open.png") self.saveSequenceButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Save_Strand_Sequence.png") self.loadSequenceButton.setAutoRaise(True) self.saveSequenceButton.setAutoRaise(True) editDirectionChoices = ["5' to 3'", "3' to 5'"] self.baseDirectionChoiceComboBox = \ PM_ComboBox( self, choices = editDirectionChoices, index = 0, spanWidth = False ) #Find and replace widgets -- self.findLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.findLineEdit.setMaximumWidth(60) self.replaceLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.replaceLineEdit.setMaximumWidth(60) self.findOptionsToolButton = PM_ToolButton(self) self.findOptionsToolButton.setMaximumWidth(12) self.findOptionsToolButton.setAutoRaise(True) self.findOptionsToolButton.setPopupMode(QToolButton.MenuButtonPopup) self._setFindOptionsToolButtonMenu() self.findNextToolButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Find_Next.png") self.findNextToolButton.setAutoRaise(True) self.findPreviousToolButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Find_Previous.png") self.findPreviousToolButton.setAutoRaise(True) self.replacePushButton = PM_PushButton(self, text = "Replace") self.warningSign = QLabel(self) self.warningSign.setPixmap( getpixmap('ui/actions/Properties Manager/Warning.png')) self.warningSign.hide() self.phraseNotFoundLabel = QLabel(self) self.phraseNotFoundLabel.setText("Sequence Not Found") self.phraseNotFoundLabel.hide() # NOTE: Following needs cleanup in the PM_WidgetRow/ PM_WidgetGrid # but this explanation is sufficient until thats done -- # When the widget type starts with the word 'PM_' , the # PM_WidgetRow treats it as a well defined widget and thus doesn't try # to create a QWidget object (or its subclasses) # This is the reason why qLabels such as self.warningSign and # self.phraseNotFoundLabel are defined as PM_Labels and not 'QLabels' # If they were defined as 'QLabel'(s) then PM_WidgetRow would have # recreated the label. Since we want to show/hide the above mentioned # labels (and if they were recreated as mentioned above), # we would have needed to define those something like this: # self.phraseNotFoundLabel = widgetRow._widgetList[-2] #Cleanup in PM_widgetGrid could be to check if the widget starts with #'Q' instead of 'PM_' #Widgets to include in the widget row. widgetList = [('PM_ToolButton', self.loadSequenceButton, 0), ('PM_ToolButton', self.saveSequenceButton, 1), ('QLabel', " Sequence direction:", 2), ('PM_ComboBox', self.baseDirectionChoiceComboBox , 3), ('QLabel', " Find:", 4), ('PM_LineEdit', self.findLineEdit, 5), ('PM_ToolButton', self.findOptionsToolButton, 6), ('PM_ToolButton', self.findPreviousToolButton, 7), ('PM_ToolButton', self.findNextToolButton, 8), ('QLabel', " Replace:", 9), ('PM_TextEdit', self.replaceLineEdit, 10), ('PM_PushButton', self.replacePushButton, 11), ('PM_Label', self.warningSign, 12), ('PM_Label', self.phraseNotFoundLabel, 13), ('QSpacerItem', 5, 5, 14) ] widgetRow = PM_WidgetRow(self, title = '', widgetList = widgetList, label = "", spanWidth = True ) def _loadTextEditWidget(self): """ Load the SequenceTexteditWidgets. """ self.sequenceTextEdit = \ PM_TextEdit( self, label = " Sequence: ", spanWidth = False, permit_enter_keystroke = False) self.sequenceTextEdit.setCursorWidth(2) self.sequenceTextEdit.setWordWrapMode( QTextOption.WrapAnywhere ) self.sequenceTextEdit.setFixedHeight(20) #The StrandSequence 'Mate' it is a read only etxtedit that shows #the complementary strand sequence. self.sequenceTextEdit_mate = \ PM_TextEdit(self, label = "", spanWidth = False, permit_enter_keystroke = False ) palette = getPalette(None, QPalette.Base, sequenceEditStrandMateBaseColor) self.sequenceTextEdit_mate.setPalette(palette) self.sequenceTextEdit_mate.setFixedHeight(20) self.sequenceTextEdit_mate.setReadOnly(True) self.sequenceTextEdit_mate.setWordWrapMode(QTextOption.WrapAnywhere) #Important to make sure that the horizontal and vertical scrollbars #for these text edits are never displayed. for textEdit in (self.sequenceTextEdit, self.sequenceTextEdit_mate): textEdit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) textEdit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) def _getFindLineEditStyleSheet(self): """ Return the style sheet for the findLineEdit. This sets the following properties only: - background-color This style is set whenever the searchStrig can't be found (sets a light red color background to the lineedit when this happens) @return: The line edit style sheet. @rtype: str """ styleSheet = \ "QLineEdit {\ background-color: rgb(255, 102, 102)\ }" #Not used: # background-color: rgb(217, 255, 216)\ return styleSheet def _setFindOptionsToolButtonMenu(self): """ Sets the menu for the findOptionstoolbutton that appears a small menu button next to the findLineEdit. """ self.findOptionsMenu = QMenu(self.findOptionsToolButton) self.caseSensitiveFindAction = QAction(self.findOptionsToolButton) self.caseSensitiveFindAction.setText('Match Case') self.caseSensitiveFindAction.setCheckable(True) self.caseSensitiveFindAction.setChecked(False) self.findOptionsMenu.addAction(self.caseSensitiveFindAction) self.findOptionsMenu.addSeparator() self.findOptionsToolButton.setMenu(self.findOptionsMenu) def _addToolTipText(self): """ What's Tool Tip text for widgets in this Property Manager. """ from ne1_ui.ToolTipText_for_PropertyManagers import ToolTip_SequenceEditor ToolTip_SequenceEditor(self) def _addWhatsThisText(self): """ What's This text for widgets in this Property Manager. """ from ne1_ui.WhatsThisText_for_PropertyManagers import whatsThis_SequenceEditor whatsThis_SequenceEditor(self)
class DocumentView(QWebView): # {{{ magnification_changed = pyqtSignal(object) DISABLED_BRUSH = QBrush(Qt.lightGray, Qt.Dense5Pattern) def initialize_view(self, debug_javascript=False): self.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform) self.flipper = SlideFlip(self) self.is_auto_repeat_event = False self.debug_javascript = debug_javascript self.shortcuts = Shortcuts(SHORTCUTS, 'shortcuts/viewer') self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self._size_hint = QSize(510, 680) self.initial_pos = 0.0 self.to_bottom = False self.document = Document(self.shortcuts, parent=self, debug_javascript=debug_javascript) self.setPage(self.document) self.inspector = WebInspector(self, self.document) self.manager = None self._reference_mode = False self._ignore_scrollbar_signals = False self.loading_url = None self.loadFinished.connect(self.load_finished) self.connect(self.document, SIGNAL('linkClicked(QUrl)'), self.link_clicked) self.connect(self.document, SIGNAL('linkHovered(QString,QString,QString)'), self.link_hovered) self.connect(self.document, SIGNAL('selectionChanged()'), self.selection_changed) self.connect(self.document, SIGNAL('animated_scroll_done()'), self.animated_scroll_done, Qt.QueuedConnection) self.document.page_turn.connect(self.page_turn_requested) copy_action = self.pageAction(self.document.Copy) copy_action.setIcon(QIcon(I('convert.png'))) d = self.document self.unimplemented_actions = list(map(self.pageAction, [d.DownloadImageToDisk, d.OpenLinkInNewWindow, d.DownloadLinkToDisk, d.OpenImageInNewWindow, d.OpenLink, d.Reload, d.InspectElement])) self.search_online_action = QAction(QIcon(I('search.png')), '', self) self.search_online_action.triggered.connect(self.search_online) self.addAction(self.search_online_action) self.dictionary_action = QAction(QIcon(I('dictionary.png')), _('&Lookup in dictionary'), self) self.dictionary_action.triggered.connect(self.lookup) self.addAction(self.dictionary_action) self.image_popup = ImagePopup(self) self.table_popup = TablePopup(self) self.view_image_action = QAction(QIcon(I('view-image.png')), _('View &image...'), self) self.view_image_action.triggered.connect(self.image_popup) self.view_table_action = QAction(QIcon(I('view.png')), _('View &table...'), self) self.view_table_action.triggered.connect(self.popup_table) self.search_action = QAction(QIcon(I('dictionary.png')), _('&Search for next occurrence'), self) self.search_action.triggered.connect(self.search_next) self.addAction(self.search_action) self.goto_location_action = QAction(_('Go to...'), self) self.goto_location_menu = m = QMenu(self) self.goto_location_actions = a = { 'Next Page': self.next_page, 'Previous Page': self.previous_page, 'Section Top' : partial(self.scroll_to, 0), 'Document Top': self.goto_document_start, 'Section Bottom':partial(self.scroll_to, 1), 'Document Bottom': self.goto_document_end, 'Next Section': self.goto_next_section, 'Previous Section': self.goto_previous_section, } for name, key in [(_('Next Section'), 'Next Section'), (_('Previous Section'), 'Previous Section'), (None, None), (_('Document Start'), 'Document Top'), (_('Document End'), 'Document Bottom'), (None, None), (_('Section Start'), 'Section Top'), (_('Section End'), 'Section Bottom'), (None, None), (_('Next Page'), 'Next Page'), (_('Previous Page'), 'Previous Page')]: if key is None: m.addSeparator() else: m.addAction(name, a[key], self.shortcuts.get_sequences(key)[0]) self.goto_location_action.setMenu(self.goto_location_menu) self.grabGesture(Qt.SwipeGesture) self.restore_fonts_action = QAction(_('Default font size'), self) self.restore_fonts_action.setCheckable(True) self.restore_fonts_action.triggered.connect(self.restore_font_size) def goto_next_section(self, *args): if self.manager is not None: self.manager.goto_next_section() def goto_previous_section(self, *args): if self.manager is not None: self.manager.goto_previous_section() def goto_document_start(self, *args): if self.manager is not None: self.manager.goto_start() def goto_document_end(self, *args): if self.manager is not None: self.manager.goto_end() @property def copy_action(self): return self.pageAction(self.document.Copy) def animated_scroll_done(self): if self.manager is not None: self.manager.scrolled(self.document.scroll_fraction) def reference_mode(self, enable): self._reference_mode = enable self.document.reference_mode(enable) def goto(self, ref): self.document.goto(ref) def goto_bookmark(self, bm): self.document.goto_bookmark(bm) def config(self, parent=None): self.document.do_config(parent) if self.document.in_fullscreen_mode: self.document.switch_to_fullscreen_mode() self.setFocus(Qt.OtherFocusReason) def load_theme(self, theme_id): themes = load_themes() theme = themes[theme_id] opts = config(theme).parse() self.document.apply_settings(opts) if self.document.in_fullscreen_mode: self.document.switch_to_fullscreen_mode() self.setFocus(Qt.OtherFocusReason) def bookmark(self): return self.document.bookmark() def selection_changed(self): if self.manager is not None: self.manager.selection_changed(unicode(self.document.selectedText())) def _selectedText(self): t = unicode(self.selectedText()).strip() if not t: return u'' if len(t) > 40: t = t[:40] + u'...' t = t.replace(u'&', u'&&') return _("S&earch Google for '%s'")%t def popup_table(self): html = self.document.extract_node() self.table_popup(html, QUrl.fromLocalFile(self.last_loaded_path), self.document.font_magnification_step) def contextMenuEvent(self, ev): mf = self.document.mainFrame() r = mf.hitTestContent(ev.pos()) img = r.pixmap() elem = r.element() if elem.isNull(): elem = r.enclosingBlockElement() table = None parent = elem while not parent.isNull(): if (unicode(parent.tagName()) == u'table' or unicode(parent.localName()) == u'table'): table = parent break parent = parent.parent() self.image_popup.current_img = img self.image_popup.current_url = r.imageUrl() menu = self.document.createStandardContextMenu() for action in self.unimplemented_actions: menu.removeAction(action) if not img.isNull(): menu.addAction(self.view_image_action) if table is not None: self.document.mark_element.emit(table) menu.addAction(self.view_table_action) text = self._selectedText() if text and img.isNull(): self.search_online_action.setText(text) for x, sc in (('search_online', 'Search online'), ('dictionary', 'Lookup word'), ('search', 'Next occurrence')): ac = getattr(self, '%s_action' % x) menu.addAction(ac.icon(), '%s [%s]' % (unicode(ac.text()), ','.join(self.shortcuts.get_shortcuts(sc))), ac.trigger) if not text and img.isNull(): menu.addSeparator() if self.manager.action_back.isEnabled(): menu.addAction(self.manager.action_back) if self.manager.action_forward.isEnabled(): menu.addAction(self.manager.action_forward) menu.addAction(self.goto_location_action) if self.manager is not None: menu.addSeparator() menu.addAction(self.manager.action_table_of_contents) menu.addSeparator() menu.addAction(self.manager.action_font_size_larger) self.restore_fonts_action.setChecked(self.multiplier == 1) menu.addAction(self.restore_fonts_action) menu.addAction(self.manager.action_font_size_smaller) menu.addSeparator() menu.addAction(_('Inspect'), self.inspect) if not text and img.isNull() and self.manager is not None: menu.addSeparator() if (not self.document.show_controls or self.document.in_fullscreen_mode) and self.manager is not None: menu.addAction(self.manager.toggle_toolbar_action) menu.addAction(self.manager.action_full_screen) menu.addSeparator() menu.addAction(self.manager.action_quit) for plugin in self.document.all_viewer_plugins: plugin.customize_context_menu(menu, ev, r) menu.exec_(ev.globalPos()) def inspect(self): self.inspector.show() self.inspector.raise_() self.pageAction(self.document.InspectElement).trigger() def lookup(self, *args): if self.manager is not None: t = unicode(self.selectedText()).strip() if t: self.manager.lookup(t.split()[0]) def search_next(self): if self.manager is not None: t = unicode(self.selectedText()).strip() if t: self.manager.search.set_search_string(t) def search_online(self): t = unicode(self.selectedText()).strip() if t: url = 'https://www.google.com/search?q=' + QUrl().toPercentEncoding(t) open_url(QUrl.fromEncoded(url)) def set_manager(self, manager): self.manager = manager self.scrollbar = manager.horizontal_scrollbar self.connect(self.scrollbar, SIGNAL('valueChanged(int)'), self.scroll_horizontally) def scroll_horizontally(self, amount): self.document.scroll_to(y=self.document.ypos, x=amount) @property def scroll_pos(self): return (self.document.ypos, self.document.ypos + self.document.window_height) @property def viewport_rect(self): # (left, top, right, bottom) of the viewport in document co-ordinates # When in paged mode, left and right are the numbers of the columns # at the left edge and *after* the right edge of the viewport d = self.document if d.in_paged_mode: try: l, r = d.column_boundaries except ValueError: l, r = (0, 1) else: l, r = d.xpos, d.xpos + d.window_width return (l, d.ypos, r, d.ypos + d.window_height) def link_hovered(self, link, text, context): link, text = unicode(link), unicode(text) if link: self.setCursor(Qt.PointingHandCursor) else: self.unsetCursor() def link_clicked(self, url): if self.manager is not None: self.manager.link_clicked(url) def sizeHint(self): return self._size_hint @dynamic_property def scroll_fraction(self): def fget(self): return self.document.scroll_fraction def fset(self, val): self.document.scroll_fraction = float(val) return property(fget=fget, fset=fset) @property def hscroll_fraction(self): return self.document.hscroll_fraction @property def content_size(self): return self.document.width, self.document.height @dynamic_property def current_language(self): def fget(self): return self.document.current_language def fset(self, val): self.document.current_language = val return property(fget=fget, fset=fset) def search(self, text, backwards=False): flags = self.document.FindBackward if backwards else self.document.FindFlags(0) found = self.document.findText(text, flags) if found and self.document.in_paged_mode: self.document.javascript('paged_display.snap_to_selection()') return found def path(self): return os.path.abspath(unicode(self.url().toLocalFile())) def load_path(self, path, pos=0.0): self.initial_pos = pos self.last_loaded_path = path def callback(lu): self.loading_url = lu if self.manager is not None: self.manager.load_started() load_html(path, self, codec=getattr(path, 'encoding', 'utf-8'), mime_type=getattr(path, 'mime_type', 'text/html'), pre_load_callback=callback) entries = set() for ie in getattr(path, 'index_entries', []): if ie.start_anchor: entries.add(ie.start_anchor) if ie.end_anchor: entries.add(ie.end_anchor) self.document.index_anchors = entries def initialize_scrollbar(self): if getattr(self, 'scrollbar', None) is not None: if self.document.in_paged_mode: self.scrollbar.setVisible(False) return delta = self.document.width - self.size().width() if delta > 0: self._ignore_scrollbar_signals = True self.scrollbar.blockSignals(True) self.scrollbar.setRange(0, delta) self.scrollbar.setValue(0) self.scrollbar.setSingleStep(1) self.scrollbar.setPageStep(int(delta/10.)) self.scrollbar.setVisible(delta > 0) self.scrollbar.blockSignals(False) self._ignore_scrollbar_signals = False def load_finished(self, ok): if self.loading_url is None: # An <iframe> finished loading return self.loading_url = None self.document.load_javascript_libraries() self.document.after_load(self.last_loaded_path) self._size_hint = self.document.mainFrame().contentsSize() scrolled = False if self.to_bottom: self.to_bottom = False self.initial_pos = 1.0 if self.initial_pos > 0.0: scrolled = True self.scroll_to(self.initial_pos, notify=False) self.initial_pos = 0.0 self.update() self.initialize_scrollbar() self.document.reference_mode(self._reference_mode) if self.manager is not None: spine_index = self.manager.load_finished(bool(ok)) if spine_index > -1: self.document.set_reference_prefix('%d.'%(spine_index+1)) if scrolled: self.manager.scrolled(self.document.scroll_fraction, onload=True) if self.flipper.isVisible(): if self.flipper.running: self.flipper.setVisible(False) else: self.flipper(self.current_page_image(), duration=self.document.page_flip_duration) @classmethod def test_line(cls, img, y): 'Test if line contains pixels of exactly the same color' start = img.pixel(0, y) for i in range(1, img.width()): if img.pixel(i, y) != start: return False return True def current_page_image(self, overlap=-1): if overlap < 0: overlap = self.height() img = QImage(self.width(), overlap, QImage.Format_ARGB32_Premultiplied) painter = QPainter(img) painter.setRenderHints(self.renderHints()) self.document.mainFrame().render(painter, QRegion(0, 0, self.width(), overlap)) painter.end() return img def find_next_blank_line(self, overlap): img = self.current_page_image(overlap) for i in range(overlap-1, -1, -1): if self.test_line(img, i): self.scroll_by(y=i, notify=False) return self.scroll_by(y=overlap) def previous_page(self): if self.flipper.running and not self.is_auto_repeat_event: return if self.loading_url is not None: return epf = self.document.enable_page_flip and not self.is_auto_repeat_event if self.document.in_paged_mode: loc = self.document.javascript( 'paged_display.previous_screen_location()', typ='int') if loc < 0: if self.manager is not None: if epf: self.flipper.initialize(self.current_page_image(), forwards=False) self.manager.previous_document() else: if epf: self.flipper.initialize(self.current_page_image(), forwards=False) self.document.scroll_to(x=loc, y=0) if epf: self.flipper(self.current_page_image(), duration=self.document.page_flip_duration) if self.manager is not None: self.manager.scrolled(self.scroll_fraction) return delta_y = self.document.window_height - 25 if self.document.at_top: if self.manager is not None: self.to_bottom = True if epf: self.flipper.initialize(self.current_page_image(), False) self.manager.previous_document() else: opos = self.document.ypos upper_limit = opos - delta_y if upper_limit < 0: upper_limit = 0 if upper_limit < opos: if epf: self.flipper.initialize(self.current_page_image(), forwards=False) self.document.scroll_to(self.document.xpos, upper_limit) if epf: self.flipper(self.current_page_image(), duration=self.document.page_flip_duration) if self.manager is not None: self.manager.scrolled(self.scroll_fraction) def next_page(self): if self.flipper.running and not self.is_auto_repeat_event: return if self.loading_url is not None: return epf = self.document.enable_page_flip and not self.is_auto_repeat_event if self.document.in_paged_mode: loc = self.document.javascript( 'paged_display.next_screen_location()', typ='int') if loc < 0: if self.manager is not None: if epf: self.flipper.initialize(self.current_page_image()) self.manager.next_document() else: if epf: self.flipper.initialize(self.current_page_image()) self.document.scroll_to(x=loc, y=0) if epf: self.flipper(self.current_page_image(), duration=self.document.page_flip_duration) if self.manager is not None: self.manager.scrolled(self.scroll_fraction) return window_height = self.document.window_height document_height = self.document.height ddelta = document_height - window_height # print '\nWindow height:', window_height # print 'Document height:', self.document.height delta_y = window_height - 25 if self.document.at_bottom or ddelta <= 0: if self.manager is not None: if epf: self.flipper.initialize(self.current_page_image()) self.manager.next_document() elif ddelta < 25: self.scroll_by(y=ddelta) return else: oopos = self.document.ypos # print 'Original position:', oopos self.document.set_bottom_padding(0) opos = self.document.ypos # print 'After set padding=0:', self.document.ypos if opos < oopos: if self.manager is not None: if epf: self.flipper.initialize(self.current_page_image()) self.manager.next_document() return # oheight = self.document.height lower_limit = opos + delta_y # Max value of top y co-ord after scrolling max_y = self.document.height - window_height # The maximum possible top y co-ord if max_y < lower_limit: padding = lower_limit - max_y if padding == window_height: if self.manager is not None: if epf: self.flipper.initialize(self.current_page_image()) self.manager.next_document() return # print 'Setting padding to:', lower_limit - max_y self.document.set_bottom_padding(lower_limit - max_y) if epf: self.flipper.initialize(self.current_page_image()) # print 'Document height:', self.document.height # print 'Height change:', (self.document.height - oheight) max_y = self.document.height - window_height lower_limit = min(max_y, lower_limit) # print 'Scroll to:', lower_limit if lower_limit > opos: self.document.scroll_to(self.document.xpos, lower_limit) actually_scrolled = self.document.ypos - opos # print 'After scroll pos:', self.document.ypos # print 'Scrolled by:', self.document.ypos - opos self.find_next_blank_line(window_height - actually_scrolled) # print 'After blank line pos:', self.document.ypos if epf: self.flipper(self.current_page_image(), duration=self.document.page_flip_duration) if self.manager is not None: self.manager.scrolled(self.scroll_fraction) # print 'After all:', self.document.ypos def page_turn_requested(self, backwards): if backwards: self.previous_page() else: self.next_page() def scroll_by(self, x=0, y=0, notify=True): old_pos = (self.document.xpos if self.document.in_paged_mode else self.document.ypos) self.document.scroll_by(x, y) new_pos = (self.document.xpos if self.document.in_paged_mode else self.document.ypos) if notify and self.manager is not None and new_pos != old_pos: self.manager.scrolled(self.scroll_fraction) def scroll_to(self, pos, notify=True): if self._ignore_scrollbar_signals: return old_pos = (self.document.xpos if self.document.in_paged_mode else self.document.ypos) if self.document.in_paged_mode: if isinstance(pos, basestring): self.document.jump_to_anchor(pos) else: self.document.scroll_fraction = pos else: if isinstance(pos, basestring): self.document.jump_to_anchor(pos) else: if pos >= 1: self.document.scroll_to(0, self.document.height) else: y = int(math.ceil( pos*(self.document.height-self.document.window_height))) self.document.scroll_to(0, y) new_pos = (self.document.xpos if self.document.in_paged_mode else self.document.ypos) if notify and self.manager is not None and new_pos != old_pos: self.manager.scrolled(self.scroll_fraction) @dynamic_property def multiplier(self): def fget(self): return self.zoomFactor() def fset(self, val): self.setZoomFactor(val) self.magnification_changed.emit(val) return property(fget=fget, fset=fset) def magnify_fonts(self, amount=None): if amount is None: amount = self.document.font_magnification_step with self.document.page_position: self.multiplier += amount return self.document.scroll_fraction def shrink_fonts(self, amount=None): if amount is None: amount = self.document.font_magnification_step if self.multiplier >= amount: with self.document.page_position: self.multiplier -= amount return self.document.scroll_fraction def restore_font_size(self): with self.document.page_position: self.multiplier = 1 return self.document.scroll_fraction def changeEvent(self, event): if event.type() == event.EnabledChange: self.update() return QWebView.changeEvent(self, event) def paintEvent(self, event): painter = QPainter(self) painter.setRenderHints(self.renderHints()) self.document.mainFrame().render(painter, event.region()) if not self.isEnabled(): painter.fillRect(event.region().boundingRect(), self.DISABLED_BRUSH) painter.end() def wheelEvent(self, event): mods = event.modifiers() if mods & Qt.CTRL: if self.manager is not None and event.delta() != 0: (self.manager.font_size_larger if event.delta() > 0 else self.manager.font_size_smaller)() return if self.document.in_paged_mode: if abs(event.delta()) < 15: return typ = 'screen' if self.document.wheel_flips_pages else 'col' direction = 'next' if event.delta() < 0 else 'previous' loc = self.document.javascript('paged_display.%s_%s_location()'%( direction, typ), typ='int') if loc > -1: self.document.scroll_to(x=loc, y=0) if self.manager is not None: self.manager.scrolled(self.scroll_fraction) event.accept() elif self.manager is not None: if direction == 'next': self.manager.next_document() else: self.manager.previous_document() event.accept() return if event.delta() < -14: if self.document.wheel_flips_pages: self.next_page() event.accept() return if self.document.at_bottom: self.scroll_by(y=15) # at_bottom can lie on windows if self.manager is not None: self.manager.next_document() event.accept() return elif event.delta() > 14: if self.document.wheel_flips_pages: self.previous_page() event.accept() return if self.document.at_top: if self.manager is not None: self.manager.previous_document() event.accept() return ret = QWebView.wheelEvent(self, event) scroll_amount = (event.delta() / 120.0) * .2 * -1 if event.orientation() == Qt.Vertical: self.scroll_by(0, self.document.viewportSize().height() * scroll_amount) else: self.scroll_by(self.document.viewportSize().width() * scroll_amount, 0) if self.manager is not None: self.manager.scrolled(self.scroll_fraction) return ret def keyPressEvent(self, event): if not self.handle_key_press(event): return QWebView.keyPressEvent(self, event) def paged_col_scroll(self, forward=True, scroll_past_end=True): dir = 'next' if forward else 'previous' loc = self.document.javascript( 'paged_display.%s_col_location()'%dir, typ='int') if loc > -1: self.document.scroll_to(x=loc, y=0) self.manager.scrolled(self.document.scroll_fraction) elif scroll_past_end: (self.manager.next_document() if forward else self.manager.previous_document()) def handle_key_press(self, event): handled = True key = self.shortcuts.get_match(event) func = self.goto_location_actions.get(key, None) if func is not None: self.is_auto_repeat_event = event.isAutoRepeat() try: func() finally: self.is_auto_repeat_event = False elif key == 'Down': if self.document.in_paged_mode: self.paged_col_scroll(scroll_past_end=not self.document.line_scrolling_stops_on_pagebreaks) else: if (not self.document.line_scrolling_stops_on_pagebreaks and self.document.at_bottom): self.manager.next_document() else: self.scroll_by(y=15) elif key == 'Up': if self.document.in_paged_mode: self.paged_col_scroll(forward=False, scroll_past_end=not self.document.line_scrolling_stops_on_pagebreaks) else: if (not self.document.line_scrolling_stops_on_pagebreaks and self.document.at_top): self.manager.previous_document() else: self.scroll_by(y=-15) elif key == 'Left': if self.document.in_paged_mode: self.paged_col_scroll(forward=False) else: self.scroll_by(x=-15) elif key == 'Right': if self.document.in_paged_mode: self.paged_col_scroll() else: self.scroll_by(x=15) elif key == 'Back': if self.manager is not None: self.manager.back(None) elif key == 'Forward': if self.manager is not None: self.manager.forward(None) else: handled = False return handled def resizeEvent(self, event): if self.manager is not None: self.manager.viewport_resize_started(event) return QWebView.resizeEvent(self, event) def event(self, ev): if ev.type() == ev.Gesture: swipe = ev.gesture(Qt.SwipeGesture) if swipe is not None: self.handle_swipe(swipe) return True return QWebView.event(self, ev) def handle_swipe(self, swipe): if swipe.state() == Qt.GestureFinished: if swipe.horizontalDirection() == QSwipeGesture.Left: self.previous_page() elif swipe.horizontalDirection() == QSwipeGesture.Right: self.next_page() elif swipe.verticalDirection() == QSwipeGesture.Up: self.goto_previous_section() elif swipe.horizontalDirection() == QSwipeGesture.Down: self.goto_next_section() def mouseReleaseEvent(self, ev): opos = self.document.ypos ret = QWebView.mouseReleaseEvent(self, ev) if self.manager is not None and opos != self.document.ypos: self.manager.internal_link_clicked(opos) self.manager.scrolled(self.scroll_fraction) return ret
class Ui_ProteinSequenceEditor(PM_DockWidget): """ The Ui_DnaSequenceEditor class defines UI elements for the Sequence Editor object. The sequence editor is usually visible while in DNA edit mode. It is a DockWidget that is doced at the bottom of the MainWindow """ _title = "Sequence Editor" _groupBoxCount = 0 _lastGroupBox = None def __init__(self, win): """ Constructor for the Ui_DnaSequenceEditor @param win: The parentWidget (MainWindow) for the sequence editor """ self.win = win # Should parentWidget for a docwidget always be win? #Not necessary but most likely it will be the case. parentWidget = win _superclass.__init__(self, parentWidget, title = self._title) #A flag used to restore the state of the Reports dock widget #(which can be accessed through View > Reports) see self.show() and #self.closeEvent() for more details. self._reportsDockWidget_closed_in_show_method = False self.setFixedHeight(90) def show(self): """ Shows the sequence editor. While doing this, it also closes the reports dock widget (if visible) the state of the reports dockwidget will be restored when the sequence editor is closed. @see:self.closeEvent() """ self._reportsDockWidget_closed_in_show_method = False if self.win.viewFullScreenAction.isChecked() or \ self.win.viewSemiFullScreenAction.isChecked(): pass else: if self.win.reportsDockWidget.isVisible(): self.win.reportsDockWidget.close() self._reportsDockWidget_closed_in_show_method = True _superclass.show(self) def closeEvent(self, event): """ Overrides close event. Makes sure that the visible state of the reports widgetis restored when the sequence editor is closed. @see: self.show() """ _superclass.closeEvent(self, event) if self.win.viewFullScreenAction.isChecked() or \ self.win.viewSemiFullScreenAction.isChecked(): pass else: if self._reportsDockWidget_closed_in_show_method: self.win.viewReportsAction.setChecked(True) self._reportsDockWidget_closed_in_show_method = False def _loadWidgets(self): """ Overrides PM.PM_DockWidget._loadWidgets. Loads the widget in this dockwidget. """ self._loadMenuWidgets() self._loadTextEditWidget() def _loadMenuWidgets(self): """ Load the various menu widgets (e.g. Open, save sequence options, Find and replace widgets etc. """ #Note: Find and replace widgets might be moved to their own class. self.loadSequenceButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Open.png") self.saveSequenceButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Save_Strand_Sequence.png") self.loadSequenceButton.setAutoRaise(True) self.saveSequenceButton.setAutoRaise(True) #Find and replace widgets -- self.findLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.findLineEdit.setMaximumWidth(60) self.replaceLineEdit = \ PM_LineEdit( self, label = "", spanWidth = False) self.replaceLineEdit.setMaximumWidth(60) self.findOptionsToolButton = PM_ToolButton(self) self.findOptionsToolButton.setMaximumWidth(12) self.findOptionsToolButton.setAutoRaise(True) self.findOptionsToolButton.setPopupMode(QToolButton.MenuButtonPopup) self._setFindOptionsToolButtonMenu() self.findNextToolButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Find_Next.png") self.findNextToolButton.setAutoRaise(True) self.findPreviousToolButton = PM_ToolButton( self, iconPath = "ui/actions/Properties Manager/Find_Previous.png") self.findPreviousToolButton.setAutoRaise(True) self.replacePushButton = PM_PushButton(self, text = "Replace") self.warningSign = QLabel(self) self.warningSign.setPixmap( getpixmap('ui/actions/Properties Manager/Warning.png')) self.warningSign.hide() self.phraseNotFoundLabel = QLabel(self) self.phraseNotFoundLabel.setText("Sequence Not Found") self.phraseNotFoundLabel.hide() #Widgets to include in the widget row. widgetList = [('PM_ToolButton', self.loadSequenceButton, 0), ('PM_ToolButton', self.saveSequenceButton, 1), ('QLabel', " Find:", 4), ('PM_LineEdit', self.findLineEdit, 5), ('PM_ToolButton', self.findOptionsToolButton, 6), ('PM_ToolButton', self.findPreviousToolButton, 7), ('PM_ToolButton', self.findNextToolButton, 8), ('QLabel', " Replace:", 9), ('PM_TextEdit', self.replaceLineEdit, 10), ('PM_PushButton', self.replacePushButton, 11), ('PM_Label', self.warningSign, 12), ('PM_Label', self.phraseNotFoundLabel, 13), ('QSpacerItem', 5, 5, 14) ] widgetRow = PM_WidgetRow(self, title = '', widgetList = widgetList, label = "", spanWidth = True ) def _loadTextEditWidget(self): """ Load the SequenceTexteditWidgets. """ self.aaRulerTextEdit = \ PM_TextEdit( self, label = "", spanWidth = False, permit_enter_keystroke = False) palette = getPalette(None, QPalette.Base, pmGrpBoxColor) self.aaRulerTextEdit.setPalette(palette) self.aaRulerTextEdit.setWordWrapMode( QTextOption.WrapAnywhere ) self.aaRulerTextEdit.setFixedHeight(20) self.aaRulerTextEdit.setReadOnly(True) self.sequenceTextEdit = \ PM_TextEdit( self, label = " Sequence: ", spanWidth = False, permit_enter_keystroke = False) self.sequenceTextEdit.setCursorWidth(2) self.sequenceTextEdit.setWordWrapMode( QTextOption.WrapAnywhere ) self.sequenceTextEdit.setFixedHeight(20) self.secStrucTextEdit = \ PM_TextEdit( self, label = " Secondary structure: ", spanWidth = False, permit_enter_keystroke = False) palette = getPalette(None, QPalette.Base, sequenceEditStrandMateBaseColor) self.secStrucTextEdit.setPalette(palette) self.secStrucTextEdit.setWordWrapMode( QTextOption.WrapAnywhere ) self.secStrucTextEdit.setFixedHeight(20) self.secStrucTextEdit.setReadOnly(True) #Important to make sure that the horizontal and vertical scrollbars #for these text edits are never displayed. self.sequenceTextEdit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.sequenceTextEdit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.secStrucTextEdit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.secStrucTextEdit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.aaRulerTextEdit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.aaRulerTextEdit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) def _getFindLineEditStyleSheet(self): """ Return the style sheet for the findLineEdit. This sets the following properties only: - background-color This style is set whenever the searchStrig can't be found (sets a light red color background to the lineedit when this happens) @return: The line edit style sheet. @rtype: str """ styleSheet = \ "QLineEdit {\ background-color: rgb(255, 102, 102)\ }" #Not used: # background-color: rgb(217, 255, 216)\ return styleSheet def _setFindOptionsToolButtonMenu(self): """ Sets the menu for the findOptionstoolbutton that appears a small menu button next to the findLineEdit. """ self.findOptionsMenu = QMenu(self.findOptionsToolButton) self.caseSensitiveFindAction = QAction(self.findOptionsToolButton) self.caseSensitiveFindAction.setText('Match Case') self.caseSensitiveFindAction.setCheckable(True) self.caseSensitiveFindAction.setChecked(False) self.findOptionsMenu.addAction(self.caseSensitiveFindAction) self.findOptionsMenu.addSeparator() self.findOptionsToolButton.setMenu(self.findOptionsMenu) def _addToolTipText(self): """ What's Tool Tip text for widgets in this Property Manager. """ pass def _addWhatsThisText(self): """ What's This text for widgets in this Property Manager. """ pass
def makemenu_helper(widget, menu_spec, menu=None): """ Make and return a reusable or one-time-use (at caller's option) popup menu whose structure is specified by menu_spec, which is a list of menu item specifiers, each of which is either None (for a separator) or a tuple of the form (menu text, callable or submenu, option1, option2, ...) with 0 or more options (described below). A submenu can be either another menu_spec list, or a QMenu object (but in the latter case the menu text is ignored -- maybe it comes from that QMenu object somehow -- not sure if this was different in Qt3). In either case it is the 2nd menu-item-tuple element, in place of the callable. Otherwise the callable must satisfy the python 'callable' predicate, and is executed if the menu item is chosen, wrapped inside another function which handles Undo checkpointing and Undo-command-name setting. The options in a menu item tuple can be zero or more (in any order, duplicates allowed) of the following: 'disabled' -- the menu item should be disabled; 'checked' -- the menu item will be checked; None -- this option is legal but ignored (but the callable must still satisfy the python predicate "callable"; constants.noop might be useful for that case). The Qt3 version also supported tuple-options consisting of one of the words 'iconset' and 'whatsThis' followed by an appropriate argument, but those have not yet been ported to Qt4 (except possibly for disabled menu items -- UNTESTED). Unrecognized options may or may not generate warnings, and are otherwise ignored. [###FIX that -- they always ought to print a warning to developers. Note that right now they do iff 'disabled' is one of the options and ATOM_DEBUG is set.] The 'widget' argument should be the Qt widget which is using this function to put up a menu. If the menu argument is provided, it should be a QMenu to which we'll add items; otherwise we create our own QMenu and add items to it. """ from utilities.debug import print_compact_traceback import types if menu is None: menu = QMenu(widget) ## menu.show() #bruce 070514 removed menu.show() to fix a cosmetic and performance bug # (on Mac, possibly on other platforms too; probably unreported) # in which the debug menu first appears in screen center, slowly grows # to full size while remaining blank, then moves to its final position # and looks normal (causing a visual glitch, and a 2-3 second delay # in being able to use it). May fix similar issues with other menus. # If this causes harm for some menus or platforms, we can adapt it. # bruce 040909-16 moved this method from basicMode to GLPane, # leaving a delegator for it in basicMode. # (bruce was not the original author, but modified it) #menu = QMenu( widget) for m in menu_spec: try: #bruce 050416 added try/except as debug code and for safety menutext = m and widget.trUtf8(m[0]) if m and isinstance(m[1], QMenu): #bruce 041010 added this case submenu = m[1] #menu.insertItem( menutext, submenu ) menu.addMenu(submenu) # how do I get menutext in there? # (similar code might work for QAction case too, not sure) elif m and isinstance( m[1], types.ListType): #bruce 041103 added this case submenu = QMenu(menutext, menu) submenu = makemenu_helper( widget, m[1], submenu) # [this used to call widget.makemenu] menu.addMenu(submenu) elif m: assert callable(m[1]), \ "%r[1] needs to be a callable" % (m,) #bruce 041103 # transform m[1] into a new callable that makes undo checkpoints and provides an undo command-name # [bruce 060324 for possible bugs in undo noticing cmenu items, and for the cmdnames] func = wrap_callable_for_undo(m[1], cmdname=m[0]) # guess about cmdname, but it might be reasonable for A7 as long as we ensure weird characters won't confuse it import foundation.changes as changes changes.keep_forever( func ) # THIS IS BAD (memory leak), but it's not a severe one, so ok for A7 [bruce 060324] # (note: the hard part about removing these when we no longer need them is knowing when to do that # if the user ends up not selecting anything from the menu. Also, some callers make these # menus for reuse multiple times, and for them we never want to deallocate func even when some # menu command gets used. We could solve both of these by making the caller pass a place to keep these # which it would deallocate someday or which would ensure only one per distinct kind of menu is kept. #e) if 'disabled' not in m[2:]: act = QAction(widget) act.setText(menutext) if 'checked' in m[2:]: act.setCheckable(True) act.setChecked(True) menu.addAction(act) widget.connect(act, SIGNAL("activated()"), func) else: # disabled case # [why is this case done differently, in this Qt4 port?? -- bruce 070522 question] insert_command_into_menu(menu, menutext, func, options=m[2:], raw_command=True) else: menu.addSeparator( ) #bruce 070522 bugfix -- before this, separators were placed lower down or dropped # so as not to come before disabled items, for unknown reasons. # (Speculation: maybe because insertSeparator was used, since addSeparator didn't work or wasn't noticed, # and since disabled item were added by an older function (also for unknown reasons)?) pass except Exception, e: if isinstance(e, SystemExit): raise print_compact_traceback( "exception in makemenu_helper ignored, for %r:\n" % (m, )) #bruce 070522 restored this (was skipped in Qt4 port) pass #e could add a fake menu item here as an error message
class EditorBar(QToolBar): saveDocAsSignal = pyqtSignal() spellSignal = pyqtSignal(bool) whiteSpaceSignal = pyqtSignal(bool) boldSignal = pyqtSignal(bool) italicSignal = pyqtSignal(bool) underlineSignal = pyqtSignal(bool) strikethroughSignal = pyqtSignal(bool) subscriptSignal = pyqtSignal(bool) superscriptSignal = pyqtSignal(bool) def __init__(self, parent = None): QtGui.QToolBar.__init__(self, parent) self.setWindowTitle('EditorBar') self.setIconSize(QSize(16, 16)) self.createActions() def createActions(self): self.settingsAction = QAction(self.tr("Settings"), self) self.settingsAction.setIcon(QtGui.QIcon(":/icons/icons/configure.png")) self.settingsAction.triggered.connect(self.settings) self.addAction(self.settingsAction) self.saveDocAsAction = QAction(self.tr("Save As"), self) self.saveDocAsAction.triggered.connect(self.SaveDocumentAs) self.saveDocAsAction.setIcon(QtGui.QIcon(":/icons/icons/filesave.png")) self.addAction(self.saveDocAsAction) self.spellAction = QAction(self.tr("Spellchecking"), self) self.spellAction.setIcon( QtGui.QIcon(":/icons/icons/tools-check-spelling.png")) self.spellAction.setCheckable(True) self.spellAction.setChecked(settings.get('editor:spell')) self.spellAction.toggled.connect(self.spell) self.insertSeparator(self.spellAction) self.addAction(self.spellAction) self.whiteSpaceAction = QAction(self.tr("Show whitespace"), self) self.whiteSpaceAction.setIcon( QtGui.QIcon(":/icons/icons/whiteSpace.png")) self.whiteSpaceAction.setCheckable(True) self.whiteSpaceAction.setChecked(settings.get('editor:whiteSpace')) self.whiteSpaceAction.toggled.connect(self.whiteSpace) self.addAction(self.whiteSpaceAction) self.BoldAction = QtGui.QAction( QtGui.QIcon(":/icons/icons/format-text-bold.png"), self.tr("&Bold"), self, shortcut=QtCore.Qt.CTRL + QtCore.Qt.Key_B, triggered=self.bold, checkable=True) self.addAction(self.BoldAction) self.ItalicAction = QAction(self.tr("Italic"), self) self.ItalicAction.setIcon( QtGui.QIcon(":/icons/icons/format-text-italic.png")) self.ItalicAction.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_I) self.ItalicAction.setCheckable(True) self.ItalicAction.triggered.connect(self.italic) self.addAction(self.ItalicAction) self.UnderlineAction = QAction(self.tr("Underline"), self) self.UnderlineAction.setIcon( QtGui.QIcon(":/icons/icons/format-text-underline.png")) self.UnderlineAction.setCheckable(True) self.UnderlineAction.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_U) self.UnderlineAction.triggered.connect(self.underline) self.addAction(self.UnderlineAction) self.StrikethroughAction = QAction(self.tr("Strikethrough"), self) self.StrikethroughAction.setIcon( QtGui.QIcon(":/icons/icons/format-text-strikethrough.png")) self.StrikethroughAction.setCheckable(True) self.StrikethroughAction.triggered.connect(self.strikethrough) self.addAction(self.StrikethroughAction) self.SubscriptAction = QAction(self.tr("Subscript"), self) self.SubscriptAction.setIcon( QtGui.QIcon(":/icons/icons/format-text-subscript.png")) self.SubscriptAction.setCheckable(True) self.SubscriptAction.triggered.connect(self.subscript) self.addAction(self.SubscriptAction) self.SuperscriptAction = QAction(self.tr("Superscript"), self) self.SuperscriptAction.setIcon( QtGui.QIcon(":/icons/icons/format-text-superscript.png")) self.SuperscriptAction.setCheckable(True) self.SuperscriptAction.triggered.connect(self.superscript) self.addAction(self.SuperscriptAction) def settings(self): lectorSettings = Settings(self, 1) # QObject.connect(lectorSettings, SIGNAL('accepted()'), # self.updateTextEditor) lectorSettings.settingAccepted.connect(self.resetSpell) lectorSettings.show() def SaveDocumentAs(self): self.saveDocAsSignal.emit() def spell(self): state = self.spellAction.isChecked() self.spellSignal.emit(state) def resetSpell(self): ''' Turn off and on spellcheckig to use correct dictionary ''' state = self.spellAction.isChecked() if state: self.spellSignal.emit(False) self.spellSignal.emit(state) def whiteSpace(self): state = self.whiteSpaceAction.isChecked() self.whiteSpaceSignal.emit(state) def toggleFormat(self, CharFormat): font = CharFormat.font() self.BoldAction.setChecked(font.bold()) self.ItalicAction.setChecked(font.italic()) self.UnderlineAction.setChecked(font.underline()) self.StrikethroughAction.setChecked(CharFormat.fontStrikeOut()) if CharFormat.verticalAlignment() == \ QtGui.QTextCharFormat.AlignSuperScript: self.SubscriptAction.setChecked(False) self.SuperscriptAction.setChecked(True) elif CharFormat.verticalAlignment() == \ QtGui.QTextCharFormat.AlignSubScript: self.SubscriptAction.setChecked(True) self.SuperscriptAction.setChecked(False) else: self.SubscriptAction.setChecked(False) self.SuperscriptAction.setChecked(False) def bold(self): state = self.BoldAction.isChecked() self.boldSignal.emit(state) def italic(self): state = self.ItalicAction.isChecked() self.italicSignal.emit(state) def underline(self): state = self.UnderlineAction.isChecked() self.underlineSignal.emit(state) def strikethrough(self): state = self.StrikethroughAction.isChecked() self.strikethroughSignal.emit(state) def subscript(self): state = self.SubscriptAction.isChecked() self.subscriptSignal.emit(state) def superscript(self): state = self.SuperscriptAction.isChecked() self.superscriptSignal.emit(state)
class EditorBar(QToolBar): saveDocAsSignal = pyqtSignal() spellSignal = pyqtSignal(bool) whiteSpaceSignal = pyqtSignal(bool) boldSignal = pyqtSignal(bool) italicSignal = pyqtSignal(bool) underlineSignal = pyqtSignal(bool) strikethroughSignal = pyqtSignal(bool) subscriptSignal = pyqtSignal(bool) superscriptSignal = pyqtSignal(bool) def __init__(self, parent=None): QtGui.QToolBar.__init__(self, parent) self.setWindowTitle('EditorBar') self.setIconSize(QSize(16, 16)) self.createActions() def createActions(self): self.settingsAction = QAction(self.tr("Settings"), self) self.settingsAction.setIcon(QtGui.QIcon(":/icons/icons/configure.png")) self.settingsAction.triggered.connect(self.settings) self.addAction(self.settingsAction) self.saveDocAsAction = QAction(self.tr("Save As"), self) self.saveDocAsAction.triggered.connect(self.SaveDocumentAs) self.saveDocAsAction.setIcon(QtGui.QIcon(":/icons/icons/filesave.png")) self.addAction(self.saveDocAsAction) self.spellAction = QAction(self.tr("Spellchecking"), self) self.spellAction.setIcon( QtGui.QIcon(":/icons/icons/tools-check-spelling.png")) self.spellAction.setCheckable(True) self.spellAction.setChecked(settings.get('editor:spell')) self.spellAction.toggled.connect(self.spell) self.insertSeparator(self.spellAction) self.addAction(self.spellAction) self.whiteSpaceAction = QAction(self.tr("Show whitespace"), self) self.whiteSpaceAction.setIcon( QtGui.QIcon(":/icons/icons/whiteSpace.png")) self.whiteSpaceAction.setCheckable(True) self.whiteSpaceAction.setChecked(settings.get('editor:whiteSpace')) self.whiteSpaceAction.toggled.connect(self.whiteSpace) self.addAction(self.whiteSpaceAction) self.BoldAction = QtGui.QAction( QtGui.QIcon(":/icons/icons/format-text-bold.png"), self.tr("&Bold"), self, shortcut=QtCore.Qt.CTRL + QtCore.Qt.Key_B, triggered=self.bold, checkable=True) self.addAction(self.BoldAction) self.ItalicAction = QAction(self.tr("Italic"), self) self.ItalicAction.setIcon( QtGui.QIcon(":/icons/icons/format-text-italic.png")) self.ItalicAction.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_I) self.ItalicAction.setCheckable(True) self.ItalicAction.triggered.connect(self.italic) self.addAction(self.ItalicAction) self.UnderlineAction = QAction(self.tr("Underline"), self) self.UnderlineAction.setIcon( QtGui.QIcon(":/icons/icons/format-text-underline.png")) self.UnderlineAction.setCheckable(True) self.UnderlineAction.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_U) self.UnderlineAction.triggered.connect(self.underline) self.addAction(self.UnderlineAction) self.StrikethroughAction = QAction(self.tr("Strikethrough"), self) self.StrikethroughAction.setIcon( QtGui.QIcon(":/icons/icons/format-text-strikethrough.png")) self.StrikethroughAction.setCheckable(True) self.StrikethroughAction.triggered.connect(self.strikethrough) self.addAction(self.StrikethroughAction) self.SubscriptAction = QAction(self.tr("Subscript"), self) self.SubscriptAction.setIcon( QtGui.QIcon(":/icons/icons/format-text-subscript.png")) self.SubscriptAction.setCheckable(True) self.SubscriptAction.triggered.connect(self.subscript) self.addAction(self.SubscriptAction) self.SuperscriptAction = QAction(self.tr("Superscript"), self) self.SuperscriptAction.setIcon( QtGui.QIcon(":/icons/icons/format-text-superscript.png")) self.SuperscriptAction.setCheckable(True) self.SuperscriptAction.triggered.connect(self.superscript) self.addAction(self.SuperscriptAction) def settings(self): lectorSettings = Settings(self, 1) # QObject.connect(lectorSettings, SIGNAL('accepted()'), # self.updateTextEditor) lectorSettings.settingAccepted.connect(self.resetSpell) lectorSettings.show() def SaveDocumentAs(self): self.saveDocAsSignal.emit() def spell(self): state = self.spellAction.isChecked() self.spellSignal.emit(state) def resetSpell(self): ''' Turn off and on spellcheckig to use correct dictionary ''' state = self.spellAction.isChecked() if state: self.spellSignal.emit(False) self.spellSignal.emit(state) def whiteSpace(self): state = self.whiteSpaceAction.isChecked() self.whiteSpaceSignal.emit(state) def toggleFormat(self, CharFormat): font = CharFormat.font() self.BoldAction.setChecked(font.bold()) self.ItalicAction.setChecked(font.italic()) self.UnderlineAction.setChecked(font.underline()) self.StrikethroughAction.setChecked(CharFormat.fontStrikeOut()) if CharFormat.verticalAlignment() == \ QtGui.QTextCharFormat.AlignSuperScript: self.SubscriptAction.setChecked(False) self.SuperscriptAction.setChecked(True) elif CharFormat.verticalAlignment() == \ QtGui.QTextCharFormat.AlignSubScript: self.SubscriptAction.setChecked(True) self.SuperscriptAction.setChecked(False) else: self.SubscriptAction.setChecked(False) self.SuperscriptAction.setChecked(False) def bold(self): state = self.BoldAction.isChecked() self.boldSignal.emit(state) def italic(self): state = self.ItalicAction.isChecked() self.italicSignal.emit(state) def underline(self): state = self.UnderlineAction.isChecked() self.underlineSignal.emit(state) def strikethrough(self): state = self.StrikethroughAction.isChecked() self.strikethroughSignal.emit(state) def subscript(self): state = self.SubscriptAction.isChecked() self.subscriptSignal.emit(state) def superscript(self): state = self.SuperscriptAction.isChecked() self.superscriptSignal.emit(state)
def makemenu_helper(widget, menu_spec, menu = None): """ Make and return a reusable or one-time-use (at caller's option) popup menu whose structure is specified by menu_spec, which is a list of menu item specifiers, each of which is either None (for a separator) or a tuple of the form (menu text, callable or submenu, option1, option2, ...) with 0 or more options (described below). A submenu can be either another menu_spec list, or a QMenu object (but in the latter case the menu text is ignored -- maybe it comes from that QMenu object somehow -- not sure if this was different in Qt3). In either case it is the 2nd menu-item-tuple element, in place of the callable. Otherwise the callable must satisfy the python 'callable' predicate, and is executed if the menu item is chosen, wrapped inside another function which handles Undo checkpointing and Undo-command-name setting. The options in a menu item tuple can be zero or more (in any order, duplicates allowed) of the following: 'disabled' -- the menu item should be disabled; 'checked' -- the menu item will be checked; None -- this option is legal but ignored (but the callable must still satisfy the python predicate "callable"; constants.noop might be useful for that case). The Qt3 version also supported tuple-options consisting of one of the words 'iconset' and 'whatsThis' followed by an appropriate argument, but those have not yet been ported to Qt4 (except possibly for disabled menu items -- UNTESTED). Unrecognized options may or may not generate warnings, and are otherwise ignored. [###FIX that -- they always ought to print a warning to developers. Note that right now they do iff 'disabled' is one of the options and ATOM_DEBUG is set.] The 'widget' argument should be the Qt widget which is using this function to put up a menu. If the menu argument is provided, it should be a QMenu to which we'll add items; otherwise we create our own QMenu and add items to it. """ from utilities.debug import print_compact_traceback import types if menu is None: menu = QMenu(widget) ## menu.show() #bruce 070514 removed menu.show() to fix a cosmetic and performance bug # (on Mac, possibly on other platforms too; probably unreported) # in which the debug menu first appears in screen center, slowly grows # to full size while remaining blank, then moves to its final position # and looks normal (causing a visual glitch, and a 2-3 second delay # in being able to use it). May fix similar issues with other menus. # If this causes harm for some menus or platforms, we can adapt it. # bruce 040909-16 moved this method from basicMode to GLPane, # leaving a delegator for it in basicMode. # (bruce was not the original author, but modified it) #menu = QMenu( widget) for m in menu_spec: try: #bruce 050416 added try/except as debug code and for safety menutext = m and widget.trUtf8(m[0]) if m and isinstance(m[1], QMenu): #bruce 041010 added this case submenu = m[1] #menu.insertItem( menutext, submenu ) menu.addMenu(submenu) # how do I get menutext in there? # (similar code might work for QAction case too, not sure) elif m and isinstance(m[1], types.ListType): #bruce 041103 added this case submenu = QMenu(menutext, menu) submenu = makemenu_helper(widget, m[1], submenu) # [this used to call widget.makemenu] menu.addMenu(submenu) elif m: assert callable(m[1]), \ "%r[1] needs to be a callable" % (m,) #bruce 041103 # transform m[1] into a new callable that makes undo checkpoints and provides an undo command-name # [bruce 060324 for possible bugs in undo noticing cmenu items, and for the cmdnames] func = wrap_callable_for_undo(m[1], cmdname = m[0]) # guess about cmdname, but it might be reasonable for A7 as long as we ensure weird characters won't confuse it import foundation.changes as changes changes.keep_forever(func) # THIS IS BAD (memory leak), but it's not a severe one, so ok for A7 [bruce 060324] # (note: the hard part about removing these when we no longer need them is knowing when to do that # if the user ends up not selecting anything from the menu. Also, some callers make these # menus for reuse multiple times, and for them we never want to deallocate func even when some # menu command gets used. We could solve both of these by making the caller pass a place to keep these # which it would deallocate someday or which would ensure only one per distinct kind of menu is kept. #e) if 'disabled' not in m[2:]: act = QAction(widget) act.setText( menutext) if 'checked' in m[2:]: act.setCheckable(True) act.setChecked(True) menu.addAction(act) widget.connect(act, SIGNAL("activated()"), func) else: # disabled case # [why is this case done differently, in this Qt4 port?? -- bruce 070522 question] insert_command_into_menu(menu, menutext, func, options = m[2:], raw_command = True) else: menu.addSeparator() #bruce 070522 bugfix -- before this, separators were placed lower down or dropped # so as not to come before disabled items, for unknown reasons. # (Speculation: maybe because insertSeparator was used, since addSeparator didn't work or wasn't noticed, # and since disabled item were added by an older function (also for unknown reasons)?) pass except Exception, e: if isinstance(e, SystemExit): raise print_compact_traceback("exception in makemenu_helper ignored, for %r:\n" % (m,) ) #bruce 070522 restored this (was skipped in Qt4 port) pass #e could add a fake menu item here as an error message