def startDrag(self): """ this method is called whenever a user start to drag one of the label """ #D( ALL, "dragging started..." ) v = self._view lp = v.painter() fm = lp.fontmetrics() idx, _ = v.xy2coord( 0, self._startpos.y() ) self._dragidx = idx label = self.model()[idx].label lineheight = v.lineheight() baseline = (lineheight + fm.ascent())/2 width = fm.width(label) hotspot = QtCore.QPoint( width/2, lineheight/2 ) drag = QtGui.QDrag(v) pixmap = QtGui.QPixmap(width, lineheight) p = QtGui.QPainter(pixmap) p.fillRect(pixmap.rect(), QtGui.QColor(191, 191, 127)) p.setPen( QtCore.Qt.white ) p.setFont( lp.font() ) p.drawText(0, baseline, label) p.end() # fill in the data seq = self.model()[idx] mimedata = QtCore.QMimeData() text_data = ">%s\n%s\n" % (seq.label, seq.seq) mimedata.setText( text_data ) mimedata.setData("application/x-fasta", text_data.encode('UTF-8')) drag.setMimeData(mimedata) drag.setPixmap(pixmap) drag.setHotSpot(hotspot) drag.exec_(QtCore.Qt.CopyAction)
def box(self, view): if self._box: return self._box left, _ = self.model().boundaries(self._startpos) _, right = self.model().boundaries(self._endpos) self._box = QtCore.QRect(left, 0, right - left, view._scrollarea.height()) return self._box
def box(self): if self._box: return self._box curpos = self.curpos() if curpos < 0: return None left, right = self._view.model().boundaries(curpos) self._box = QtCore.QRect( left, 0, right - left, self._view._scrollarea.height() ) return self._box
def box(self, view, margin=0): if not self._end: return None idx1, pos1 = self._begin idx2, pos2 = self._end start_corner = (min(idx1, idx2), min(pos1, pos2)) end_corner = (max(idx1, idx2), max(pos1, pos2)) lineheight = view.lineheight() charwidth = view.charwidth() x1 = start_corner[1] * charwidth y1 = start_corner[0] * lineheight x2 = (end_corner[1] + 1) * charwidth y2 = (end_corner[0] + 1) * lineheight return QtCore.QRect(x1, y1, x2 - x1, y2 - y1)
class tracemodel_signals(QtCore.QObject): def __init__(self, parent): super(tracemodel_signals, self).__init__() self._parent = parent updateRange = QtCore.pyqtSignal(int, int) cursorMoved = QtCore.pyqtSignal(int) UpdatePosition = QtCore.pyqtSignal(int, int) TraceUpdated = QtCore.pyqtSignal(int, int) RegionUpdated = QtCore.pyqtSignal(object) ScaleChanged = QtCore.pyqtSignal()
class IVerticalScrollBar(QtWidgets.QScrollBar): def __init__(self, pane, idx): super(IVerticalScrollBar, self).__init__(QtCore.Qt.Vertical) self._idx = idx self.setMinimumHeight(50) self._pane = pane self.valueChanged.connect(self.value_changed) def resizeEvent(self, ev): height = self._pane.height_hint() pagestep = self.height() if self.pageStep() != pagestep: self.setPageStep(pagestep) max_height = max(0, height - pagestep) if self.maximum() != max_height: self.setMaximum(max_height) def slideEvent(self, delta): """ when this vbar needed to be slide for a particular value """ self.setValue(self.value() + delta) # cascading signal def value_changed(self, val): #self.emit( sig.VScrollSplitter, val, self._idx ) self.VScrollSplitter.emit(val, self._idx) def wheelEvent(self, ev): offset = ev.angleDelta().y() / 5 self.setValue(self.value() - offset) ev.accept() def widget(self): return self def setMaximum(self, val): super(IVerticalScrollBar, self).setMaximum(val) ## ## SIGNALS # this signal is emitted when the value of a particular vbar at index idx changes its value # signature: new_value, index VScrollSplitter = QtCore.pyqtSignal(int, int)
def sizeHint(self): return QtCore.QSize(600, 150)
class qt_msasignals(QtCore.QObject): def __init__(self): super(qt_msasignals, self).__init__() RegionUpdated = QtCore.pyqtSignal( object ) # coordinate region update; signature ( QRect ) BlockSelectionUpdated = QtCore.pyqtSignal( object, object ) # block selection update; signature ( (idx1, pos1), (idx2, pos2) ) ColumnSelectionUpdated = QtCore.pyqtSignal( object ) # column selection update; signature( [col1, col2, ...] ) LabelSelectionUpdated = QtCore.pyqtSignal( object ) SelectionUpdated = QtCore.pyqtSignal() # general selection being updated SelectionReset = QtCore.pyqtSignal() # general selection being reset SequenceUpdated = QtCore.pyqtSignal( int, int ) # sequence update; signature: idx, pos ContentUpdated = QtCore.pyqtSignal() # whole content updated CaretMoved = QtCore.pyqtSignal(int, int, object) # caret movement CaretStopped = QtCore.pyqtSignal(object) # caret hidden SequenceTypeChanged = QtCore.pyqtSignal(object)
class BaseFrame(QtWidgets.QFrame): """ BaseFrame This class holds single main pane (usually LabelPane), multiple other panes (mostly SequencePane), and single vertical scrollbar pane within single horizontal splitter. Main pane, where model is kept, is the vscrollbarpane 8-). Obviously, the only thing that will relatively remain the same for the whole evolution of thi software. Each pane can hold its own model so that it would be possible for different pane to have different model, e.g. a TranslatedSequencePane to have a translatedmodel. """ def __init__(self, env, parent=None): super(BaseFrame, self).__init__(parent) self._env = env self._vsplit = self.env().split() self._panes = [] self._vscrollbarpane = None self._layout = None self._splitter = ISplitter(QtCore.Qt.Horizontal) self._block_resize_event = False self.EnvironmentChanged.connect(self.update_environment) def mainpane(self): return self._vscrollbarpane def menupane(self): return self._panes[0] def model(self): return self.mainpane().model() def set_env(self, env): self._env = env def env(self): if not self._env: raise RuntimeError("env has not been set!") return self._env def lineheight(self): return self.env().lineheight() def set_lineheight(self, height): self.env().set_lineheight(height) def vsplit(self): return self._vsplit def set_vsplit(self, n): #print "set split" self._vsplit = n for p in self._panes: p.set_split(n) if self._vscrollbarpane: self._vscrollbarpane.set_split(n) def insert_pane(self, pane, idx=-1): if pane.env() is None: # if no env has been set up for the pane, get from this frame pane.set_env(self.env()) pane.set_mainframe(self) pane.set_split(self.vsplit()) if idx == -1: self._panes.append(pane) self._splitter.addWidget(pane) else: raise NotImplemented( 'inserting pane at arbitrary position is not supported yet') if self._vscrollbarpane: # connect all signal/slot to VScrollbarPane self._vscrollbarpane.connect_pane(pane) # connect the rest of signal/slot pane.ActiveViewChanged.connect(self.set_activeview) def init_layout(self): self._layout = QtWidgets.QHBoxLayout() self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0) self._layout.addWidget(self._splitter, 1) if self._vscrollbarpane: self._layout.addWidget(self._vscrollbarpane, 0) self.setLayout(self._layout) def init_vscrollbarpane(self, pane): self._vscrollbarpane = pane self._vscrollbarpane.set_split(self.vsplit()) # slots def set_activeview(self, view): self.parent().set_activepane(view.pane()) def update_environment(self): for p in self._panes: p.update_environment() ## ## SIGNALS ## These signals will be propagated within this frame ## # EnvironmentChanged is emitted when changes occured in any of environment setting, # such as font resize etc; the object is the environment EnvironmentChanged = QtCore.pyqtSignal()
def draw_single_trace(self, minTp, maxTp, data, pen, p, scaleX, scaleY): p.setPen( pen ) points = [ QtCore.QPoint( x * scaleX, data[x] * scaleY) for x in range(max(0, minTp - 5), min(maxTp+5, len(data))) ] p.drawPolyline( QtGui.QPolygon(points) )
def update_tracepoint(self, start, end): """ start & end is position, not tracepoint """ l, _ = self.model().boundaries(start) _, r = self.model().boundaries(end) self.update_content( QtCore.QRect( l, 0, r-l, self._splitter.widget(0).height()) )
def sizeHint(self): return QtCore.QSize(0, self._pane.footer_height())
def sizeHint(self): return QtCore.QSize(100, 100)
def sizeHint(self): """ provides hint for widget size, usually caled during resizeEvent """ # ask this container pane for calculating width & height of data return QtCore.QSize(self.pane().width_hint(), self.pane().height_hint())
class BasePane(QtWidgets.QFrame): """ BasePane This class holds model, individual env, horizontal splitter, pane-sensitive statusbar and menubar Layout: _layout -> header splitter (n) footer """ have_hscrollbar = True splitter_class = ISplitter #_MenuBar_ = None _ToolBar_ = None _StatusBar_ = None _PaneActions_ = None def __init__(self, model, view_class=None, parent=None): super(BasePane, self).__init__(parent) #self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent) self._model = model self._env = None self._painter = None # class responsible for actual painting/drawing self._hscrollbar = None # horizontal scroll bar self._actions = None # pane-sensitive actions self._menubar = None # pane-sensitive menubar self._statusbar = None # pane-sensitive statusbar self._toolbar = None # pane-sensitive toolbar self._layout = None self._header = None self._footer = None self._view_class = view_class self._mainframe = None self._splitter = self.splitter_class(QtCore.Qt.Vertical) if self._PaneActions_: self.set_custom_actions(self._PaneActions_(self)) # if self._MenuBar_: # self._menubar = self._MenuBar_( self ) # if self._ToolBar_: # self._toolbar = self._ToolBar_( self ) if self._StatusBar_: self.set_statusbar(self._StatusBar_(self)) def model(self): return self._model def env(self): assert self._env return self._env def custom_actions(self): return self._actions def set_custom_actions(self, actions): self._actions = actions def set_env(self, env): self._env = env def layout(self): assert self._layout return self._layout def mainframe(self): return self._mainframe def set_mainframe(self, mainframe): self._mainframe = mainframe def menubar(self): return self._MenuBar_(self) def set_menubar(self, menubar): self._menubar = menubar def statusbar(self): return self._statusbar def set_statusbar(self, statusbar): self._statusbar = statusbar def toolbar(self): return self._toolbar def set_toolbar(self, toolbar): self._toolbar = toolbar def painter(self): return self._painter def set_painter(self, painter=None): self._painter = painter def add_mainwidget(self, w): self._splitter.addWidget(w) def lineheight(self): return self.env().lineheight() def charwidth(self): return self.env().charwidth() def hscrollbar(self): return self._hscrollbar def split(self): return self._splitter.count() def init_hscrollbar(self): """ this function initialize horizontal scrollbar, further setting up of the scrollbar will be performed during resizeEvent by width_hint() """ if self.have_hscrollbar: self._hscrollbar = QtWidgets.QScrollBar(QtCore.Qt.Horizontal) self._layout.addWidget(self._hscrollbar) else: self._layout.addSpacing( QtWidgets.QScrollBar(QtCore.Qt.Horizontal).sizeHint().height()) def init_layout(self, header=None, footer=None): """ initialize the layout; this function needs to be called before everything else """ self._layout = QtWidgets.QVBoxLayout() self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0) self._layout.addWidget(self._splitter, 1) self.setLayout(self._layout) self.init_hscrollbar() if header: self.set_header(header) if footer: self.set_footer(footer) self.init_signals() self.set_painter() # split is set to 1, but will be adjusted when this pane is inserted to frame self.set_split(1) def init_signals(self): """ connect all necessary signals """ if self._hscrollbar: #self._hscrollbar.actionTriggered.connect( self.action_triggered ) self._hscrollbar.valueChanged.connect(self.hslide) self._splitter.splitterMoved.connect(self.splitter_moved) def header(self): return self._header def set_header(self, header): if self._header is not None: self._layout.deleteWidget(0) del self._header self._header = header scrollarea = header.widget() scrollarea.setFixedHeight(header.sizeHint().height()) self._layout.insertWidget(0, scrollarea, 0) def footer(self): return self._footer def set_footer(self, footer): if self._footer is not None: self._layout.deleteWidget(2) del self._footer self._footer = footer scrollarea = footer.widget() scrollarea.setFixedHeight(footer.sizeHint().height()) self._layout.insertWidget(2, scrollarea, 0) def set_split(self, n): count = self._splitter.count() if n > count: for i in range(count, n): # create the view instance, and add its widget (scrollarea) to splitter # as well as the index c = self._view_class(self, i) w = c.widget() self._splitter.addWidget(w) elif n < count: for i in range(count - 1, n - 1, -1): w = self._splitter.widget(i) w.hide() w.setParent(None) w.destroy() del w # SLOTS def action_triggered(self, action): pass def hslide(self, hval): """ Slide all views in this pane manually by setting their respective horizontalScrollBar. This function is not meant to be called directly, only pane's viewable horizontal bar should call this function. """ for i in range(0, self._layout.count() - 1): w = self._layout.itemAt(i).widget() if w == self._splitter: for j in range(0, self._splitter.count()): self._splitter.widget(j).horizontalScrollBar().setValue( hval) else: w.horizontalScrollBar().setValue(hval) def move_content_splitter(self, pos, idx, pane): """ only move our content splitter at particular idx to pos when we are not the sender, ie. not the one receiving focus/mouse movement """ # XXX: refactor by using self.sender, eg: if self.sender() == self: if pane != self: self._splitter.blockSignals(True) self._splitter.moveSplitter(pos, idx) self._splitter.blockSignals(False) def splitter_moved(self, pos, idx): self.ContentSplitterMoved.emit(pos, idx, self) def update_view(self): for i in range(0, self._splitter.count()): w = self._splitter.widget(i) w.widget().update() if self.header(): self.header().update() if self.footer(): self.footer().update() def update_content(self, rect): if rect: #print rect.size() for i in range(0, self._splitter.count()): #print self._splitter.widget(i).widget() self._splitter.widget(i).widget().update(rect) def update_scale(self): for i in range(0, self._splitter.count()): self._splitter.widget(i).widget().update_scale() def vscroll_splitter(self, val, idx): """ vertically scroll a particular view within splitter; idx -> view index, val -> new value """ self._splitter.widget(idx).verticalScrollBar().setValue(val) def focus_pane(self): self._splitter.widget(0).widget().setFocus() def setFocus(self, reason=None): if reason: super(BasePane, self).setFocus(reason) else: super(BasePane, self).setFocus() self.ActiveViewChanged.emit(self._splitter.widget(0).widget()) def update_environment(self): self.set_painter() # HELPERS def width_hint(self): """ this function is mostly called by view's sizeHint when view is triggered with resizeEvent, must be implemented by deriving class """ raise NotImplemented( 'width_hint() must be provided by class derived from BasePane') def length_hint(self): raise NotImplemented('use width_hint() instead of length_hint()') def height_hint(self): """ calculate the height for displaying all data in view; warning: this function seems to be called excessively, probably need to provide a caching-based solution """ return (len(self._model) + 2) * self.lineheight() def header_height(self): """ this function gets the synchronized height of all headers in all panes """ # XXX: for now, just return a number return 30 ## ## SIGNALS ## These signals will be propagated within this pane ## # vscrollsplitter is emitted when vertical scroll bar in VScrollbarPane is moved # signature: ? VScrollSplitter = QtCore.pyqtSignal(int, int) # ContentWheelEvent is emitted when mouse wheel event occured in this pane # signature: ? ContentWheelEvent = QtCore.pyqtSignal(object, int) # ContentSlideEvent is emitted when a view needs to be vertically scrolled # signature: delta_slide, splitter_index ContentSlideEvent = QtCore.pyqtSignal(int, int) # ContentSplitterMoved is emitted when a splitter with idx is moved # signature: pos, splitter_idx, sender_pane ContentSplitterMoved = QtCore.pyqtSignal(int, int, object) # ActiveViewChanged is emitted when a particular view gets a focus ActiveViewChanged = QtCore.pyqtSignal(object) # CursorMoved is emitted when caret / cursor is moved # signature: idx, pos CursorMoved = QtCore.pyqtSignal(int, int) HorizontalCursorMoved = QtCore.pyqtSignal(int) VerticalCursorMoved = QtCore.pyqtSignal(int) # update screen of view ContentUpdated = QtCore.pyqtSignal(object) @classmethod def set_menuclass(cls, impl): cls._MenuBar_ = impl @classmethod def set_statusbarclass(cls, impl): cls._StatusBar_ = impl @classmethod def set_paneactionclass(cls, impl): cls._PaneActions_ = impl
def sizeHint(self): #D(ALL, "ruler height: %d" % self.pane().header_height()) return QtCore.QSize(self.pane().width_hint(), self.pane().header_height())
def sizeHint(self): return QtCore.QSize(800, 600)