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

2016-04-16  本文已影响812人  knarfeh

上一篇,这一篇我们写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的内容,使得书名,作者显示出来:

上一篇下一篇

猜你喜欢

热点阅读