美文网首页
用PyQt4+Python写一个简单的EPub阅读器(2/3)

用PyQt4+Python写一个简单的EPub阅读器(2/3)

作者: knarfeh | 来源:发表于2016-04-16 21:24 被阅读812次

    上一篇,这一篇我们写GUI。

    上一篇提出了图书仓库的概念,更具体的想法是:这个仓库是一个文件夹,所有打开的书都往这个文件夹中复制一份,同时,我们在仓库中有一个library.json,存放书籍清单,每次打开一本书,也在该清单中记录一份,根据清单刷新我们的Library(dockwidget目录)

    目前目录结构如下:

    GUI

    这里只是写GUI,所以不做过多的讲解,画GUI也真的没有什么好讲的。当然,这里画GUI用的是比较繁琐的方式,用Qt creator画出界面再用pyuic4生出py文件会比较方便一点,讲真,中文的PyQt的资料实在太少了,有空的话可以写一个中文教程(好像又给自己挖坑了)。这里我就直接贴代码了。

    项目中总会有一些常量,我们把它记录在constants.py中,同时这个模块进行初始化的操作,新建必要的文件夹,数据文件。

    import os
    
    PROJECT_DIR = os.path.abspath(os.path.dirname(__file__))
    LIBRARY_DIR = os.path.join(PROJECT_DIR, 'bookdata') + os.sep
    
    if not os.path.exists(LIBRARY_DIR):
        os.mkdir(LIBRARY_DIR)
    
    LIBRARY = os.path.join(LIBRARY_DIR, "library.json")
    
    if not os.path.exists(LIBRARY):
        open(LIBRARY, 'w').close()
    

    由于目录结构的变化,上一篇写的books.py也有一点变化,LIBRARY_DIR可以从constants模块中导入,还要加上两行

    parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.insert(0, parentdir)
    

    这样就能从父模块中导入constants,所以books.py就变成了:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    import os
    import zipfile
    import sys
    
    from lxml import etree
    from BeautifulSoup import BeautifulStoneSoup
    parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.insert(0, parentdir)
    
    from constants import LIBRARY_DIR
    
    # LIBRARY_DIR = os.path.abspath('.') + os.sep
    
    RECOVER_PARSER = etree.XMLParser(recover=True, no_network=True)
    NAMESPACES = {
        'dc': 'http://purl.org/dc/elements/1.1/',
    }
    
    
    class Book(object):
        u"""
        需要主动调用open方法才能获得相应的属性
        """
        _FILE = LIBRARY_DIR + '%s.epub'
    
        def __init__(self, book_id=None):
            if book_id:
                self.open(book_id)
    
        def fromstring(self, raw, parser=RECOVER_PARSER):
            return etree.fromstring(raw, parser=parser)
    
        def read_doc_props(self, raw):
            u"""
    
            :param raw: raw string of xml
            :return:
            """
            root = self.fromstring(raw)
            self.title = root.xpath('//dc:title', namespaces={'dc': NAMESPACES['dc']})[0].text
            self.author = root.xpath('//dc:creator', namespaces={'dc': NAMESPACES['dc']})[0].text
    
        def open(self, book_id=None):
            if book_id:
                self.book_id = book_id
            if not self.book_id:
                raise Exception('Book id not set')
    
            self.f = zipfile.ZipFile(self._FILE % self.book_id, 'r')
            soup = BeautifulStoneSoup(self.f.read('META-INF/container.xml'))
    
            oebps = soup.findAll('rootfile')[0]['full-path']
            folder = oebps.rfind(os.sep)
            self.oebps_folder = '' if folder == -1 else oebps[:folder+1]   # 找到oebps的文件夹名称
    
            oebps_content = self.f.read(oebps)
            self.read_doc_props(oebps_content)
    
            opf_bs = BeautifulStoneSoup(oebps_content)
            ncx = opf_bs.findAll('item', {'id': 'ncx'})[0]
            ncx = self.oebps_folder + ncx['href']     # 找到ncx的完整路径
    
            ncx_bs = BeautifulStoneSoup(self.f.read(ncx))
    
            self.chapters = [(nav.navlabel.text, nav.content['src']) for
                             nav in ncx_bs.findAll('navmap')[0].findAll('navpoint')]
    
    if __name__ == '__main__':
        book = Book('莎士比亚全集')
        print book.oebps_folder
    
        print book.title
        print book.author
    
        print str(book.chapters).decode("unicode-escape").encode("utf-8")
    

    接下来,是bookview.py

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    from PyQt4.QtGui import (QWidget, QPushButton, QHBoxLayout, QVBoxLayout,
                             QListWidget, QLabel, QSplitter)
    from PyQt4.QtWebKit import QWebView
    
    
    class BookView(QSplitter):
        def __init__(self, parent=None):
            super(BookView, self).__init__(parent=parent)
            self.create_layout()
    
        def create_layout(self):
            self.web_view = QWebView()
            self.chapter_list = QListWidget()
            self.next_button = QPushButton("Next chapter")
            self.previous_button = QPushButton("Previous chapter")
    
            hbox = QHBoxLayout()
            hbox.addStretch()
            hbox.addWidget(self.previous_button)
            hbox.addWidget(self.next_button)
    
            vbox = QVBoxLayout()
            vbox.addWidget(QLabel("Chapters"))
            vbox.addWidget(self.chapter_list)
            vbox.addLayout(hbox)
    
            widget = QWidget()
            widget.setLayout(vbox)
    
            self.addWidget(self.web_view)
            self.addWidget(widget)
    
    

    library.py :

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    import json
    import os
    import sys
    
    from PyQt4.QtGui import QTableWidget, QTableWidgetItem
    from PyQt4.QtCore import Qt
    
    parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.insert(0, parentdir)
    
    from constants import LIBRARY
    
    
    def get_library():
        with open(LIBRARY, 'r') as f:
            try:
                library = json.load(f)
            except Exception, e:
                print(e)
                library = {'books': []}
        return library
    
    
    def insert_library(book):
        u"""
    
        :param book: books.py中定义的类型, 有id, 有title, 有authors
        :return:
        """
        lib = get_library()
        book.open()
        lib['books'].append({'id': book.book_id, 'title': book.title, 'author': book.author})
    
        with open(LIBRARY, 'w') as f:
            json.dump(lib, f, indent=4)
    
    
    # 下面的GUI代码不应该跟逻辑代码写在一起,这里的写法不是好例子
    class LibraryTableWidget(QTableWidget):
    
        def __init__(self, book_view, parent=None):
            super(LibraryTableWidget, self).__init__(parent=None)
            self.book_view = book_view
    
            self.setColumnCount(2)
            self.refresh()
    
        def refresh(self):
            self.library = get_library()
    
            self.clear()
            self.setRowCount(len(self.library['books']))
            self.setHorizontalHeaderLabels(['Title', 'Authors'])
    
            for i, book in enumerate(self.library['books']):
                for j, cell in enumerate((book['title'], book['author'])):
                    item = QTableWidgetItem(cell)
                    item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsEnabled)
                    self.setItem(i, j, item)
    
            self.resizeColumnsToContents()
    
        def create_connections(self):
            pass
    
        def view_book(self):
            book_id = self.library['books'][self.currentRow()]['id']
            self.book_view.load_book(book_id)
    

    window.py

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    import os
    import sys
    import shutil
    
    from PyQt4.QtCore import Qt, SIGNAL, SLOT
    from PyQt4.QtGui import (QMainWindow, QDockWidget, QAction, QApplication,
                             QMessageBox, QFileDialog)
    
    from library import LibraryTableWidget, insert_library
    from bookview import BookView
    from books import Book
    
    parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.insert(0, parentdir)
    
    from constants import LIBRARY_DIR
    
    
    class MainWindow(QMainWindow):
    
        def __init__(self):
            super(MainWindow, self).__init__()
    
            self.create_layout()
            self.create_actions()
            self.create_menus()
            self.create_connections()
    
        def create_layout(self):
            self.book = BookView(self)
            self.setCentralWidget(self.book)
    
            self.create_library_dock()
    
        def create_library_dock(self):
            if getattr(self, 'dock', None):
                self.dock.show()
                return
    
            self.dock = QDockWidget("Library", self)
            self.dock.setAllowedAreas(Qt.LeftDockWidgetArea|Qt.RightDockWidgetArea)
            self.library = LibraryTableWidget(self.book)
            self.dock.setWidget(self.library)
            self.addDockWidget(Qt.LeftDockWidgetArea, self.dock)
    
        def create_menus(self):
            file_menu = self.menuBar().addMenu("&File")
            help_menu = self.menuBar().addMenu("&Help")
    
            file_menu.addAction(self.library_action)
            file_menu.addAction(self.open_action)
            file_menu.addSeparator()
            file_menu.addAction(self.quit_action)
    
            help_menu.addAction(self.help_action)
            help_menu.addAction(self.about_action)
    
        def create_actions(self):
            self.library_action = QAction("&Library", self)
            self.open_action = QAction("&Open", self)
            self.quit_action = QAction("&Quit", self)
    
            self.help_action = QAction("Help", self)
            self.about_action = QAction("&About", self)
    
    
        def create_connections(self):
            self.connect(self.library_action, SIGNAL("triggered()"), self.create_library_dock)
            self.connect(self.open_action, SIGNAL("triggered()"), self.open_book)
            self.connect(self.quit_action, SIGNAL("triggered()"), QApplication.instance(),
                         SLOT("closeAllWindows"))
            self.connect(self.about_action, SIGNAL("triggered()"), self.about)
            self.connect(self.help_action, SIGNAL("triggered()"), self.help)
    
        def about(self):
            QMessageBox.about(self, "QtBooks", "An ebook reader")
    
    
        def help(self):
            QMessageBox.information(self, 'Help', 'Nothing yet!')
    
        def open_book(self):
            book_path = QFileDialog.getOpenFileName(self, u'打开Epub格式电子书', ".", "(*.epub)")
    
            print u"in open_book, book_name is:" + str(book_path)
            print u"in open_book, bookdata path:" + str(LIBRARY_DIR)
            print os.path.dirname(str(book_path))
    
            if os.path.dirname(str(book_path))+os.sep != str(LIBRARY_DIR):
                shutil.copy(str(book_path), LIBRARY_DIR)
    
            file_name = os.path.basename(str(book_path))
            book_id = file_name.split('.epub')[0]
            book = Book(book_id)
            insert_library(book)
            self.library.refresh()        
    

    最后是main.py:

    # -*- coding: utf-8 -*-
    
    #!/usr/bin/env python
    
    import sys
    
    from PyQt4.QtGui import QApplication
    from src.window import MainWindow
    
    
    reload(sys)
    sys.setdefaultencoding('utf8')
    
    def main():
        app = QApplication(sys.argv)
        window = MainWindow()
        window.show()
        sys.exit(app.exec_())
    
    if __name__ == '__main__':
        main()
    

    MainWindow的部分除了GUI,还加上了几个无关紧要的弹出对话框的内容,涉及到Qt的信号槽机制,这部分留到下一篇。

    只加上了一段逻辑代码,可以打开epub文件,并将该文件复制到仓库中(文件系统中), 同时刷新LibraryTableWidget的内容,使得书名,作者显示出来:

    相关文章

      网友评论

          本文标题:用PyQt4+Python写一个简单的EPub阅读器(2/3)

          本文链接:https://www.haomeiwen.com/subject/uukolttx.html