class MapTip(QDockWidget): closed = pyqtSignal() def __init__(self, iface: QgisInterface, html: str, point: QgsPointXY): super().__init__() self.map_canvas = iface.mapCanvas() self.point = point self.web_view = QWebView(self) self.dbg_info('map position: {}'.format(point.asWkt())) self.web_view.page().setLinkDelegationPolicy( QWebPage.DelegateAllLinks) # Handle link clicks by yourself self.web_view.setContextMenuPolicy( Qt.NoContextMenu ) # No context menu is allowed if you don't need it self.web_view.linkClicked.connect(self.on_link_clicked) self.web_view.page().settings().setAttribute( QWebSettings.DeveloperExtrasEnabled, True) self.web_view.page().settings().setAttribute( QWebSettings.JavascriptEnabled, True) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWidget(self.web_view) # assure the map tip is never larger than half the map canvas max_width = int(self.map_canvas.geometry().width() / 1.8) max_height = int(self.map_canvas.geometry().height() / 1.8) self.dbg_info('max size {} {}'.format(max_height, max_width)) self.setMaximumSize(max_width, max_height) # start with 0 size, # the content will automatically make it grow up to MaximumSize self.resize(300, 200) background_color = self.palette().base().color() background_color.setAlpha(235) stroke_color = self.palette().shadow().color() #self.setStyleSheet(".QDocWidget{{ border: 1px solid {stroke}; background-color: {bg} }}" # .format(stroke=stroke_color.name(QColor.HexArgb), # bg=background_color.name(QColor.HexArgb))) palette = self.web_view.palette() palette.setBrush(QPalette.Base, Qt.transparent) palette.setBrush(QPalette.Base, background_color) self.web_view.page().setPalette(palette) self.web_view.setAttribute(Qt.WA_OpaquePaintEvent, False) body_style = "background-color: {bg}; margin: 0".format( bg=background_color) container_style = "display: inline-block; margin: 0px" body_html = "<html><body style='{body_style}'>" \ "<div id='QgsWebViewContainer' style='{container_style}'>{html}</div><" \ "/body></html>".format(body_style=body_style, container_style=container_style, html=html) self.web_view.setHtml(body_html) scrollbar_width = self.web_view.page().mainFrame().scrollBarGeometry( Qt.Vertical).width() scrollbar_height = self.web_view.page().mainFrame().scrollBarGeometry( Qt.Horizontal).height() if scrollbar_width > 0 or scrollbar_height > 0: # Get the content size container = self.web_view.page().mainFrame().findFirstElement( "#QgsWebViewContainer") width = container.geometry().width() + 25 + scrollbar_width height = container.geometry().height() + 25 + scrollbar_height #self.resize(width, height) iface.addDockWidget(Qt.RightDockWidgetArea, self) self.setFeatures(QDockWidget.AllDockWidgetFeatures) self.setFloating(True) self.setWindowOpacity(0.9) self.move_to_point() def move_to_point(self): pixel_position = self.map_canvas.mapSettings().mapToPixel().transform( self.point) pixel_position = self.map_canvas.mapToGlobal( QPoint(pixel_position.x(), pixel_position.y())) self.move(pixel_position.x() + 10, pixel_position.y() + 10) def on_link_clicked(self, url): QDesktopServices.openUrl(url) def closeEvent(self, event: QCloseEvent): self.closed.emit() def info(self, msg="", level=Qgis.Info): QgsMessageLog.logMessage('{} {}'.format(self.__class__.__name__, msg), 'Locator bar', level) def dbg_info(self, msg=""): if DEBUG: self.info(msg)
class HostWindow(QMainWindow): # signals SIGTERM = pyqtSignal() SIGUSR1 = pyqtSignal() # -------------------------------------------------------------------------------------------------------- def __init__(self): QMainWindow.__init__(self) gCarla.gui = self URI = sys.argv[1] # ---------------------------------------------------------------------------------------------------- # Internal stuff self.fCurrentFrame = None self.fDocElemement = None self.fCanSetValues = False self.fNeedsShow = False self.fSizeSetup = False self.fQuitReceived = False self.fWasRepainted = False self.fPlugin = get_plugin_info(URI) self.fPorts = self.fPlugin['ports'] self.fPortSymbols = {} self.fPortValues = {} for port in self.fPorts['control']['input'] + self.fPorts['control']['output']: self.fPortSymbols[port['index']] = port['symbol'] self.fPortValues [port['index']] = port['ranges']['default'] # ---------------------------------------------------------------------------------------------------- # Init pipe if len(sys.argv) == 7: self.fPipeClient = gCarla.utils.pipe_client_new(lambda s,msg: self.msgCallback(msg)) else: self.fPipeClient = None # ---------------------------------------------------------------------------------------------------- # Init Web server self.fWebServerThread = WebServerThread(self) self.fWebServerThread.start() # ---------------------------------------------------------------------------------------------------- # Set up GUI self.setContentsMargins(0, 0, 0, 0) self.fWebview = QWebView(self) #self.fWebview.setAttribute(Qt.WA_OpaquePaintEvent, False) #self.fWebview.setAttribute(Qt.WA_TranslucentBackground, True) self.setCentralWidget(self.fWebview) page = self.fWebview.page() page.setViewportSize(QSize(980, 600)) mainFrame = page.mainFrame() mainFrame.setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff) mainFrame.setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff) palette = self.fWebview.palette() palette.setBrush(QPalette.Base, palette.brush(QPalette.Window)) page.setPalette(palette) self.fWebview.setPalette(palette) settings = self.fWebview.settings() settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True) self.fWebview.loadFinished.connect(self.slot_webviewLoadFinished) url = "http://127.0.0.1:%s/icon.html#%s" % (PORT, URI) print("url:", url) self.fWebview.load(QUrl(url)) # ---------------------------------------------------------------------------------------------------- # Connect actions to functions self.SIGTERM.connect(self.slot_handleSIGTERM) # ---------------------------------------------------------------------------------------------------- # Final setup self.fIdleTimer = self.startTimer(30) if self.fPipeClient is None: # testing, show UI only self.setWindowTitle("TestUI") self.fNeedsShow = True # -------------------------------------------------------------------------------------------------------- def closeExternalUI(self): self.fWebServerThread.stopWait() if self.fPipeClient is None: return if not self.fQuitReceived: self.send(["exiting"]) gCarla.utils.pipe_client_destroy(self.fPipeClient) self.fPipeClient = None def idleStuff(self): if self.fPipeClient is not None: gCarla.utils.pipe_client_idle(self.fPipeClient) self.checkForRepaintChanges() if self.fSizeSetup: return if self.fDocElemement is None or self.fDocElemement.isNull(): return pedal = self.fDocElemement.findFirst(".mod-pedal") if pedal.isNull(): return size = pedal.geometry().size() if size.width() <= 10 or size.height() <= 10: return # render web frame to image image = QImage(self.fWebview.page().viewportSize(), QImage.Format_ARGB32_Premultiplied) image.fill(Qt.transparent) painter = QPainter(image) self.fCurrentFrame.render(painter) painter.end() #image.save("/tmp/test.png") # get coordinates and size from image #x = -1 #y = -1 #lastx = -1 #lasty = -1 #bgcol = self.fHostColor.rgba() #for h in range(0, image.height()): #hasNonTransPixels = False #for w in range(0, image.width()): #if image.pixel(w, h) not in (0, bgcol): # 0xff070707): #hasNonTransPixels = True #if x == -1 or x > w: #x = w #lastx = max(lastx, w) #if hasNonTransPixels: ##if y == -1: ##y = h #lasty = h # set size and position accordingly #if -1 not in (x, lastx, lasty): #self.setFixedSize(lastx-x, lasty) #self.fCurrentFrame.setScrollPosition(QPoint(x, 0)) #else: # TODO that^ needs work if True: self.setFixedSize(size) # set initial values self.fCurrentFrame.evaluateJavaScript("icongui.setPortValue(':bypass', 0, null)") for index in self.fPortValues.keys(): symbol = self.fPortSymbols[index] value = self.fPortValues[index] self.fCurrentFrame.evaluateJavaScript("icongui.setPortValue('%s', %f, null)" % (symbol, value)) # final setup self.fCanSetValues = True self.fSizeSetup = True self.fDocElemement = None if self.fNeedsShow: self.show() def checkForRepaintChanges(self): if not self.fWasRepainted: return self.fWasRepainted = False if not self.fCanSetValues: return for index in self.fPortValues.keys(): symbol = self.fPortSymbols[index] oldValue = self.fPortValues[index] newValue = self.fCurrentFrame.evaluateJavaScript("icongui.getPortValue('%s')" % (symbol,)) if oldValue != newValue: self.fPortValues[index] = newValue self.send(["control", index, newValue]) # -------------------------------------------------------------------------------------------------------- @pyqtSlot(bool) def slot_webviewLoadFinished(self, ok): page = self.fWebview.page() page.repaintRequested.connect(self.slot_repaintRequested) self.fCurrentFrame = page.currentFrame() self.fDocElemement = self.fCurrentFrame.documentElement() def slot_repaintRequested(self): if self.fCanSetValues: self.fWasRepainted = True # -------------------------------------------------------------------------------------------------------- # Callback def msgCallback(self, msg): msg = charPtrToString(msg) if msg == "control": index = int(self.readlineblock()) value = float(self.readlineblock()) self.dspParameterChanged(index, value) elif msg == "program": index = int(self.readlineblock()) self.dspProgramChanged(index) elif msg == "midiprogram": bank = int(self.readlineblock()) program = float(self.readlineblock()) self.dspMidiProgramChanged(bank, program) elif msg == "configure": key = self.readlineblock() value = self.readlineblock() self.dspStateChanged(key, value) elif msg == "note": onOff = bool(self.readlineblock() == "true") channel = int(self.readlineblock()) note = int(self.readlineblock()) velocity = int(self.readlineblock()) self.dspNoteReceived(onOff, channel, note, velocity) elif msg == "atom": index = int(self.readlineblock()) size = int(self.readlineblock()) base64atom = self.readlineblock() # nothing to do yet elif msg == "urid": urid = int(self.readlineblock()) uri = self.readlineblock() # nothing to do yet elif msg == "uiOptions": sampleRate = float(self.readlineblock()) useTheme = bool(self.readlineblock() == "true") useThemeColors = bool(self.readlineblock() == "true") windowTitle = self.readlineblock() transWindowId = int(self.readlineblock()) self.uiTitleChanged(windowTitle) elif msg == "show": self.uiShow() elif msg == "focus": self.uiFocus() elif msg == "hide": self.uiHide() elif msg == "quit": self.fQuitReceived = True self.uiQuit() elif msg == "uiTitle": uiTitle = self.readlineblock() self.uiTitleChanged(uiTitle) else: print("unknown message: \"" + msg + "\"") # -------------------------------------------------------------------------------------------------------- def dspParameterChanged(self, index, value): self.fPortValues[index] = value if self.fCurrentFrame is not None and self.fCanSetValues: symbol = self.fPortSymbols[index] self.fCurrentFrame.evaluateJavaScript("icongui.setPortValue('%s', %f, null)" % (symbol, value)) def dspProgramChanged(self, index): return def dspMidiProgramChanged(self, bank, program): return def dspStateChanged(self, key, value): return def dspNoteReceived(self, onOff, channel, note, velocity): return # -------------------------------------------------------------------------------------------------------- def uiShow(self): if self.fSizeSetup: self.show() else: self.fNeedsShow = True def uiFocus(self): if not self.fSizeSetup: return self.setWindowState((self.windowState() & ~Qt.WindowMinimized) | Qt.WindowActive) self.show() self.raise_() self.activateWindow() def uiHide(self): self.hide() def uiQuit(self): self.closeExternalUI() self.close() app.quit() def uiTitleChanged(self, uiTitle): self.setWindowTitle(uiTitle) # -------------------------------------------------------------------------------------------------------- # Qt events def closeEvent(self, event): self.closeExternalUI() QMainWindow.closeEvent(self, event) # there might be other qt windows open which will block carla-modgui from quitting app.quit() def timerEvent(self, event): if event.timerId() == self.fIdleTimer: self.idleStuff() QMainWindow.timerEvent(self, event) # -------------------------------------------------------------------------------------------------------- @pyqtSlot() def slot_handleSIGTERM(self): print("Got SIGTERM -> Closing now") self.close() # -------------------------------------------------------------------------------------------------------- # Internal stuff def readlineblock(self): if self.fPipeClient is None: return "" return gCarla.utils.pipe_client_readlineblock(self.fPipeClient, 5000) def send(self, lines): if self.fPipeClient is None or len(lines) == 0: return gCarla.utils.pipe_client_lock(self.fPipeClient) # this must never fail, we need to unlock at the end try: for line in lines: if line is None: line2 = "(null)" elif isinstance(line, str): line2 = line.replace("\n", "\r") elif isinstance(line, bool): line2 = "true" if line else "false" elif isinstance(line, int): line2 = "%i" % line elif isinstance(line, float): line2 = "%.10f" % line else: print("unknown data type to send:", type(line)) return gCarla.utils.pipe_client_write_msg(self.fPipeClient, line2 + "\n") except: pass gCarla.utils.pipe_client_flush_and_unlock(self.fPipeClient)
class HostWindow(QMainWindow): # signals SIGTERM = pyqtSignal() SIGUSR1 = pyqtSignal() # -------------------------------------------------------------------------------------------------------- def __init__(self): QMainWindow.__init__(self) gCarla.gui = self URI = sys.argv[1] # ---------------------------------------------------------------------------------------------------- # Internal stuff self.fCurrentFrame = None self.fDocElemement = None self.fCanSetValues = False self.fNeedsShow = False self.fSizeSetup = False self.fQuitReceived = False self.fWasRepainted = False self.fPlugin = get_plugin_info(URI) self.fPorts = self.fPlugin['ports'] self.fPortSymbols = {} self.fPortValues = {} for port in self.fPorts['control']['input'] + self.fPorts['control'][ 'output']: self.fPortSymbols[port['index']] = port['symbol'] self.fPortValues[port['index']] = port['ranges']['default'] # ---------------------------------------------------------------------------------------------------- # Init pipe if len(sys.argv) == 7: self.fPipeClient = gCarla.utils.pipe_client_new( lambda s, msg: self.msgCallback(msg)) else: self.fPipeClient = None # ---------------------------------------------------------------------------------------------------- # Init Web server self.fWebServerThread = WebServerThread(self) self.fWebServerThread.start() # ---------------------------------------------------------------------------------------------------- # Set up GUI self.setContentsMargins(0, 0, 0, 0) self.fWebview = QWebView(self) #self.fWebview.setAttribute(Qt.WA_OpaquePaintEvent, False) #self.fWebview.setAttribute(Qt.WA_TranslucentBackground, True) self.setCentralWidget(self.fWebview) page = self.fWebview.page() page.setViewportSize(QSize(980, 600)) mainFrame = page.mainFrame() mainFrame.setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff) mainFrame.setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff) palette = self.fWebview.palette() palette.setBrush(QPalette.Base, palette.brush(QPalette.Window)) page.setPalette(palette) self.fWebview.setPalette(palette) settings = self.fWebview.settings() settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True) self.fWebview.loadFinished.connect(self.slot_webviewLoadFinished) url = "http://127.0.0.1:%s/icon.html#%s" % (PORT, URI) print("url:", url) self.fWebview.load(QUrl(url)) # ---------------------------------------------------------------------------------------------------- # Connect actions to functions self.SIGTERM.connect(self.slot_handleSIGTERM) # ---------------------------------------------------------------------------------------------------- # Final setup self.fIdleTimer = self.startTimer(30) if self.fPipeClient is None: # testing, show UI only self.setWindowTitle("TestUI") self.fNeedsShow = True # -------------------------------------------------------------------------------------------------------- def closeExternalUI(self): self.fWebServerThread.stopWait() if self.fPipeClient is None: return if not self.fQuitReceived: self.send(["exiting"]) gCarla.utils.pipe_client_destroy(self.fPipeClient) self.fPipeClient = None def idleStuff(self): if self.fPipeClient is not None: gCarla.utils.pipe_client_idle(self.fPipeClient) self.checkForRepaintChanges() if self.fSizeSetup: return if self.fDocElemement is None or self.fDocElemement.isNull(): return pedal = self.fDocElemement.findFirst(".mod-pedal") if pedal.isNull(): return size = pedal.geometry().size() if size.width() <= 10 or size.height() <= 10: return # render web frame to image image = QImage(self.fWebview.page().viewportSize(), QImage.Format_ARGB32_Premultiplied) image.fill(Qt.transparent) painter = QPainter(image) self.fCurrentFrame.render(painter) painter.end() #image.save("/tmp/test.png") # get coordinates and size from image #x = -1 #y = -1 #lastx = -1 #lasty = -1 #bgcol = self.fHostColor.rgba() #for h in range(0, image.height()): #hasNonTransPixels = False #for w in range(0, image.width()): #if image.pixel(w, h) not in (0, bgcol): # 0xff070707): #hasNonTransPixels = True #if x == -1 or x > w: #x = w #lastx = max(lastx, w) #if hasNonTransPixels: ##if y == -1: ##y = h #lasty = h # set size and position accordingly #if -1 not in (x, lastx, lasty): #self.setFixedSize(lastx-x, lasty) #self.fCurrentFrame.setScrollPosition(QPoint(x, 0)) #else: # TODO that^ needs work if True: self.setFixedSize(size) # set initial values for index in self.fPortValues.keys(): symbol = self.fPortSymbols[index] value = self.fPortValues[index] self.fCurrentFrame.evaluateJavaScript( "icongui.setPortValue('%s', %f, null)" % (symbol, value)) # final setup self.fCanSetValues = True self.fSizeSetup = True self.fDocElemement = None if self.fNeedsShow: self.show() def checkForRepaintChanges(self): if not self.fWasRepainted: return self.fWasRepainted = False if not self.fCanSetValues: return for index in self.fPortValues.keys(): symbol = self.fPortSymbols[index] oldValue = self.fPortValues[index] newValue = self.fCurrentFrame.evaluateJavaScript( "icongui.getPortValue('%s')" % (symbol, )) if oldValue != newValue: self.fPortValues[index] = newValue self.send(["control", index, newValue]) # -------------------------------------------------------------------------------------------------------- @pyqtSlot(bool) def slot_webviewLoadFinished(self, ok): page = self.fWebview.page() page.repaintRequested.connect(self.slot_repaintRequested) self.fCurrentFrame = page.currentFrame() self.fDocElemement = self.fCurrentFrame.documentElement() def slot_repaintRequested(self): if self.fCanSetValues: self.fWasRepainted = True # -------------------------------------------------------------------------------------------------------- # Callback def msgCallback(self, msg): msg = charPtrToString(msg) if msg == "control": index = int(self.readlineblock()) value = float(self.readlineblock()) self.dspParameterChanged(index, value) elif msg == "program": index = int(self.readlineblock()) self.dspProgramChanged(index) elif msg == "midiprogram": bank = int(self.readlineblock()) program = float(self.readlineblock()) self.dspMidiProgramChanged(bank, program) elif msg == "configure": key = self.readlineblock() value = self.readlineblock() self.dspStateChanged(key, value) elif msg == "note": onOff = bool(self.readlineblock() == "true") channel = int(self.readlineblock()) note = int(self.readlineblock()) velocity = int(self.readlineblock()) self.dspNoteReceived(onOff, channel, note, velocity) elif msg == "atom": index = int(self.readlineblock()) size = int(self.readlineblock()) base64atom = self.readlineblock() # nothing to do yet elif msg == "urid": urid = int(self.readlineblock()) uri = self.readlineblock() # nothing to do yet elif msg == "uiOptions": sampleRate = float(self.readlineblock()) useTheme = bool(self.readlineblock() == "true") useThemeColors = bool(self.readlineblock() == "true") windowTitle = self.readlineblock() transWindowId = int(self.readlineblock()) self.uiTitleChanged(windowTitle) elif msg == "show": self.uiShow() elif msg == "focus": self.uiFocus() elif msg == "hide": self.uiHide() elif msg == "quit": self.fQuitReceived = True self.uiQuit() elif msg == "uiTitle": uiTitle = self.readlineblock() self.uiTitleChanged(uiTitle) else: print("unknown message: \"" + msg + "\"") # -------------------------------------------------------------------------------------------------------- def dspParameterChanged(self, index, value): self.fPortValues[index] = value if self.fCurrentFrame is not None and self.fCanSetValues: symbol = self.fPortSymbols[index] self.fCurrentFrame.evaluateJavaScript( "icongui.setPortValue('%s', %f, null)" % (symbol, value)) def dspProgramChanged(self, index): return def dspMidiProgramChanged(self, bank, program): return def dspStateChanged(self, key, value): return def dspNoteReceived(self, onOff, channel, note, velocity): return # -------------------------------------------------------------------------------------------------------- def uiShow(self): if self.fSizeSetup: self.show() else: self.fNeedsShow = True def uiFocus(self): if not self.fSizeSetup: return self.setWindowState((self.windowState() & ~Qt.WindowMinimized) | Qt.WindowActive) self.show() self.raise_() self.activateWindow() def uiHide(self): self.hide() def uiQuit(self): self.closeExternalUI() self.close() app.quit() def uiTitleChanged(self, uiTitle): self.setWindowTitle(uiTitle) # -------------------------------------------------------------------------------------------------------- # Qt events def closeEvent(self, event): self.closeExternalUI() QMainWindow.closeEvent(self, event) # there might be other qt windows open which will block carla-modgui from quitting app.quit() def timerEvent(self, event): if event.timerId() == self.fIdleTimer: self.idleStuff() QMainWindow.timerEvent(self, event) # -------------------------------------------------------------------------------------------------------- @pyqtSlot() def slot_handleSIGTERM(self): print("Got SIGTERM -> Closing now") self.close() # -------------------------------------------------------------------------------------------------------- # Internal stuff def readlineblock(self): if self.fPipeClient is None: return "" return gCarla.utils.pipe_client_readlineblock(self.fPipeClient, 5000) def send(self, lines): if self.fPipeClient is None or len(lines) == 0: return gCarla.utils.pipe_client_lock(self.fPipeClient) # this must never fail, we need to unlock at the end try: for line in lines: if line is None: line2 = "(null)" elif isinstance(line, str): line2 = line.replace("\n", "\r") elif isinstance(line, bool): line2 = "true" if line else "false" elif isinstance(line, int): line2 = "%i" % line elif isinstance(line, float): line2 = "%.10f" % line else: print("unknown data type to send:", type(line)) return gCarla.utils.pipe_client_write_msg(self.fPipeClient, line2 + "\n") except: pass gCarla.utils.pipe_client_flush_and_unlock(self.fPipeClient)
class BookInfo(QDialog): closed = pyqtSignal(object) def __init__(self, parent, view, row, link_delegate): QDialog.__init__(self, parent) self.normal_brush = QBrush(Qt.white) self.marked_brush = QBrush(Qt.lightGray) self.marked = None self.gui = parent self.splitter = QSplitter(self) self._l = l = QVBoxLayout(self) self.setLayout(l) l.addWidget(self.splitter) self.cover = CoverView(self) self.cover.resizeEvent = self.cover_view_resized self.cover.cover_changed.connect(self.cover_changed) self.cover_pixmap = None self.cover.sizeHint = self.details_size_hint self.splitter.addWidget(self.cover) self.details = QWebView(self) self.details.sizeHint = self.details_size_hint self.details.page().setLinkDelegationPolicy( self.details.page().DelegateAllLinks) self.details.linkClicked.connect(self.link_clicked) s = self.details.page().settings() s.setAttribute(s.JavascriptEnabled, False) self.css = css() self.link_delegate = link_delegate self.details.setAttribute(Qt.WA_OpaquePaintEvent, False) palette = self.details.palette() self.details.setAcceptDrops(False) palette.setBrush(QPalette.Base, Qt.transparent) self.details.page().setPalette(palette) self.c = QWidget(self) self.c.l = l2 = QGridLayout(self.c) self.c.setLayout(l2) l2.addWidget(self.details, 0, 0, 1, -1) self.splitter.addWidget(self.c) self.fit_cover = QCheckBox(_('Fit &cover within view'), self) self.fit_cover.setChecked( gprefs.get('book_info_dialog_fit_cover', True)) l2.addWidget(self.fit_cover, l2.rowCount(), 0, 1, -1) self.previous_button = QPushButton(QIcon(I('previous.png')), _('&Previous'), self) self.previous_button.clicked.connect(self.previous) l2.addWidget(self.previous_button, l2.rowCount(), 0) self.next_button = QPushButton(QIcon(I('next.png')), _('&Next'), self) self.next_button.clicked.connect(self.next) l2.addWidget(self.next_button, l2.rowCount() - 1, 1) self.view = view self.current_row = None self.refresh(row) self.view.model().new_bookdisplay_data.connect(self.slave) self.fit_cover.stateChanged.connect(self.toggle_cover_fit) self.ns = QShortcut(QKeySequence('Alt+Right'), self) self.ns.activated.connect(self.next) self.ps = QShortcut(QKeySequence('Alt+Left'), self) self.ps.activated.connect(self.previous) self.next_button.setToolTip( _('Next [%s]') % unicode(self.ns.key().toString(QKeySequence.NativeText))) self.previous_button.setToolTip( _('Previous [%s]') % unicode(self.ps.key().toString(QKeySequence.NativeText))) geom = QCoreApplication.instance().desktop().availableGeometry(self) screen_height = geom.height() - 100 screen_width = geom.width() - 100 self.resize(max(int(screen_width / 2), 700), screen_height) saved_layout = gprefs.get('book_info_dialog_layout', None) if saved_layout is not None: try: self.restoreGeometry(saved_layout[0]) self.splitter.restoreState(saved_layout[1]) except Exception: pass def link_clicked(self, qurl): link = unicode(qurl.toString(QUrl.None)) self.link_delegate(link) def done(self, r): saved_layout = (bytearray(self.saveGeometry()), bytearray(self.splitter.saveState())) gprefs.set('book_info_dialog_layout', saved_layout) ret = QDialog.done(self, r) self.view.model().new_bookdisplay_data.disconnect(self.slave) self.view = self.link_delegate = self.gui = None self.closed.emit(self) return ret def cover_changed(self, data): if self.current_row is not None: id_ = self.view.model().id(self.current_row) self.view.model().db.set_cover(id_, data) if self.gui.cover_flow: self.gui.cover_flow.dataChanged() ci = self.view.currentIndex() if ci.isValid(): self.view.model().current_changed(ci, ci) def details_size_hint(self): return QSize(350, 550) def toggle_cover_fit(self, state): gprefs.set('book_info_dialog_fit_cover', self.fit_cover.isChecked()) self.resize_cover() def cover_view_resized(self, event): QTimer.singleShot(1, self.resize_cover) def slave(self, mi): self.refresh(mi.row_number, mi) def move(self, delta=1): idx = self.view.currentIndex() if idx.isValid(): m = self.view.model() ni = m.index(idx.row() + delta, idx.column()) if ni.isValid(): if self.view.isVisible(): self.view.scrollTo(ni) self.view.setCurrentIndex(ni) def next(self): self.move() def previous(self): self.move(-1) def resize_cover(self): if self.cover_pixmap is None: return pixmap = self.cover_pixmap if self.fit_cover.isChecked(): scaled, new_width, new_height = fit_image( pixmap.width(), pixmap.height(), self.cover.size().width() - 10, self.cover.size().height() - 10) if scaled: pixmap = pixmap.scaled(new_width, new_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.cover.set_pixmap(pixmap) self.update_cover_tooltip() def update_cover_tooltip(self): tt = '' if self.marked: tt = _('This book is marked') if self.marked in { True, 'true' } else _('This book is marked as: %s') % self.marked tt += '\n\n' if self.cover_pixmap is not None: sz = self.cover_pixmap.size() tt += _('Cover size: %(width)d x %(height)d') % dict( width=sz.width(), height=sz.height()) self.cover.setToolTip(tt) def refresh(self, row, mi=None): if isinstance(row, QModelIndex): row = row.row() if row == self.current_row and mi is None: return mi = self.view.model().get_book_display_info(row) if mi is None else mi if mi is None: # Indicates books was deleted from library, or row numbers have # changed return self.previous_button.setEnabled(False if row == 0 else True) self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex()) - 1 else True) self.current_row = row self.setWindowTitle(mi.title) self.cover_pixmap = QPixmap.fromImage(mi.cover_data[1]) self.resize_cover() html = render_html(mi, self.css, True, self, all_fields=True) self.details.setHtml(html) self.marked = mi.marked self.cover.setBackgroundBrush( self.marked_brush if mi.marked else self.normal_brush) self.update_cover_tooltip()
class BookInfo(QDialog): closed = pyqtSignal(object) def __init__(self, parent, view, row, link_delegate): QDialog.__init__(self, parent) self.normal_brush = QBrush(Qt.white) self.marked_brush = QBrush(Qt.lightGray) self.marked = None self.gui = parent self.splitter = QSplitter(self) self._l = l = QVBoxLayout(self) self.setLayout(l) l.addWidget(self.splitter) self.cover = CoverView(self) self.cover.resizeEvent = self.cover_view_resized self.cover.cover_changed.connect(self.cover_changed) self.cover_pixmap = None self.cover.sizeHint = self.details_size_hint self.splitter.addWidget(self.cover) self.details = QWebView(self) self.details.sizeHint = self.details_size_hint self.details.page().setLinkDelegationPolicy(self.details.page().DelegateAllLinks) self.details.linkClicked.connect(self.link_clicked) s = self.details.page().settings() s.setAttribute(s.JavascriptEnabled, False) self.css = css() self.link_delegate = link_delegate self.details.setAttribute(Qt.WA_OpaquePaintEvent, False) palette = self.details.palette() self.details.setAcceptDrops(False) palette.setBrush(QPalette.Base, Qt.transparent) self.details.page().setPalette(palette) self.c = QWidget(self) self.c.l = l2 = QGridLayout(self.c) self.c.setLayout(l2) l2.addWidget(self.details, 0, 0, 1, -1) self.splitter.addWidget(self.c) self.fit_cover = QCheckBox(_('Fit &cover within view'), self) self.fit_cover.setChecked(gprefs.get('book_info_dialog_fit_cover', True)) l2.addWidget(self.fit_cover, l2.rowCount(), 0, 1, -1) self.previous_button = QPushButton(QIcon(I('previous.png')), _('&Previous'), self) self.previous_button.clicked.connect(self.previous) l2.addWidget(self.previous_button, l2.rowCount(), 0) self.next_button = QPushButton(QIcon(I('next.png')), _('&Next'), self) self.next_button.clicked.connect(self.next) l2.addWidget(self.next_button, l2.rowCount() - 1, 1) self.view = view self.current_row = None self.refresh(row) self.view.model().new_bookdisplay_data.connect(self.slave) self.fit_cover.stateChanged.connect(self.toggle_cover_fit) self.ns = QShortcut(QKeySequence('Alt+Right'), self) self.ns.activated.connect(self.next) self.ps = QShortcut(QKeySequence('Alt+Left'), self) self.ps.activated.connect(self.previous) self.next_button.setToolTip(_('Next [%s]')% unicode(self.ns.key().toString(QKeySequence.NativeText))) self.previous_button.setToolTip(_('Previous [%s]')% unicode(self.ps.key().toString(QKeySequence.NativeText))) geom = QCoreApplication.instance().desktop().availableGeometry(self) screen_height = geom.height() - 100 screen_width = geom.width() - 100 self.resize(max(int(screen_width/2), 700), screen_height) saved_layout = gprefs.get('book_info_dialog_layout', None) if saved_layout is not None: try: self.restoreGeometry(saved_layout[0]) self.splitter.restoreState(saved_layout[1]) except Exception: pass def link_clicked(self, qurl): link = unicode(qurl.toString(QUrl.None)) self.link_delegate(link) def done(self, r): saved_layout = (bytearray(self.saveGeometry()), bytearray(self.splitter.saveState())) gprefs.set('book_info_dialog_layout', saved_layout) ret = QDialog.done(self, r) self.view.model().new_bookdisplay_data.disconnect(self.slave) self.view = self.link_delegate = self.gui = None self.closed.emit(self) return ret def cover_changed(self, data): if self.current_row is not None: id_ = self.view.model().id(self.current_row) self.view.model().db.set_cover(id_, data) if self.gui.cover_flow: self.gui.cover_flow.dataChanged() ci = self.view.currentIndex() if ci.isValid(): self.view.model().current_changed(ci, ci) def details_size_hint(self): return QSize(350, 550) def toggle_cover_fit(self, state): gprefs.set('book_info_dialog_fit_cover', self.fit_cover.isChecked()) self.resize_cover() def cover_view_resized(self, event): QTimer.singleShot(1, self.resize_cover) def slave(self, mi): self.refresh(mi.row_number, mi) def move(self, delta=1): idx = self.view.currentIndex() if idx.isValid(): m = self.view.model() ni = m.index(idx.row() + delta, idx.column()) if ni.isValid(): if self.view.isVisible(): self.view.scrollTo(ni) self.view.setCurrentIndex(ni) def next(self): self.move() def previous(self): self.move(-1) def resize_cover(self): if self.cover_pixmap is None: return pixmap = self.cover_pixmap if self.fit_cover.isChecked(): scaled, new_width, new_height = fit_image(pixmap.width(), pixmap.height(), self.cover.size().width()-10, self.cover.size().height()-10) if scaled: pixmap = pixmap.scaled(new_width, new_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.cover.set_pixmap(pixmap) self.update_cover_tooltip() def update_cover_tooltip(self): tt = '' if self.marked: tt = _('This book is marked') if self.marked in {True, 'true'} else _( 'This book is marked as: %s') % self.marked tt += '\n\n' if self.cover_pixmap is not None: sz = self.cover_pixmap.size() tt += _('Cover size: %(width)d x %(height)d')%dict(width=sz.width(), height=sz.height()) self.cover.setToolTip(tt) def refresh(self, row, mi=None): if isinstance(row, QModelIndex): row = row.row() if row == self.current_row and mi is None: return mi = self.view.model().get_book_display_info(row) if mi is None else mi if mi is None: # Indicates books was deleted from library, or row numbers have # changed return self.previous_button.setEnabled(False if row == 0 else True) self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex())-1 else True) self.current_row = row self.setWindowTitle(mi.title) self.cover_pixmap = QPixmap.fromImage(mi.cover_data[1]) self.resize_cover() html = render_html(mi, self.css, True, self, all_fields=True) self.details.setHtml(html) self.marked = mi.marked self.cover.setBackgroundBrush(self.marked_brush if mi.marked else self.normal_brush) self.update_cover_tooltip()
class Application(object): settings = None html_template = None geometry = None loaded_plugins = None command_handlers = None metadata = None def _on_navigation(self, url): url = str(url.toString()) handler, cmd = url.split('::') if handler and handler in self.command_handlers: return self.command_handlers[handler].handle(cmd) else: self.web_view.load(QUrl(url)) return False def render_template(self): metadata = self.metadata output = self.html_template # @TODO: create shell::{some command} parser # MSG = MSG.replace('%UNAME%', shell_cmd('uname -a').decode('UTF-8')) if metadata: output = output.replace('%ARTIST%', metadata['artist']) output = output.replace('%ALBUM%', metadata['album']) output = output.replace('%TITLE%', metadata['title']) output = output.replace('%ARTURL%', metadata['arturl']) self.web_view.setHtml(output) def _read_config(self): # @TODO: here it will be config parsing, now it's a placeholder self.settings = SETTINGS try: self.html_template = open('index.html', 'r').read() except Exception: print('Error in template') self.html_template = DEFAULT_MSG left = self.settings.get( "left", self.app.desktop().screenGeometry().width() - self.settings["right"] - self.settings["width"] ) self.settings['geometry'] = ( left, self.settings["top"], self.settings["width"], self.settings["height"] ) self.html_template = self.html_template.replace( '%WIDTH%', str(self.settings["width"])) self.html_template = self.html_template.replace('%PATH%', APP_PATH) def __init__(self): self.app = QApplication(sys.argv) self.window = QMainWindow() self.metadata = {} self.command_handlers = {} self._read_config() if get_desktop_name() in ['openbox', 'pekwm']: # windowAttribute for openbox/pekwm WM self.window.setAttribute(Qt.WA_X11NetWmWindowTypeDesktop) else: # windowAttribute for any other DE like xfce, gnome, unity, kde etc self.window.setAttribute(Qt.WA_X11NetWmWindowTypeDock) self.window.setWindowFlags(Qt.WindowStaysOnBottomHint) self.window.setAttribute(Qt.WA_TranslucentBackground) self.web_view = QWebView() # trasparent webview palette = self.web_view.palette() palette.setBrush(QPalette.Base, Qt.transparent) self.web_view.page().setPalette(palette) self.web_view.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.web_view.linkClicked.connect(self._on_navigation) self.web_view.setHtml(self.html_template) self.window.setGeometry(*self.settings['geometry']) self.window.setCentralWidget(self.web_view) self.window.show() self.loaded_plugins = [ CmdHandler(self), ClementineDBusInterface(self), ] sys.exit(self.app.exec_())