######################################################### ## @file : kmanapp.py ## @desc : kindle note managerment tool GUI ## @create : 2020/06/03 ## @author : Chengan ## @email : douboer@gmail.com ######################################################### import sys import os import operator from time import sleep #import pandas as pd from mtable import mTable import threading #if hasattr(sys, 'frozen'): # os.environ['PATH'] = sys._MEIPASS + ";" + os.environ['PATH'] from PySide2.QtWidgets import (QMainWindow, QApplication, QMessageBox, QFileDialog, QLabel, QAbstractItemView, QHeaderView, QMenu) from PySide2.QtCore import (QAbstractTableModel, Signal, QSize, QTimer, Qt) from PySide2.QtGui import (QPalette, QStandardItemModel, QStandardItem, QIcon) from mainwindow import Ui_MainWindow from kman import * from parseweb import * # import binary resource file(kmanapp_rc.py) import kmanapp_rc notes_temp = """
《{bookname}》 {author} ({time}) 【{note}】


{content} 【P{position}】
""" infos_temp = """

{bookname}

作者 : {author}

评论数 : {ratenum}

评分 : {score}

出版社 : {publisher}

出版时间 : {publishing}

内容简介 : {description}

""" words_temp = """
{usage}

{bookname} {author} {category} {timestamp} {position}


""" ONLY_TEST = 1 class kmanWindow(QMainWindow): """ def __init__(self, *args, **kwargs): super(kmanWindow, self).__init__(*args, **kwargs) """ flag = True def __init__(self, parent=None): super(kmanWindow, self).__init__(parent) self.copyinfo = '' # create ui and initial it ui = Ui_MainWindow() ui.setupUi(self) self.ui = ui self.add_ui_component() # initial tree selected self.tree_selected = 'note_root' self.km = kMan() self.spide = bookInfoSpide() ### in order to smaller the package, ### substitute pandas table with mTable self.mt = mTable() self.books_info = defaultdict(dict) # initial check order: # 1. backup file bk.data -> # 2. kindle(My Clippings.txt) -> # 3. local file(config) -> flg = 0 if os.path.exists(BACKUPNOTEFN) and os.path.exists(BACKUPWORDFN): self.books_data = self.km.json2dict(BACKUPNOTEFN) self.words_data = self.km.json2dict(BACKUPWORDFN) if self.books_data and len(self.books_data) >=1 and \ self.words_data and len(self.words_data[0])>=1: flg = 1 if not flg: if self.km.get_kindle_path(): self.import_kindle() else: self.books_data = self.km.import_clips() self.words_data = self.km.import_words() [self.filter_books, self.filter_list] = self.km.filter_clips(self.books_data) #self.filter_list = self.km.filter_words(self.words_data) # initial books information which grab from douban or amazon # if the information exist in backup file, initial with this file, # and grap new book's information from douban # else grap all book information from douban try: if os.path.exists(BACKUPINFOFN): self.books_info = self.km.json2dict(BACKUPINFOFN) increase_book_list = self.check_increase_books(self.books_data, self.books_info) if len(increase_book_list) > 0: trd = threading.Thread(target=self.grab_books_info, args=(increase_book_list,)) trd.setDaemon(True) trd.start() else: booklist = list(self.books_data.keys()) trd = threading.Thread(target=self.grab_books_info, args=(book_list,)) trd.setDaemon(True) trd.start() except Exception as e: print(e) finally: pass self.fill_treeview() self.refresh_ui_component(comp=1) # timer to check status of kindle self.timer = QTimer(self) self.timer.timeout.connect(self.show_status_info) self.timer.start(1000) # connect action/toolbutton to slot functions ui.actionimportkindle.triggered.connect(lambda: self.import_kindle()) ui.actionimportlocal.triggered.connect(lambda: self.import_local()) ui.actionconfig.triggered.connect(lambda: self.config()) ui.actionwords.triggered.connect(lambda: self.words()) ui.actionstatistic.triggered.connect(lambda: self.statistic()) ui.actionhomepage.triggered.connect(lambda: self.homepage()) ui.actionabout.triggered.connect(lambda: self.about()) ui.actionflush.triggered.connect(lambda: self.refresh()) ui.actionexport.triggered.connect(lambda: self.export()) #ui.searchLineEdit.returnPressed.connect(self.search_return_press()) ui.searchComboBox.currentIndexChanged.connect(self.search_scope_change) ui.searchToolButton.clicked.connect(self.search_button_clicked) #ui.treeView.clicked.connect(self.tree_item_clicked) ui.treeView.selectionModel().selectionChanged.connect(self.tree_item_selectionchanged) ui.tableView.clicked.connect(self.table_item_clicked) ui.tableView.horizontalHeader().setStretchLastSection(True) #ui.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) ui.tableView.verticalHeader().hide() ui.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) ui.tableView.setColumnWidth(0, 40) # type ui.tableView.setColumnWidth(2, 50) # author ui.tableView.selectRow(0) # initial tableView data = self.convert_to_panda(self.filter_list) ui.tablemodel = nTableModel(data) ui.tableView.verticalHeader().hide() ui.tableView.setModel(self.ui.tablemodel) ui.tableView.setSortingEnabled(True) ui.tableView.selectionModel().selectionChanged.connect(self.table_item_selectionchanged) #ui.tableView.contextMenuEvent().selectionChanged.connect(self.table_item_selectionchanged) # XXXX self.ui.textEdit.installEventFilter(self) # XXXX def eventFilter(self, source, event): if source==self.ui.textEdit: """ if (event.type() == QEvent.Type.InputMethodQuery): self.anchor = self.anchorAt(e.pos()) if self.anchor: QApplication.setOverrideCursor(Qt.PointingHandCursor) elif (event.type() == QEvent.Type.InputMethodQuery): if self.anchor: QDesktopServices.openUrl(QUrl(self.anchor)) QApplication.setOverrideCursor(Qt.ArrowCursor) self.anchor = None """ else: super(kmanWindow, self).eventFilter(source, event) def check_increase_books(self, bks, bksinfo): new_list = list(bks.keys()) # kindle's books with note new_list = [re.split(r'[\((\-\::_\s]',nn.strip())[0] for nn in new_list] last_list = list(bksinfo.keys()) # grab book information last time increase_list = [] for nn in new_list: flag = 0 for gg in last_list: if nn in gg: flag = 1 if flag == 0: increase_list.append(nn) return increase_list def add_ui_component(self): self.ui.searchComboBox.addItems([u'ALL',u'BOOKNAME',u'CONTENT',u'AUTHOR']) self.ui.treeView.resize(200,200) # status bar self.ui.status_label = QLabel() self.ui.pe = QPalette() def refresh_ui_component(self, comp=0): """ refresh treeView, tableview, textedit information after import or open clips file Args: comp 0 - treeview + tablevew + textedit , initial, fill_treeview() move to __init__() 1 - tablevew + textedit , note tree view clicked 2 - textedit , note table view clicked 3 - tablevew + textedit , word tree view clicked 4 - textedit , word table view clicked 5 - textedit , info tree view clicked """ # refresh tableview click note tree if comp in [1,2]: data = self.convert_to_panda(self.filter_list,0) # refresh tableview click word tree elif comp in [3,4]: data = self.convert_to_panda(self.filter_list,1) # refresh tableview content if hasattr(self.ui, 'tablemodel'): del self.ui.tablemodel self.ui.tablemodel = nTableModel(data) self.ui.tableView.verticalHeader().hide() self.ui.tableView.setModel(self.ui.tablemodel) #self.ui.tablemodel.tabledata_update.connect(self.tabledata_update_slot) self.ui.tableView.selectionModel().selectionChanged.connect(self.table_item_selectionchanged) # refresh textedit content if comp in [1,2]: if len(self.filter_list)>0: [stype,sbookname,sauthor,sposition,stime,scontent] = self.filter_list[0] tt = notes_temp.format( bookname=sbookname, author=sauthor, time=stime, note=stype, content=scontent, position=sposition) print(re.split(r'[\((\-\::_\s]',sbookname.strip())[0]) self.ui.textEdit.setHtml(tt) self.copyinfo = \ """ {content} \n\n--《{bookname}》{author} ({time}) 【{note} P{position}】""". \ format( content=scontent, bookname=sbookname, author=sauthor, time=stime, note=stype, position=sposition ) elif comp in [3,4]: self.render_textedit_words(self.words_data) elif comp == 5: self.render_textedit_infos(self.books_info) self.show_status_info() def render_textedit_words(self, wdata, mrow=100): [bookinfo, words, lookups] = wdata index = self.ui.tableView.currentIndex() if index.row() == -1: word = self.filter_list[0][0] else: word = index.sibling(index.row(), 0).data() txt = "" self.copyinfo = '' for row in lookups: if words[row[1]]['word'] == word: [susage, stimestamp, sbookname, sauthor, scategory, sposition] = \ [row[4],row[5],bookinfo[row[2]]['bookname'], \ bookinfo[row[2]]['author'], words[row[1]]['category'],row[3]] txt += words_temp.format( usage=susage,bookname=sbookname, author=sauthor,category=scategory, timestamp=stimestamp,position=sposition) self.copyinfo += \ """{usage} \n\n-- {bookname} {author} {category} {timestamp} {position} \n """ \ .format( usage=susage,bookname=sbookname, \ author=sauthor,category=scategory, \ timestamp=stimestamp,position=sposition) self.ui.textEdit.setHtml(txt) ''' { "金融的本质": { "link": "https://book.douban.com/subject/25843334", "bookname": "金融的本质", "img": "https://img9.doubanio.com/view/subject/s/public/s27246465.jpg", "score": "8.2", "ratenum": "893", "publisher": "中信出版社", "publishing": "2014", "description": "xxx", "author": "【美】伯南克/巴曙松" } ''' def render_textedit_infos(self, idata, selectitem): """ render textedit for book information Args: idata - books_info selectitem - text of treeitem selected """ if selectitem in list(idata.keys()): vv = idata[selectitem] self.ui.textEdit.setOpenExternalLinks(True) self.ui.textEdit.setHtml(infos_temp.format(link=vv['link'], bookname=vv['bookname'], author=vv['author'], ratenum=vv['ratenum'], score=vv['score'], publisher=vv['publisher'], publishing=vv['publishing'], description=vv['description'], img=vv['img'].split('/')[-1])) self.copyinfo = """ 1 | :green_book: | [{bookname}]({link}) | """ \ """{author}/{score}/{publisher}/{publishing} {description}""" \ .format( bookname=vv['bookname'], \ link=vv['link'], author=vv['author'], score=vv['score'], publisher=vv['publisher'], publishing=vv['publishing'], description=vv['description'],) else: pass def convert_to_panda(self, mlist, tp=0): if tp==0: #### XXX remove pandas #pdframe = pd.DataFrame(mlist, \ # columns = ['Type','Bookname','Author','Position','Date','Content']) self.mt.dataframe(mlist, \ columns = ['Type','Bookname','Author','Position','Date','Content']) else: #pdframe = pd.DataFrame(mlist, \ # columns = ['Word','Bookname','Author','Category']) self.mt.dataframe(mlist, \ columns = ['Word','Bookname','Author','Category']) return self.mt def tabledata_update_slot(self, s): print('call tabledata_update_slot() {}'.format(s)) def tree_item_selectionchanged(self, selected, deselected): # selected, deselected are QItemSelection objects model_index_list = selected.indexes() # QModelIndexList if not model_index_list: return 0 model_index = model_index_list[0] # QModelIndex self.tree_item_clicked(model_index) def tree_item_clicked(self, modelidx): # modelidx is a QModelIndex object model_index_list = self.ui.treeView.selectedIndexes() # QModelIndexList model_index = model_index_list[0] # QModelIndex itemmodel = model_index.model() #QAbstractItemModel/QStandardItemModel item = itemmodel.itemFromIndex(model_index) #QStandardItem self.tree_selected = item.accessibleDescription() print(self.tree_selected, item.text()) # filter clips self.filter_list = None info = re.split(r'\s+',item.text())[0] comp = 0 if self.tree_selected.split('_')[0] == 'info': self.ui.tableView.setVisible(False) self.ui.tableView.clearSelection() self.render_textedit_infos(self.books_info, item.text()) comp = 5 else: self.ui.tableView.setVisible(True) # refresh filter clips(tableview) after tree item clicked if self.tree_selected in ['note_root', 'note_bookname', 'note_author']: [self.filter_books, self.filter_list] = self.km.filter_clips(self.books_data) comp = 1 elif self.tree_selected == 'note_bleaf': # bookname leaf comp = 1 [self.filter_books, self.filter_list] = self.km.filter_clips(self.books_data, info, 1) elif self.tree_selected == 'note_aleaf': # author leaf comp = 1 [self.filter_books, self.filter_list] = self.km.filter_clips(self.books_data, info, 2) elif self.tree_selected in ['word_root', 'word_bookname']: self.filter_list = self.km.filter_words(self.words_data) comp = 3 elif self.tree_selected == 'word_leaf': # word bookname leaf comp = 3 self.filter_list = self.km.filter_words(self.words_data, info) else: return if comp == 3: self.ui.tableView.setColumnWidth(1, 50) # author self.ui.tableView.setColumnWidth(3, 50) # category # QHeaderView::Interactive 0 # QHeaderView::Stretch 1 # QHeaderView::Fixed 2 # QHeaderView::ResizeToContents 3 self.ui.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) if comp == 1: self.ui.tableView.setColumnWidth(0, 40) # type self.ui.tableView.setColumnWidth(2, 50) # author self.ui.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) self.refresh_ui_component(comp) def table_item_selectionchanged(self, selected, deselected): # selected, deselected are QItemSelection objects model_index_list = selected.indexes() # QModelIndexList if not model_index_list: return 0 model_index = model_index_list[0] # QModelIndex self.table_item_clicked(model_index) def table_item_clicked(self, index): """ print('tableView.currentIndex().row() {} tableView.currentIndex().column() {}' .format(self.ui.tableView.currentIndex().row(), self.ui.tableView.currentIndex().column())) """ if not index.isValid(): return ss = self.tree_selected.split('_')[0] if ss=='note': row = index.row() stype = index.sibling(row, 0).data() sbookname = index.sibling(row, 1).data() sauthor = index.sibling(row, 2).data() sposition = index.sibling(row, 3).data() stime = index.sibling(row, 4).data() scontent = index.sibling(row, 5).data() self.ui.textEdit.setHtml(notes_temp.format( bookname=sbookname, author=sauthor, time=stime, note=stype, content=scontent, position=sposition)) self.copyinfo = \ """ {content} \n\n-- 《{bookname}》{author} ({time}) 【{note} P{position}】""". \ format( content=scontent, bookname=sbookname, author=sauthor, time=stime, note=stype, position=sposition ) elif ss=='word': self.render_textedit_words(self.words_data) def search_button_clicked(self): search_word = self.ui.searchLineEdit.text() if search_word.strip() == '': self.messagebox(ico=2, info=u'\n\n search content is empty!') return 0 content_type = self.ui.searchComboBox.currentText() #content_idx = self.ui.searchComboBox.currentIndex() [nu, sbks] = self.km.search_clip(self.books_data, search_word, 'ALL', content_type) [self.filter_books, self.filter_list] = self.km.filter_clips(sbks) self.refresh_ui_component(1) print( 'call search_button_clicked()' ) def keyPressEvent(self, event): #print('hasfocus {} key {} {} {}'.format(self.ui.searchLineEdit.hasFocus(),\ # event.key(), Qt.Key_Return, event.key()==Qt.Key_Return)) if (self.ui.searchLineEdit.hasFocus() and event.key() == Qt.Key_Return): print('call keyPressEvent() {} '.format(event.key())) self.search_button_clicked() def contextMenuEvent(self, event): # do not work!? #if(self.ui.tableView.geometry().contains(event.pos())): # print('contextMenuEvent pos {}'.format(event.globalPos())) tree_index_list = self.ui.treeView.selectedIndexes() # QModelIndexList table_index_list = self.ui.tableView.selectedIndexes() # tableview item selected if table_index_list: model_index = table_index_list[0] # QModelIndex menu = QMenu() copy_action = menu.addAction(u"Copy") action = menu.exec_(self.mapToGlobal(event.pos())) if action ==copy_action: self.copy_action(model_index.row(), model_index.column()) elif tree_index_list: model_index = tree_index_list[0] # QModelIndex itemmodel = model_index.model() #QAbstractItemModel/QStandardItemModel item = itemmodel.itemFromIndex(model_index) #QStandardItem pp = self.tree_selected.split('_')[0] menu = QMenu() [copy_info_action, export_note_action, export_word_action] = \ [None, None, None] if pp=='info': copy_info_action = menu.addAction(u"Copy") elif pp=='note': export_note_action = menu.addAction(u"Export Notes") elif pp=='word': export_word_action = menu.addAction(u"Export Notes") else: pass action = menu.exec_(self.mapToGlobal(event.pos())) fn = self.tree_selected+item.text().split('(')[0].strip() if action==copy_info_action: action = menu.exec_(self.mapToGlobal(event.pos())) self.copy_action(model_index.row(), model_index.column()) elif action==export_note_action: self.export_filter_notes(fn) elif action==export_word_action: self.export_filter_words(fn) else: pass else: pass print('contextMenuEvent pos {}'.format(event.globalPos())) def copy_action(self, row, column): import clipboard clipboard.copy(self.copyinfo) print('copy_action row {} column {}'.format(row, column)) def search_return_press(self): self.search_button_clicked() print('call search_return_press()') # XXX def search_scope_change(self,t): p = {0:'ALL',1:'TITLE',2:'AUTHOR',3:'CONTENT'} s = self.ui.searchLineEdit.text() #print(self.books_data) #print(search_clip(self.books_data,s,'ALL',p[t])) print('call search_scope_change()') ## XXX ''' def check_kindle_status(self): while self.flag: self.show_status_info() sleep(2) ''' def show_status_info(self): """ show status information on statusbar Args: conn: 1 if kindle is connected else 0 Return: conn """ status = self.km.status_info() self.ui.statusbar.showMessage(status[0],0) self.ui.status_label.setText(status[1]) if not self.km.status: self.ui.pe.setColor(QPalette.WindowText,Qt.red) #self.ui.status_label.setAutoFillBackground(True) self.ui.status_label.setPalette(pe) self.ui.statusbar.addPermanentWidget(self.ui.status_label, stretch=0) # define slot functions def import_kindle(self): fp = self.km.get_kindle_path() if not fp: self.messagebox(ico=2, info='\n\n kindle is not connected') return 0 self.books_data = self.km.import_clips(os.path.join(fp,'documents',CLIPFN)) self.words_data = self.km.import_words(os.path.join(fp,'system','vocabulary',WORDFN)) [self.filter_books, self.filter_list] = self.km.filter_clips(self.books_data) self.filter_wordlist = self.km.filter_words(self.words_data) self.fill_treeview() self.refresh_ui_component(1) def import_local(self): fn, ft = QFileDialog.getOpenFileName(self, "choose file to import", './', # start path "All Files (*);;Text Files (*.txt)") # filter file type self.fn = fn self.books_data = self.km.import_clips(fn) [self.filter_books, self.filter_list] = self.km.filter_clips(self.books_data) self.fill_treeview() self.refresh_ui_component(1) #print('filename ', fn, 'filetype ', ft) if fn == "": return False def fill_treeview(self): self.ui.model = QStandardItemModel() rootItem = self.ui.model.invisibleRootItem() item = QStandardItem('All Notes ({})'.format(self.km.get_totalnum_nt(self.books_data))) icon = QIcon() icon.addFile(u":/icons/emblem_library.png", QSize(), QIcon.Normal, QIcon.Off) item.setIcon(icon) item.setAccessibleDescription('note_root') rootItem.appendRow(item) parent_item = item # add bookname tree [numbooks, booknum] = self.km.get_bookname_num(self.books_data) bookname_item = QStandardItem('Bookname ({})'.format(numbooks)) icon = QIcon() icon.addFile(u":/icons/book_open.png", QSize(), QIcon.Normal, QIcon.Off) bookname_item.setIcon(icon) bookname_item.setAccessibleDescription('note_bookname') parent_item.appendRow(bookname_item) if numbooks > 0: for k, v in booknum.items(): item = QStandardItem('{} ({})'.format(k, v)) icon = QIcon() icon.addFile(u":/icons/book_open_bookmark.png", QSize(), QIcon.Normal, QIcon.Off) item.setIcon(icon) item.setAccessibleDescription('note_bleaf') bookname_item.appendRow(item) # add author tree [numauthor, authnum] = self.km.get_author_num(self.books_data) author_item = QStandardItem('Author ({})'.format(numauthor)) icon = QIcon() icon.addFile(u":/icons/person.png", QSize(), QIcon.Normal, QIcon.Off) author_item.setIcon(icon) author_item.setAccessibleDescription('note_author') parent_item.appendRow(author_item) parent_item = author_item if numauthor > 0: for k, v in authnum.items(): item = QStandardItem('{} ({})'.format(k, v)) icon = QIcon() icon.addFile(u":/icons/user.png", QSize(), QIcon.Normal, QIcon.Off) item.setIcon(icon) item.setAccessibleDescription('note_aleaf') author_item.appendRow(item) # add words root word_rootItem = self.ui.model.invisibleRootItem() [numwords, wordnum] = self.km.get_book_word_num(self.words_data) item = QStandardItem('All Words({})'.format(numwords)) icon = QIcon() icon.addFile(u":/icons/emblem_library.png", QSize(), QIcon.Normal, QIcon.Off) item.setIcon(icon) item.setAccessibleDescription('word_root') word_rootItem.appendRow(item) word_parent_item = item # add word bookname tree word_bookname_item = QStandardItem('Bookname ({})'.format(numbooks)) icon = QIcon() icon.addFile(u":/icons/book_open.png", QSize(), QIcon.Normal, QIcon.Off) word_bookname_item.setIcon(icon) word_bookname_item.setAccessibleDescription('word_bookname') word_parent_item.appendRow(word_bookname_item) if numwords > 0: for k, v in wordnum.items(): item = QStandardItem('{} ({})'.format(k, v)) icon = QIcon() icon.addFile(u":/icons/book_open_bookmark.png", QSize(), QIcon.Normal, QIcon.Off) item.setIcon(icon) item.setAccessibleDescription('word_leaf') word_bookname_item.appendRow(item) # add infos root self.ui.tableView.setVisible(True) info_rootItem = self.ui.model.invisibleRootItem() item = QStandardItem('All Books({})'.format(len(self.books_info.keys()))) icon = QIcon() icon.addFile(u":/icons/amazon.png", QSize(), QIcon.Normal, QIcon.Off) item.setIcon(icon) item.setAccessibleDescription('info_root') info_rootItem.appendRow(item) info_parent_item = item # add book info tree info_bookname_item = QStandardItem('Douban ({})'.format(numbooks)) icon = QIcon() icon.addFile(u":/icons/book_open.png", QSize(), QIcon.Normal, QIcon.Off) info_bookname_item.setIcon(icon) info_bookname_item.setAccessibleDescription('info_bookname') info_parent_item.appendRow(info_bookname_item) for k in self.books_info.keys(): item = QStandardItem('{}'.format(k)) icon = QIcon() icon.addFile(u":/icons/list.png", QSize(), QIcon.Normal, QIcon.Off) item.setIcon(icon) item.setAccessibleDescription('info_leaf') info_bookname_item.appendRow(item) self.ui.treeView.setModel(self.ui.model) self.ui.treeView.expandAll() def config(self): print("call slot config()") pass def words(self): print("call slot words()") pass def statistic(self): print("call slot statistic()") pass def homepage(self): import webbrowser webbrowser.open('https://gitee.com/douboer/kman') print("call slot homepage()") pass def about(self): self.messagebox(ico=1, info='\n'+ \ ' kindle management tool \n\n' + \ ' v1.0.4\n\n' + \ ' Author: chengan\n\n' + \ ' douboer@gmail.com') print("call slot about()") pass def refresh(self): self.import_kindle() print("call slot refresh()") pass def export(self): pp = self.tree_selected.split('_')[0] if pp=='note': self.export_filter_notes('export') elif pp=='word': self.export_filter_words('export') else: # info pass print("call export()") def export_filter_notes(self, fn): self.km.export_notes(self.filter_books, fn, ft='MD') pass def export_filter_words(self, fn): self.km.export_words(self.words_data, self.filter_list, fn, ft='MD') pass def messagebox(self, ico=1, info=''): """ unify messagebox Args: ico - QMessageBox.NoIcon 0 QMessageBox.Information 1 QMessageBox.Warning 2 QMessageBox.Critical 3 QMessageBox.Question 4 """ icons = {0:QMessageBox.NoIcon, \ 1:QMessageBox.Information, \ 2:QMessageBox.Warning, \ 3:QMessageBox.Critical, \ 4:QMessageBox.Question } msgBox = QMessageBox() msgBox.setText(info) msgBox.setInformativeText("") msgBox.setIcon(icons[ico]) msgBox.setStandardButtons(QMessageBox.Cancel | QMessageBox.Ok) msgBox.setBaseSize(QSize(600, 300)) r = msgBox.exec() # backup file when kman closed # so we can read backup file when kman start def closeEvent(self, e): if not os.path.exists(BACKUPFOLDER): os.mkdir(BACKUPFOLDER) with open(BACKUPNOTEFN, 'w', encoding='utf8', errors='ignore') as fw: fw.write(self.km.dict2json(self.books_data)) with open(BACKUPWORDFN, 'w', encoding='utf8', errors='ignore') as fw: fw.write(self.km.dict2json(self.words_data)) with open(BACKUPINFOFN, 'w', encoding='utf8', errors='ignore') as fw: fw.write(self.km.dict2json(self.books_info)) # stop check thread self.flag = False def grab_books_info(self, booklist): bks_info = {} for bkname in booklist: bkname = re.split(r'[\((\-\::_\s]',bkname.strip())[0] print(bkname) bkinfo = self.spide.grab_book_info(bkname) filter_bkinfo = self.spide.filter_spide_book(bkinfo) if filter_bkinfo: bks_info.update(filter_bkinfo) self.spide.down_book_img(filter_bkinfo) if bks_info: return self.books_info.update(bks_info) # thanks Martin Fitzpatrick ^_^ # https://www.learnpyqt.com/courses/model-views/qtableview-modelviews-numpy-pandas/ class nTableModel(QAbstractTableModel): tabledata_update = Signal(str) def __init__(self, data): super(nTableModel, self).__init__() self._data = data def data(self, index, role): if role == Qt.DisplayRole: #### XXX remove pandas #value = self._data.iloc[index.row(), index.column()] value = self._data.get_iat(index.row(), index.column()) self.tabledata_update[str].emit('{} {}'.format(index.row(),index.column())) return str(value) def rowCount(self, index): #### XXX remove pandas #return self._data.shape[1] return self._data.get_num_rows() def columnCount(self, index): #### XXX remove pandas #return self._data.shape[1] return self._data.get_num_columns() def headerData(self, section, orientation, role): # section is the index of the column/row. if role == Qt.DisplayRole: if orientation == Qt.Horizontal: #### XXX remove pandas #return str(self._data.columns[section]) return str(self._data.get_columns()[section]) if orientation == Qt.Vertical: #### XXX remove pandas #return str(self._data.index[section]) return str(self._data.get_index()[section]) # cheer!!! def sort(self, column, order): """Sort table by given column number. """ self.layoutAboutToBeChanged.emit() self._data.set_data(sorted(self._data.get_data(), key=operator.itemgetter(column))) if order == Qt.DescendingOrder: self._data.reverse() self.layoutChanged.emit() if __name__ == "__main__": print('sys.path[0]', sys.path[0]) print('sys.argv[0]', sys.argv[0]) print('os.path.realpath(sys.executable)', os.path.realpath(sys.executable)) print('os.path.realpath(sys.argv[0]))', os.path.realpath(sys.argv[0])) print('os.path.dirname(os.path.realpath(sys.executable))', os.path.dirname(os.path.realpath(sys.executable))) print('os.path.dirname(os.path.realpath(sys.argv[0]))', os.path.dirname(os.path.realpath(sys.argv[0]))) util = Util() print('get_app_path',util.get_app_path()) app = QApplication(sys.argv) kmw = kmanWindow() icon = QIcon() icon.addFile(u":/icons/Cbb20.png", QSize(), QIcon.Normal, QIcon.Off) kmw.setWindowIcon(icon) kmw.setWindowTitle("kindle management") kmw.resize(1200, 800) #kmw.showFullScreen() kmw.show() """ # move to __init__() try: booklist = self.books_data.keys() trd = threading.Thread(target=kmw.grab_books_info, args=(booklist,)) trd.setDaemon(True) trd.start() except Exception as e: print(e) pass finally: pass """ # loop check kindle is connected or not # BUG to be implement XXXX """ try: t = threading.Thread(target=kmw.check_kindle_status) t.start() except: print ("Error: can not start thread") """ app.exec_()