View Single Post
Old 03-24-2022, 12:49 PM   #187
lrpirlet
Connoisseur
lrpirlet began at the beginning.
 
Posts: 96
Karma: 40
Join Date: Mar 2020
Location: Belgium (sorry, I am from the Walloon side of the country and I speak french only)
Device: PW3, Kobo Libra H2O
type object 'QKeySequence' has no attribute 'FindNext'

Hello

I have been working on porting noosfere_util to Qt6...

I have the following:
Code:
2022-03-24 15:02:04,658:ERROR:STDERR:Traceback (most recent call last):
2022-03-24 15:02:04,658:ERROR:STDERR:  File "runpy.py", line 196, in _run_module_as_main
2022-03-24 15:02:04,659:ERROR:STDERR:  File "runpy.py", line 86, in _run_code
2022-03-24 15:02:04,659:ERROR:STDERR:  File "site.py", line 82, in <module>
2022-03-24 15:02:04,661:ERROR:STDERR:  File "site.py", line 77, in main
2022-03-24 15:02:04,661:ERROR:STDERR:  File "site.py", line 49, in run_entry_point
2022-03-24 15:02:04,662:ERROR:STDERR:  File "calibre\utils\ipc\worker.py", line 215, in main
2022-03-24 15:02:04,662:ERROR:STDERR:  File "calibre\gui_launch.py", line 98, in webengine_dialog
2022-03-24 15:02:04,662:ERROR:STDERR:  File "calibre_plugins.noosfere_util.web_main", line 453, in main
2022-03-24 15:02:04,662:ERROR:STDERR:  File "calibre_plugins.noosfere_util.web_main", line 150, in __init__
2022-03-24 15:02:04,663:ERROR:STDERR:  File "calibre_plugins.noosfere_util.web_main", line 223, in set_search_bar
2022-03-24 15:02:04,663:ERROR:STDERR:  File "calibre_plugins.noosfere_util.web_main", line 101, in __init__
2022-03-24 15:02:04,663:ERROR:STDERR:AttributeError
2022-03-24 15:02:04,663:ERROR:STDERR::
2022-03-24 15:02:04,663:ERROR:STDERR:type object 'QKeySequence' has no attribute 'FindNext'
When I comment that line, I also have it for QKeySequence.FindPrevious and for QKeySequence.Find. According to the doc, this should be ok under qt6... It is working under qt5...

short code extract

Code:
        #QShortcut(QKeySequence.FindNext, self, activated=next_btn.animateClick)
        #QShortcut(QKeySequence.FindPrevious, self, activated=prev_btn.animateClick)
        QShortcut(QKeySequence(Qt.Key_Escape), self.srch_dsp, activated=self.closed)
complete source file if needed

Spoiler:
Code:
#!/usr/bin/env python
# vim:fileencoding=utf-8

__license__   = 'GPL v3'
__copyright__ = '2022, Louis Richard Pirlet'


# from PyQt5.QtCore import pyqtSlot, QUrl, QSize, Qt, pyqtSignal, QTimer
from qt.core import (pyqtSlot, QUrl, QSize, Qt, pyqtSignal, QTimer,      # from PyQt5.QtCore import pyqtSlot, QUrl, QSize, Qt, pyqtSignal, QTimer
    QMainWindow, QToolBar, QAction, QLineEdit, QStatusBar, QProgressBar, # from PyQt5.QtWidgets import (QMainWindow, QToolBar, QAction, QLineEdit, QStatusBar, QProgressBar,
    QMessageBox, QWidget, QVBoxLayout, QHBoxLayout, QLabel,        # qApp,                                 QMessageBox, qApp, QWidget, QVBoxLayout, QHBoxLayout, QLabel,
    QPushButton, QShortcut,                                              #                                 QPushButton, QShortcut)
    QKeySequence, QIcon                                                  # from PyQt5.QtGui import QKeySequence    #, QIcon
)
# from qt.core import ()
#     QApplication, QBrush, QByteArray, QCheckBox, QColor, QColorDialog,
#     QDialog, QDialogButtonBox, QFont, QFontInfo, QFontMetrics, QFormLayout,
#     QMenu, QPalette, QPlainTextEdit,
#     QSyntaxHighlighter, QTabWidget, QTextBlockFormat, QTextCharFormat,
#     QTextCursor, QTextEdit, QTextFormat, QTextListFormat,
#     QObject, QToolButton,  QMenuBar,
#     QPropertyAnimation, QEasingCurve, pyqtProperty, QPainter,
#     QEvent, QStylePainter,
#     QSizePolicy, QSplitter, QStackedWidget, QStyle, QStyleOption,
#     QTabBar,
#     QComboBox, QCompleter, QDateTime,
#     QGridLayout, QInputDialog,
#     QListView, QModelIndex,
#     QPixmap, pyqtSlot, QUrl, QSize, Qt, pyqtSignal, QTimer,
#     QMainWindow, QToolBar, QAction, QLineEdit, QStatusBar, QProgressBar,
#     QMessageBox, QWidget, QVBoxLayout, QHBoxLayout, QLabel,            #                                 QPushButton, QShortcut)
#     QKeySequence, QIcon
# )





from qt.webengine import QWebEngineView, QWebEnginePage     # from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage

from calibre.gui2 import Application

from json import dumps
from functools import partial
import tempfile, os, sys, logging


class StreamToLogger(object):
    """
    Fake file-like stream object that redirects writes to a logger instance.
    This will help when the web browser in web_main does not pop-up (read: web_main crashes)
    """
    def __init__(self, logger, log_level=logging.INFO):
      self.logger = logger
      self.log_level = log_level
      self.linebuf = ''

    def write(self, buf):
      for line in buf.rstrip().splitlines():
         self.logger.log(self.log_level, line.rstrip())

    def flush(self):
        for handler in self.logger.handlers:
            handler.flush()

class Search_Panel(QWidget):
    searched = pyqtSignal(str, QWebEnginePage.FindFlag)
    closed = pyqtSignal()

    def __init__(self,parent=None):
        super(Search_Panel,self).__init__(parent)

        next_btn = QPushButton('Suivant')
        next_btn.setToolTip("Ce bouton recherche la prochaine occurrence dans la page")
        next_btn.clicked.connect(self.update_searching)
        if isinstance(next_btn, QPushButton): next_btn.clicked.connect(self.setFocus)

        prev_btn = QPushButton('Précédent')
        prev_btn.setToolTip("Ce bouton recherche l'occurrence précédente dans la page")
        prev_btn.clicked.connect(self.on_preview_find)
        if isinstance(prev_btn, QPushButton): prev_btn.clicked.connect(self.setFocus)

        done_btn = QPushButton("Terminé")
        done_btn.setToolTip("Ce bouton ferme la barre de recherche")
        done_btn.clicked.connect(self.closed)
        if isinstance(done_btn, QPushButton): done_btn.clicked.connect(self.setFocus)

        self.srch_dsp = QLineEdit()
        self.srch_dsp.setToolTip(" Cette boite contient le texte Ã* chercher dans la page")
        self.setFocusProxy(self.srch_dsp)
        self.srch_dsp.textChanged.connect(self.update_searching)
        self.srch_dsp.returnPressed.connect(self.update_searching)
        self.closed.connect(self.srch_dsp.clear)

        self.srch_lt = QHBoxLayout(self)
        self.srch_lt.addWidget(self.srch_dsp)
        self.srch_lt.addWidget(next_btn)
        self.srch_lt.addWidget(prev_btn)
        self.srch_lt.addWidget(done_btn)

        #QShortcut(QKeySequence.FindNext, self, activated=next_btn.animateClick)
        #QShortcut(QKeySequence.FindPrevious, self, activated=prev_btn.animateClick)
        QShortcut(QKeySequence(Qt.Key_Escape), self.srch_dsp, activated=self.closed)

    @pyqtSlot()
    def on_preview_find(self):
        self.update_searching(QWebEnginePage.FindBackward)

    @pyqtSlot()
    def update_searching(self, direction=QWebEnginePage.FindFlag(0)):
        flag = direction
        self.searched.emit(self.srch_dsp.text(), flag)

    def showEvent(self, event):
        super(Search_Panel, self).showEvent(event)
        self.setFocus(True)

class MainWindow(QMainWindow):
    """
    this process, running in the calibre environment, is detached from calibre program
    It does receive data from noofere_util, processes it, then communicates back the result and dies.
    In fact this is a WEB browser centered on www.noosfere.org to get the nsfr_id of a choosen volume.

    """

    def __init__(self, data):
        super().__init__()

      # Initialize environment..
      # note: web_main is NOT supposed to output anything over STDOUT or STDERR
        logging.basicConfig(
        level = logging.DEBUG,
        format = '%(asctime)s:%(levelname)s:%(name)s:%(message)s',
        filename = os.path.join(tempfile.gettempdir(), 'nsfr_utl-web_main.log'),
        filemode = 'a')
        stdout_logger = logging.getLogger('STDOUT')
        sl = StreamToLogger(stdout_logger, logging.INFO)
        sys.stdout = sl
        stderr_logger = logging.getLogger('STDERR')
        sl = StreamToLogger(stderr_logger, logging.ERROR)
        sys.stderr = sl

      # data = [url, isbn, auteurs, titre]
        self.isbn, self.auteurs, self.titre = data[1].replace("-",""), data[2], data[3]

        self.set_browser()
        self.set_isbn_box()
        self.set_auteurs_box()
        self.set_titre_box()
        self.set_search_bar()
        self.join_all_boxes()
        self.set_nav_and_status_bar()

      # make all that visible... I want this window on top ready to work with
        self.setWindowFlags(Qt.WindowStaysOnTopHint)
        self.show()
        self.activateWindow()

      # signals
        self.browser.urlChanged.connect(self.update_urlbar)
        self.browser.loadStarted.connect(self.loading_title)
        self.browser.loadStarted.connect(self.set_progress_bar)
        self.browser.loadProgress.connect(self.update_progress_bar)
        self.browser.loadFinished.connect(self.update_title)
        self.browser.loadFinished.connect(self.reset_progress_bar)
        self.isbn_btn.clicked.connect(partial(self.set_noosearch_page, "isbn"))
        self.auteurs_btn.clicked.connect(partial(self.set_noosearch_page, "auteurs"))
        self.titre_btn.clicked.connect(partial(self.set_noosearch_page, "titre"))

      # browser
    def set_browser(self):
        print("in set_browser")
        self.browser = QWebEngineView()
        self.browser.setUrl(QUrl("http://www.google.com"))

      # info boxes
    def set_isbn_box(self):        # info boxes isbn
        print("in set_isbn_box")
        self.isbn_btn = QPushButton(" ISBN ", self)
        self.isbn_btn.setToolTip('Action sur la page noosfere initiale: "Mots-clefs Ã* rechercher" = ISBN, coche la case "Livre".')
                                   # Action on home page: "Mots-clefs Ã* rechercher" = ISBN, set checkbox "Livre".
        self.isbn_dsp = QLineEdit()
        self.isbn_dsp.setReadOnly(True)
        self.isbn_dsp.setText(self.isbn)
        self.isbn_dsp.setToolTip(" Cette boite montre l'ISBN protégé en écriture. Du texte peut y être sélectionné pour chercher dans la page")
                                   # This box displays the ISBN write protected. Some text may be selected here to search the page.

        self.isbn_lt = QHBoxLayout()
        self.isbn_lt.addWidget(self.isbn_btn)
        self.isbn_lt.addWidget(self.isbn_dsp)

    def set_auteurs_box(self):                  # info boxes auteurs
        print("in set_auteurs_box")
        self.auteurs_btn = QPushButton("Auteur(s)", self)
        self.auteurs_btn.setToolTip('Action sur la page noosfere initiale: "Mots-clefs Ã* rechercher" = Auteur(s), coche la case "Auteurs".')
                                      # Action on home page: "Mots-clefs Ã* rechercher" = Auteur(s), set checkbox "Auteurs".
        self.auteurs_dsp = QLineEdit()
        self.auteurs_dsp.setReadOnly(True)
        self.auteurs_dsp.setText(self.auteurs)
        self.auteurs_dsp.setToolTip(" Cette boite montre le ou les Auteur(s) protégé(s) en écriture. Du texte peut être manuellement introduit pour chercher dans la page")
                                      # This box displays the Author(s) write protected. Some text may be written here to search the page.
        self.auteurs_lt = QHBoxLayout()
        self.auteurs_lt.addWidget(self.auteurs_btn)
        self.auteurs_lt.addWidget(self.auteurs_dsp)

    def set_titre_box(self):                    # info boxes titre
        print("in set_titre_box")
        self.titre_btn = QPushButton("Titre", self)
        self.titre_btn.setToolTip('Action sur la page noosfere initiale: "Mots-clefs Ã* rechercher" = Titre, coche la case "Livres".')
                                    # Action on home page: "Mots-clefs Ã* rechercher" = Titre, set checkbox "Livres".
        self.titre_dsp = QLineEdit()
        self.titre_dsp.setReadOnly(True)
        self.titre_dsp.setText(self.titre)
        self.titre_dsp.setToolTip(" Cette boite montre le Titre protégé en écriture. Tout ou partie du texte peut être sélectionné pour chercher dans la page")
                                    # This box displays the Title write protected. Some text may be selected here to search the page.
        self.titre_lt = QHBoxLayout()
        self.titre_lt.addWidget(self.titre_btn)
        self.titre_lt.addWidget(self.titre_dsp)

  # search bar hidden when inactive ready to find something (I hope :-) )
    def set_search_bar(self):
        print("in set_search_bar")
        self.search_pnl = Search_Panel()
        self.search_toolbar = QToolBar()
        self.search_toolbar.addWidget(self.search_pnl)
        self.addToolBar(Qt.BottomToolBarArea, self.search_toolbar)
        self.search_toolbar.hide()
        self.search_pnl.searched.connect(self.on_searched)
        self.search_pnl.closed.connect(self.search_toolbar.hide)

    def join_all_boxes(self):                   # put all that together, center, size and make it central widget
        print("in join_all_boxes")
        layout = QVBoxLayout()
        layout.addWidget(self.browser)
        layout.addLayout(self.isbn_lt)
        layout.addLayout(self.auteurs_lt)
        layout.addLayout(self.titre_lt)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        self.resize(1200,1000)

      # set navigation toolbar
    def set_nav_and_status_bar(self) :
        print("in set_nav_and_status_bar")
        nav_tb = QToolBar("Navigation")
        nav_tb.setIconSize(QSize(24,24))
        self.addToolBar(nav_tb)

        back_btn = QAction(get_icons('blue_icon/back.png'), "Back", self)
        back_btn.setToolTip("On revient Ã* la page précédente")                    # Back to the previous page
        back_btn.triggered.connect(self.browser.back)
        nav_tb.addAction(back_btn)

        next_btn = QAction(get_icons('blue_icon/forward.png'), "Forward", self)
        next_btn.setToolTip("On retourne Ã* la page suivante")                     # Back to the next page
        next_btn.triggered.connect(self.browser.forward)
        nav_tb.addAction(next_btn)

        reload_btn = QAction(get_icons('blue_icon/reload.png'), "Reload", self)
        reload_btn.setToolTip("On recharge la page présente")                     # Reload the page
        reload_btn.triggered.connect(self.browser.reload)
        nav_tb.addAction(reload_btn)

        home_btn = QAction(get_icons('blue_icon/home.png'), "Home", self)
        home_btn.setToolTip("On va Ã* la recherche avancée de noosfere")           # We go to the front page of noosfere
        home_btn.triggered.connect(self.navigate_home)
        nav_tb.addAction(home_btn)

        stop_btn = QAction(get_icons('blue_icon/stop.png'), "Stop", self)
        stop_btn.setToolTip("On arrête de charger la page")                       # Stop loading the page
        stop_btn.triggered.connect(self.browser.stop)
        nav_tb.addAction(stop_btn)

        nav_tb.addSeparator()

        find_btn = QAction(get_icons('blue_icon/search.png'), "Search", self)
        find_btn.setToolTip("Ce bouton fait apparaitre la barre de recherche... Z'avez pas vu Mirza? Oh la la la la la. Où est donc passé ce chien. Je le cherche partout...  (Merci Nino Ferrer)")   # search, search...
        find_btn.triggered.connect(self.wake_search_panel)
        #find_btn.setShortcut(QKeySequence.Find)
        nav_tb.addAction(find_btn)

        self.urlbox = QLineEdit()
        self.urlbox.returnPressed.connect(self.navigate_to_url)
        self.urlbox.setToolTip("On peut même introduire une adresse, hors noosfere, mais A TES RISQUES ET PERILS... noosfere est sûr (https://), la toile par contre...")
                                # You can even enter an address, outside of noosfere, but AT YOUR OWN RISK... noosfere is safe: (https://), the web on the other side...
        nav_tb.addWidget(self.urlbox)

        abort_btn = QAction(get_icons('blue_icon/abort.png'), "Abort", self)
        abort_btn.setToolTip("On arrête, on oublie, on ne change rien au livre... au suivant")
                              # Stop everything, forget everything and change nothing... proceed to next book
        abort_btn.triggered.connect(self.abort_book)
        nav_tb.addAction(abort_btn)

        nav_tb.addSeparator()

        exit_btn = QAction(get_icons('blue_icon/exit.png'), "Select and exit", self)
        exit_btn.setToolTip("On sélectionne cet URL pour extraction de nsfr_id... au suivant")
                             # select this URL for extraction of nsfr_id, continue
        exit_btn.triggered.connect(self.select_and_exit)
        nav_tb.addAction(exit_btn)

  # set status bar
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
  # Create page loading progress bar that is displayed in the status bar.
        self.msg_label = QLabel()
        self.page_load_label = QLabel()
        self.page_load_pb = QProgressBar()
  # Set up widgets on the statusbar
        self.statusBar().addPermanentWidget(self.msg_label, stretch=36)
        self.statusBar().addPermanentWidget(self.page_load_label, stretch=14)
        self.statusBar().addPermanentWidget(self.page_load_pb, stretch=50)

  # search action
    @pyqtSlot(str, QWebEnginePage.FindFlag)
    def on_searched(self, text, flag):
        print("in on_searched text : {}, flag : {}".format(text, flag))
        def callback(found):
            if text and not found:
                self.msg_label.setText('Désolé, {} pas trouvé...'.format(text))     # Sorry "text" not found
            else:
                self.msg_label.setText('')
        self.browser.findText(text, flag, callback)

  # info boxes actions
    @pyqtSlot()
    def set_noosearch_page(self, iam):
        print("in set_noosearch_page iam : {}".format(iam))
        if self.urlbox.text() == "https://www.noosfere.org/livres/noosearch.asp":
            if iam == "isbn": val = self.isbn
            elif iam == "auteurs": val = self.auteurs
            else: val = self.titre
            self.browser.page().runJavaScript("document.getElementsByName('Mots')[1].value =" + dumps(val))
            if iam == "auteurs":
                self.browser.page().runJavaScript("document.getElementsByName('auteurs')[0].checked = true")
                self.browser.page().runJavaScript("document.getElementsByName('livres')[0].checked = false")
            else:
                self.browser.page().runJavaScript("document.getElementsByName('livres')[0].checked = true")
                self.browser.page().runJavaScript("document.getElementsByName('auteurs')[0].checked = false")
        else:
            pass

    @pyqtSlot()
    def wake_search_panel(self):
        print("in wake_search_panel")
        self.search_toolbar.show()

  # Navigation actions
    def initial_url(self, url="http://www.google.com"):
        print("in initial_url url : {}".format(url))
        self.browser.setUrl(QUrl(url))

    def navigate_home(self):
        print("in navigate_home")
        self.browser.setUrl(QUrl("https://www.noosfere.org/livres/noosearch.asp"))

    def navigate_to_url(self):                    # Does not receive the Url, activated when url bar is manually changed
        print("in navigate_to_url")
        q = QUrl(self.urlbox.text())
        self.browser.setUrl(q)

    def update_urlbar(self, q):
        print("in update_urlbar")
        self.urlbox.setText(q.toString())
        self.urlbox.setCursorPosition(0)

    def loading_title(self):
        print("in loading_title")
      # anytime we change page we come here... let's clear and hide the search panel
        self.search_pnl.closed.emit()           # by sending a close search panel signal
      # before doubling indication that we load a page in the title
        title="En téléchargement de l'url"
        self.setWindowTitle(title)

    def update_title(self):
        print("in update_title")
        title = self.browser.page().title()
        self.setWindowTitle(title)

    def report_returned_id(self, returned_id):
        print("in report_returned_id returned_id : {}".format(returned_id))
        report_tpf=open(os.path.join(tempfile.gettempdir(),"nsfr_utl_report_returned_id"),"w")
        report_tpf.write(returned_id)
        report_tpf.close

    def set_progress_bar(self):
        print("in set_progress_bar")
        self.page_load_pb.show()
        self.page_load_label.show()

    def update_progress_bar(self, progress):
        print("in update_progress_bar progress : {}".format(progress))
        self.page_load_pb.setValue(progress)
        self.page_load_label.setText("En téléchargement de l'url... ({}/100)".format(str(progress)))

    def reset_progress_bar(self):
        print("in reset_progress_bar")
        def wait_a_minut():
            self.page_load_pb.hide()
            self.page_load_label.hide()
        QTimer.singleShot(1000, wait_a_minut)

    def select_and_exit(self):                    # sent response over report_returned_id file in temp dir
      # create a temp file with name starting with nsfr_id
        print("in select_and_exit")
        choosen_url = self.urlbox.text()
        if "numlivre=" in choosen_url:
            print('choosen_url : ',choosen_url)
            nsfr_id = "vl$"+choosen_url.split("numlivre=")[1]
            print("nsfr_id : ", nsfr_id)
            self.report_returned_id(nsfr_id)
        else:
            print('No book selected, no change will take place: unset')
            self.report_returned_id("unset")
        Application.instance().quit()     # exit application... qApp gone in PyQt6

    def abort_book(self):                         # we want to NOT change the book and proceed to the next one
        print("in abort_book")
        reply = QMessageBox.question(self, 'Certain', "Oublier ce livre et passer au suivant", QMessageBox.No | QMessageBox.Yes, QMessageBox.Yes)
        if reply == QMessageBox.Yes:
            print("WebEngineView was aborted: aborted")
            self.report_returned_id("aborted")
            Application.instance().quit()     # exit application... qApp gone in PyQt6


    def closeEvent(self, event):                  # abort hit window exit "X" button we stop processing this and all following books
        print("in closeEvent event : {}".format(event))
        reply = QMessageBox.question(self, 'Vraiment', "Quitter et ne plus rien changer", QMessageBox.No | QMessageBox.Yes, QMessageBox.Yes)
        if reply == QMessageBox.Yes:
            event.accept()
            print("WebEngineView was closed: killed")
            self.report_returned_id("killed")
            super().closeEvent(event)
        else:
            event.ignore()


def main(data):

    # create a temp file... while it exists launcher program will wait... this file will disappear with the process
    sync_tpf=tempfile.NamedTemporaryFile(prefix="nsfr_utl_sync-cal-qweb")

    # retrieve component from data
    #        data = [url, isbn, auteurs, titre]
    url, isbn, auteurs, titre = data[0], data[1], data[2], data[3],
    # Start QWebEngineView and associated widgets
    app = Application([])
    window = MainWindow(data)
    window.initial_url(url)     # supposed to be noosfere advanced search page, fixed by launcher program
    app.exec()

    # signal launcher program that we are finished
    sync_tpf.close           # close temp file


if __name__ == '__main__':
    '''
    watch out name 'get_icons' is not defined, and can't be defined easyly...
    workaround, swap it with QIcon + path to icon
    '''
    url = "https://www.noosfere.org/livres/noosearch.asp"   # jump directly to noosfere advanced search page
    isbn = "2-277-12362-5"
    auteurs = "Alfred Elton VAN VOGT"                       # forget not that auteurs may be a list of auteurs
    titre = "Le Monde des Ã"
    data = [url, isbn, auteurs, titre]
    main(data)

    tf = open(os.path.join(tempfile.gettempdir(),"nsfr_utl_report_returned_id"), "r")
    returned_id = tf.read()
    tf.close()

  # from here should modify the metadata, or not.
    if returned_id.replace("vl$","").replace("-","").isnumeric():
        nsfr_id = returned_id
        print("nfsr_id : ", nsfr_id)
    elif "unset" in returned_id:
        print('unset, no change will take place...')
    elif "killed" in returned_id:
        print('killed, no change will take place...')
    else:
        print("should not ends here... returned_id : ", returned_id)


I could not find any occurence of QKeySequence.FindNext, QKeySequence.FindPrevious or QKeySequence.Find in calibre source files...

Thanks in advance.
lrpirlet is offline   Reply With Quote