Initial import of new GUI. Nowhere near functional
| @ -233,8 +233,6 @@ class HTMLConverter(object): | |||||||
|                       (re.compile(r'page-break-before:\s*\w+([\s;\}])'),  |                       (re.compile(r'page-break-before:\s*\w+([\s;\}])'),  | ||||||
|                        lambda match: match.group(1)) ]  |                        lambda match: match.group(1)) ]  | ||||||
|      |      | ||||||
|      |  | ||||||
|      |  | ||||||
|     class Link(object): |     class Link(object): | ||||||
|         def __init__(self, para, tag): |         def __init__(self, para, tag): | ||||||
|             self.para = para |             self.para = para | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								src/libprs500/gui2/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,8 @@ | |||||||
|  | main_ui.py : main.ui | ||||||
|  | 	pyuic4 main.ui | sed -s s/^.*Margin.*// | sed -e s/^.*boxlayout\.setObjectName.*// > main_ui.py | ||||||
|  | 
 | ||||||
|  | images_rc.py : images.qrc | ||||||
|  | 	pyrcc4 images.qrc > images_rc.py | ||||||
|  | 
 | ||||||
|  | clean : | ||||||
|  | 	rm main_ui.py images_rc.py | ||||||
							
								
								
									
										16
									
								
								src/libprs500/gui2/images.qrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,16 @@ | |||||||
|  | <RCC> | ||||||
|  |     <qresource prefix="/" > | ||||||
|  |         <file>images/addfile.png</file> | ||||||
|  |         <file alias="default_cover" >images/cherubs.jpg</file> | ||||||
|  |         <file>images/clear.png</file> | ||||||
|  |         <file>images/delfile.png</file> | ||||||
|  |         <file>images/edit.png</file> | ||||||
|  |         <file>images/fileopen.png</file> | ||||||
|  |         <file alias="library" >images/library.png</file> | ||||||
|  |         <file alias="card" >images/memory_stick_unmount.png</file> | ||||||
|  |         <file>images/minus.png</file> | ||||||
|  |         <file>images/plus.png</file> | ||||||
|  |         <file>images/upload.png</file> | ||||||
|  |         <file alias="reader" >images/reader.png</file> | ||||||
|  |     </qresource> | ||||||
|  | </RCC> | ||||||
							
								
								
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/addfile.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/cherubs.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 19 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/clear.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/delfile.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 11 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/edit.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/fileopen.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/library.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 31 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/memory_stick_unmount.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/minus.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 9.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/plus.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/reader.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/libprs500/gui2/images/upload.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 10 KiB | 
							
								
								
									
										86
									
								
								src/libprs500/gui2/main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,86 @@ | |||||||
|  | ##    Copyright (C) 2007 Kovid Goyal kovid@kovidgoyal.net | ||||||
|  | ##    This program is free software; you can redistribute it and/or modify | ||||||
|  | ##    it under the terms of the GNU General Public License as published by | ||||||
|  | ##    the Free Software Foundation; either version 2 of the License, or | ||||||
|  | ##    (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ##    This program is distributed in the hope that it will be useful, | ||||||
|  | ##    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ##    GNU General Public License for more details. | ||||||
|  | ## | ||||||
|  | ##    You should have received a copy of the GNU General Public License along | ||||||
|  | ##    with this program; if not, write to the Free Software Foundation, Inc., | ||||||
|  | ##    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.Warning | ||||||
|  | import os, tempfile, sys | ||||||
|  | 
 | ||||||
|  | from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \ | ||||||
|  |                          QSettings, QVariant, QSize, QEventLoop, QString, \ | ||||||
|  |                          QBuffer, QIODevice, QModelIndex | ||||||
|  | from PyQt4.QtGui import QPixmap, QErrorMessage, QLineEdit, \ | ||||||
|  |                         QMessageBox, QFileDialog, QIcon, QDialog, QInputDialog | ||||||
|  | from PyQt4.Qt import qDebug, qFatal, qWarning, qCritical | ||||||
|  | 
 | ||||||
|  | from libprs500.gui2 import APP_TITLE, installErrorHandler | ||||||
|  | from libprs500.gui2.main_ui import Ui_MainWindow | ||||||
|  | 
 | ||||||
|  | class Main(QObject, Ui_MainWindow): | ||||||
|  |      | ||||||
|  |     def __init__(self, window): | ||||||
|  |         QObject.__init__(self) | ||||||
|  |         Ui_MainWindow.__init__(self) | ||||||
|  |         self.window = window | ||||||
|  |         self.setupUi(window) | ||||||
|  |         self.read_settings() | ||||||
|  |          | ||||||
|  |         ####################### Setup books view ######################## | ||||||
|  |         self.library_view.set_database(self.database_path) | ||||||
|  |         self.library_view.connect_to_search_box(self.search) | ||||||
|  |          | ||||||
|  |         window.closeEvent = self.close_event | ||||||
|  |         window.show() | ||||||
|  |         self.library_view.migrate_database() | ||||||
|  |         self.library_view.sortByColumn(3, Qt.DescendingOrder)         | ||||||
|  |         self.library_view.resizeColumnsToContents() | ||||||
|  |         self.library_view.resizeRowsToContents() | ||||||
|  |         self.search.setFocus(Qt.OtherFocusReason) | ||||||
|  |          | ||||||
|  |     def read_settings(self): | ||||||
|  |         settings = QSettings() | ||||||
|  |         settings.beginGroup("MainWindow") | ||||||
|  |         self.window.resize(settings.value("size", QVariant(QSize(1000, 700))).\ | ||||||
|  |                             toSize()) | ||||||
|  |         settings.endGroup() | ||||||
|  |         self.database_path = settings.value("database path", QVariant(os.path\ | ||||||
|  |                                     .expanduser("~/library1.db"))).toString() | ||||||
|  |      | ||||||
|  |     def write_settings(self): | ||||||
|  |         settings = QSettings() | ||||||
|  |         settings.beginGroup("MainWindow") | ||||||
|  |         settings.setValue("size", QVariant(self.window.size())) | ||||||
|  |         settings.endGroup() | ||||||
|  |      | ||||||
|  |     def close_event(self, e): | ||||||
|  |         self.write_settings() | ||||||
|  |         e.accept() | ||||||
|  | 
 | ||||||
|  | def main(): | ||||||
|  |     lock = os.path.join(tempfile.gettempdir(),"libprs500_gui_lock") | ||||||
|  |     if os.access(lock, os.F_OK): | ||||||
|  |         print >>sys.stderr, "Another instance of", APP_TITLE, "is running" | ||||||
|  |         print >>sys.stderr, "If you are sure this is not the case then "+\ | ||||||
|  |                             "manually delete the file", lock | ||||||
|  |         sys.exit(1) | ||||||
|  |     from PyQt4.Qt import QApplication, QMainWindow | ||||||
|  |     app = QApplication(sys.argv) | ||||||
|  |     window = QMainWindow() | ||||||
|  |     window.setWindowTitle(APP_TITLE) | ||||||
|  |     #window.setWindowIcon(QIcon(":/icon")) | ||||||
|  |     installErrorHandler(QErrorMessage(window)) | ||||||
|  |     QCoreApplication.setOrganizationName("KovidsBrain") | ||||||
|  |     QCoreApplication.setApplicationName(APP_TITLE) | ||||||
|  |     Main(window)  | ||||||
|  |     return app.exec_() | ||||||
|  |          | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     main() | ||||||
							
								
								
									
										405
									
								
								src/libprs500/gui2/main.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,405 @@ | |||||||
|  | <ui version="4.0" > | ||||||
|  |  <author>Kovid Goyal</author> | ||||||
|  |  <class>MainWindow</class> | ||||||
|  |  <widget class="QMainWindow" name="MainWindow" > | ||||||
|  |   <property name="geometry" > | ||||||
|  |    <rect> | ||||||
|  |     <x>0</x> | ||||||
|  |     <y>0</y> | ||||||
|  |     <width>728</width> | ||||||
|  |     <height>822</height> | ||||||
|  |    </rect> | ||||||
|  |   </property> | ||||||
|  |   <property name="sizePolicy" > | ||||||
|  |    <sizepolicy vsizetype="Preferred" hsizetype="Preferred" > | ||||||
|  |     <horstretch>0</horstretch> | ||||||
|  |     <verstretch>0</verstretch> | ||||||
|  |    </sizepolicy> | ||||||
|  |   </property> | ||||||
|  |   <property name="windowTitle" > | ||||||
|  |    <string/> | ||||||
|  |   </property> | ||||||
|  |   <property name="windowIcon" > | ||||||
|  |    <iconset/> | ||||||
|  |   </property> | ||||||
|  |   <widget class="QWidget" name="centralwidget" > | ||||||
|  |    <layout class="QVBoxLayout" > | ||||||
|  |     <property name="spacing" > | ||||||
|  |      <number>6</number> | ||||||
|  |     </property> | ||||||
|  |     <property name="leftMargin" > | ||||||
|  |      <number>9</number> | ||||||
|  |     </property> | ||||||
|  |     <property name="topMargin" > | ||||||
|  |      <number>9</number> | ||||||
|  |     </property> | ||||||
|  |     <property name="rightMargin" > | ||||||
|  |      <number>9</number> | ||||||
|  |     </property> | ||||||
|  |     <property name="bottomMargin" > | ||||||
|  |      <number>9</number> | ||||||
|  |     </property> | ||||||
|  |     <item> | ||||||
|  |      <layout class="QHBoxLayout" > | ||||||
|  |       <property name="spacing" > | ||||||
|  |        <number>6</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="leftMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="topMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="rightMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="bottomMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <item> | ||||||
|  |        <widget class="DeviceView" name="device_tree" > | ||||||
|  |         <property name="sizePolicy" > | ||||||
|  |          <sizepolicy vsizetype="Preferred" hsizetype="Preferred" > | ||||||
|  |           <horstretch>0</horstretch> | ||||||
|  |           <verstretch>0</verstretch> | ||||||
|  |          </sizepolicy> | ||||||
|  |         </property> | ||||||
|  |         <property name="maximumSize" > | ||||||
|  |          <size> | ||||||
|  |           <width>10000</width> | ||||||
|  |           <height>90</height> | ||||||
|  |          </size> | ||||||
|  |         </property> | ||||||
|  |         <property name="verticalScrollBarPolicy" > | ||||||
|  |          <enum>Qt::ScrollBarAlwaysOff</enum> | ||||||
|  |         </property> | ||||||
|  |         <property name="horizontalScrollBarPolicy" > | ||||||
|  |          <enum>Qt::ScrollBarAlwaysOff</enum> | ||||||
|  |         </property> | ||||||
|  |         <property name="dragDropMode" > | ||||||
|  |          <enum>QAbstractItemView::DragDrop</enum> | ||||||
|  |         </property> | ||||||
|  |         <property name="flow" > | ||||||
|  |          <enum>QListView::TopToBottom</enum> | ||||||
|  |         </property> | ||||||
|  |         <property name="spacing" > | ||||||
|  |          <number>20</number> | ||||||
|  |         </property> | ||||||
|  |         <property name="viewMode" > | ||||||
|  |          <enum>QListView::IconMode</enum> | ||||||
|  |         </property> | ||||||
|  |        </widget> | ||||||
|  |       </item> | ||||||
|  |       <item> | ||||||
|  |        <widget class="QLabel" name="df" > | ||||||
|  |         <property name="sizePolicy" > | ||||||
|  |          <sizepolicy vsizetype="Preferred" hsizetype="Preferred" > | ||||||
|  |           <horstretch>0</horstretch> | ||||||
|  |           <verstretch>0</verstretch> | ||||||
|  |          </sizepolicy> | ||||||
|  |         </property> | ||||||
|  |         <property name="maximumSize" > | ||||||
|  |          <size> | ||||||
|  |           <width>16777215</width> | ||||||
|  |           <height>90</height> | ||||||
|  |          </size> | ||||||
|  |         </property> | ||||||
|  |         <property name="text" > | ||||||
|  |          <string>For help visit <a href="https://libprs500.kovidgoyal.net/wiki/GuiUsage">http://libprs500.kovidgoyal.net</a><br><br><b>libprs500</b>: %1 by <b>Kovid Goyal</b> &copy; 2006<br>%2 %3 %4</string> | ||||||
|  |         </property> | ||||||
|  |         <property name="textFormat" > | ||||||
|  |          <enum>Qt::RichText</enum> | ||||||
|  |         </property> | ||||||
|  |         <property name="openExternalLinks" > | ||||||
|  |          <bool>true</bool> | ||||||
|  |         </property> | ||||||
|  |        </widget> | ||||||
|  |       </item> | ||||||
|  |      </layout> | ||||||
|  |     </item> | ||||||
|  |     <item> | ||||||
|  |      <layout class="QHBoxLayout" > | ||||||
|  |       <property name="spacing" > | ||||||
|  |        <number>6</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="leftMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="topMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="rightMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="bottomMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <item> | ||||||
|  |        <widget class="QLabel" name="label" > | ||||||
|  |         <property name="text" > | ||||||
|  |          <string>&Search:</string> | ||||||
|  |         </property> | ||||||
|  |         <property name="buddy" > | ||||||
|  |          <cstring>search</cstring> | ||||||
|  |         </property> | ||||||
|  |        </widget> | ||||||
|  |       </item> | ||||||
|  |       <item> | ||||||
|  |        <widget class="SearchBox" name="search" > | ||||||
|  |         <property name="enabled" > | ||||||
|  |          <bool>true</bool> | ||||||
|  |         </property> | ||||||
|  |         <property name="acceptDrops" > | ||||||
|  |          <bool>false</bool> | ||||||
|  |         </property> | ||||||
|  |         <property name="toolTip" > | ||||||
|  |          <string>Search the list of books by title or author<br><br>Words separated by spaces are ANDed</string> | ||||||
|  |         </property> | ||||||
|  |         <property name="whatsThis" > | ||||||
|  |          <string>Search the list of books by title, author, publisher, tags and comments<br><br>Words separated by spaces are ANDed</string> | ||||||
|  |         </property> | ||||||
|  |         <property name="autoFillBackground" > | ||||||
|  |          <bool>false</bool> | ||||||
|  |         </property> | ||||||
|  |         <property name="text" > | ||||||
|  |          <string/> | ||||||
|  |         </property> | ||||||
|  |         <property name="frame" > | ||||||
|  |          <bool>false</bool> | ||||||
|  |         </property> | ||||||
|  |        </widget> | ||||||
|  |       </item> | ||||||
|  |       <item> | ||||||
|  |        <widget class="QToolButton" name="clear_button" > | ||||||
|  |         <property name="toolTip" > | ||||||
|  |          <string>Reset Quick Search</string> | ||||||
|  |         </property> | ||||||
|  |         <property name="text" > | ||||||
|  |          <string>...</string> | ||||||
|  |         </property> | ||||||
|  |         <property name="icon" > | ||||||
|  |          <iconset resource="images.qrc" >:/images/clear.png</iconset> | ||||||
|  |         </property> | ||||||
|  |        </widget> | ||||||
|  |       </item> | ||||||
|  |      </layout> | ||||||
|  |     </item> | ||||||
|  |     <item> | ||||||
|  |      <layout class="QGridLayout" > | ||||||
|  |       <property name="leftMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="topMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="rightMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="bottomMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="horizontalSpacing" > | ||||||
|  |        <number>6</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="verticalSpacing" > | ||||||
|  |        <number>6</number> | ||||||
|  |       </property> | ||||||
|  |       <item row="1" column="0" > | ||||||
|  |        <widget class="BooksView" name="library_view" > | ||||||
|  |         <property name="sizePolicy" > | ||||||
|  |          <sizepolicy vsizetype="Preferred" hsizetype="Preferred" > | ||||||
|  |           <horstretch>0</horstretch> | ||||||
|  |           <verstretch>10</verstretch> | ||||||
|  |          </sizepolicy> | ||||||
|  |         </property> | ||||||
|  |         <property name="acceptDrops" > | ||||||
|  |          <bool>true</bool> | ||||||
|  |         </property> | ||||||
|  |         <property name="dragEnabled" > | ||||||
|  |          <bool>true</bool> | ||||||
|  |         </property> | ||||||
|  |         <property name="dragDropOverwriteMode" > | ||||||
|  |          <bool>false</bool> | ||||||
|  |         </property> | ||||||
|  |         <property name="dragDropMode" > | ||||||
|  |          <enum>QAbstractItemView::DragDrop</enum> | ||||||
|  |         </property> | ||||||
|  |         <property name="alternatingRowColors" > | ||||||
|  |          <bool>true</bool> | ||||||
|  |         </property> | ||||||
|  |         <property name="selectionBehavior" > | ||||||
|  |          <enum>QAbstractItemView::SelectRows</enum> | ||||||
|  |         </property> | ||||||
|  |         <property name="showGrid" > | ||||||
|  |          <bool>false</bool> | ||||||
|  |         </property> | ||||||
|  |        </widget> | ||||||
|  |       </item> | ||||||
|  |      </layout> | ||||||
|  |     </item> | ||||||
|  |     <item> | ||||||
|  |      <layout class="QHBoxLayout" > | ||||||
|  |       <property name="spacing" > | ||||||
|  |        <number>6</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="leftMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="topMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="rightMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <property name="bottomMargin" > | ||||||
|  |        <number>0</number> | ||||||
|  |       </property> | ||||||
|  |       <item> | ||||||
|  |        <widget class="CoverDisplay" name="book_cover" > | ||||||
|  |         <property name="maximumSize" > | ||||||
|  |          <size> | ||||||
|  |           <width>60</width> | ||||||
|  |           <height>80</height> | ||||||
|  |          </size> | ||||||
|  |         </property> | ||||||
|  |         <property name="acceptDrops" > | ||||||
|  |          <bool>true</bool> | ||||||
|  |         </property> | ||||||
|  |         <property name="pixmap" > | ||||||
|  |          <pixmap/> | ||||||
|  |         </property> | ||||||
|  |         <property name="scaledContents" > | ||||||
|  |          <bool>true</bool> | ||||||
|  |         </property> | ||||||
|  |        </widget> | ||||||
|  |       </item> | ||||||
|  |       <item> | ||||||
|  |        <widget class="QLabel" name="book_info" > | ||||||
|  |         <property name="text" > | ||||||
|  |          <string><table><tr><td><b>Title: </b>%1</td><td><b>&nbsp;Size:</b> %2</td></tr><tr><td><b>Author: </b>%3</td><td><b>&nbsp;Type: </b>%4</td></tr></table></string> | ||||||
|  |         </property> | ||||||
|  |         <property name="textFormat" > | ||||||
|  |          <enum>Qt::RichText</enum> | ||||||
|  |         </property> | ||||||
|  |        </widget> | ||||||
|  |       </item> | ||||||
|  |      </layout> | ||||||
|  |     </item> | ||||||
|  |    </layout> | ||||||
|  |   </widget> | ||||||
|  |   <widget class="QToolBar" name="tool_bar" > | ||||||
|  |    <property name="minimumSize" > | ||||||
|  |     <size> | ||||||
|  |      <width>0</width> | ||||||
|  |      <height>0</height> | ||||||
|  |     </size> | ||||||
|  |    </property> | ||||||
|  |    <property name="movable" > | ||||||
|  |     <bool>false</bool> | ||||||
|  |    </property> | ||||||
|  |    <property name="orientation" > | ||||||
|  |     <enum>Qt::Horizontal</enum> | ||||||
|  |    </property> | ||||||
|  |    <property name="iconSize" > | ||||||
|  |     <size> | ||||||
|  |      <width>64</width> | ||||||
|  |      <height>64</height> | ||||||
|  |     </size> | ||||||
|  |    </property> | ||||||
|  |    <property name="toolButtonStyle" > | ||||||
|  |     <enum>Qt::ToolButtonTextUnderIcon</enum> | ||||||
|  |    </property> | ||||||
|  |    <attribute name="toolBarArea" > | ||||||
|  |     <enum>TopToolBarArea</enum> | ||||||
|  |    </attribute> | ||||||
|  |    <attribute name="toolBarBreak" > | ||||||
|  |     <bool>false</bool> | ||||||
|  |    </attribute> | ||||||
|  |    <addaction name="action_add" /> | ||||||
|  |    <addaction name="action_del" /> | ||||||
|  |    <addaction name="action_edit" /> | ||||||
|  |   </widget> | ||||||
|  |   <action name="action_add" > | ||||||
|  |    <property name="icon" > | ||||||
|  |     <iconset resource="images.qrc" >:/images/addfile.png</iconset> | ||||||
|  |    </property> | ||||||
|  |    <property name="text" > | ||||||
|  |     <string>Add books to Library</string> | ||||||
|  |    </property> | ||||||
|  |    <property name="shortcut" > | ||||||
|  |     <string>A</string> | ||||||
|  |    </property> | ||||||
|  |    <property name="autoRepeat" > | ||||||
|  |     <bool>false</bool> | ||||||
|  |    </property> | ||||||
|  |   </action> | ||||||
|  |   <action name="action_del" > | ||||||
|  |    <property name="icon" > | ||||||
|  |     <iconset resource="images.qrc" >:/images/delfile.png</iconset> | ||||||
|  |    </property> | ||||||
|  |    <property name="text" > | ||||||
|  |     <string>Delete books</string> | ||||||
|  |    </property> | ||||||
|  |    <property name="shortcut" > | ||||||
|  |     <string>Del</string> | ||||||
|  |    </property> | ||||||
|  |   </action> | ||||||
|  |   <action name="action_edit" > | ||||||
|  |    <property name="icon" > | ||||||
|  |     <iconset resource="images.qrc" >:/images/edit.png</iconset> | ||||||
|  |    </property> | ||||||
|  |    <property name="text" > | ||||||
|  |     <string>Edit meta-information</string> | ||||||
|  |    </property> | ||||||
|  |    <property name="shortcut" > | ||||||
|  |     <string>E</string> | ||||||
|  |    </property> | ||||||
|  |    <property name="autoRepeat" > | ||||||
|  |     <bool>false</bool> | ||||||
|  |    </property> | ||||||
|  |   </action> | ||||||
|  |  </widget> | ||||||
|  |  <customwidgets> | ||||||
|  |   <customwidget> | ||||||
|  |    <class>CoverDisplay</class> | ||||||
|  |    <extends>QLabel</extends> | ||||||
|  |    <header>widgets.h</header> | ||||||
|  |   </customwidget> | ||||||
|  |   <customwidget> | ||||||
|  |    <class>DeviceView</class> | ||||||
|  |    <extends>QListView</extends> | ||||||
|  |    <header>widgets.h</header> | ||||||
|  |   </customwidget> | ||||||
|  |   <customwidget> | ||||||
|  |    <class>BooksView</class> | ||||||
|  |    <extends>QTableView</extends> | ||||||
|  |    <header>library.h</header> | ||||||
|  |   </customwidget> | ||||||
|  |   <customwidget> | ||||||
|  |    <class>SearchBox</class> | ||||||
|  |    <extends>QLineEdit</extends> | ||||||
|  |    <header>library.h</header> | ||||||
|  |   </customwidget> | ||||||
|  |  </customwidgets> | ||||||
|  |  <resources> | ||||||
|  |   <include location="images.qrc" /> | ||||||
|  |  </resources> | ||||||
|  |  <connections> | ||||||
|  |   <connection> | ||||||
|  |    <sender>clear_button</sender> | ||||||
|  |    <signal>clicked()</signal> | ||||||
|  |    <receiver>search</receiver> | ||||||
|  |    <slot>clear()</slot> | ||||||
|  |    <hints> | ||||||
|  |     <hint type="sourcelabel" > | ||||||
|  |      <x>853</x> | ||||||
|  |      <y>61</y> | ||||||
|  |     </hint> | ||||||
|  |     <hint type="destinationlabel" > | ||||||
|  |      <x>784</x> | ||||||
|  |      <y>58</y> | ||||||
|  |     </hint> | ||||||
|  |    </hints> | ||||||
|  |   </connection> | ||||||
|  |  </connections> | ||||||
|  | </ui> | ||||||
							
								
								
									
										196
									
								
								src/libprs500/gui2/main_ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,196 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | 
 | ||||||
|  | # Form implementation generated from reading ui file 'main.ui' | ||||||
|  | # | ||||||
|  | # Created: Sun May 27 10:53:01 2007 | ||||||
|  | #      by: PyQt4 UI code generator 4-snapshot-20070525 | ||||||
|  | # | ||||||
|  | # WARNING! All changes made in this file will be lost! | ||||||
|  | 
 | ||||||
|  | from PyQt4 import QtCore, QtGui | ||||||
|  | 
 | ||||||
|  | class Ui_MainWindow(object): | ||||||
|  |     def setupUi(self, MainWindow): | ||||||
|  |         MainWindow.setObjectName("MainWindow") | ||||||
|  |         MainWindow.resize(QtCore.QSize(QtCore.QRect(0,0,728,822).size()).expandedTo(MainWindow.minimumSizeHint())) | ||||||
|  | 
 | ||||||
|  |         sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,QtGui.QSizePolicy.Preferred) | ||||||
|  |         sizePolicy.setHorizontalStretch(0) | ||||||
|  |         sizePolicy.setVerticalStretch(0) | ||||||
|  |         sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) | ||||||
|  |         MainWindow.setSizePolicy(sizePolicy) | ||||||
|  | 
 | ||||||
|  |         self.centralwidget = QtGui.QWidget(MainWindow) | ||||||
|  |         self.centralwidget.setObjectName("centralwidget") | ||||||
|  | 
 | ||||||
|  |         self.vboxlayout = QtGui.QVBoxLayout(self.centralwidget) | ||||||
|  |         self.vboxlayout.setSpacing(6) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         self.hboxlayout = QtGui.QHBoxLayout() | ||||||
|  |         self.hboxlayout.setSpacing(6) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         self.device_tree = DeviceView(self.centralwidget) | ||||||
|  | 
 | ||||||
|  |         sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,QtGui.QSizePolicy.Preferred) | ||||||
|  |         sizePolicy.setHorizontalStretch(0) | ||||||
|  |         sizePolicy.setVerticalStretch(0) | ||||||
|  |         sizePolicy.setHeightForWidth(self.device_tree.sizePolicy().hasHeightForWidth()) | ||||||
|  |         self.device_tree.setSizePolicy(sizePolicy) | ||||||
|  |         self.device_tree.setMaximumSize(QtCore.QSize(10000,90)) | ||||||
|  |         self.device_tree.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) | ||||||
|  |         self.device_tree.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) | ||||||
|  |         self.device_tree.setDragDropMode(QtGui.QAbstractItemView.DragDrop) | ||||||
|  |         self.device_tree.setFlow(QtGui.QListView.TopToBottom) | ||||||
|  |         self.device_tree.setSpacing(20) | ||||||
|  |         self.device_tree.setViewMode(QtGui.QListView.IconMode) | ||||||
|  |         self.device_tree.setObjectName("device_tree") | ||||||
|  |         self.hboxlayout.addWidget(self.device_tree) | ||||||
|  | 
 | ||||||
|  |         self.df = QtGui.QLabel(self.centralwidget) | ||||||
|  | 
 | ||||||
|  |         sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,QtGui.QSizePolicy.Preferred) | ||||||
|  |         sizePolicy.setHorizontalStretch(0) | ||||||
|  |         sizePolicy.setVerticalStretch(0) | ||||||
|  |         sizePolicy.setHeightForWidth(self.df.sizePolicy().hasHeightForWidth()) | ||||||
|  |         self.df.setSizePolicy(sizePolicy) | ||||||
|  |         self.df.setMaximumSize(QtCore.QSize(16777215,90)) | ||||||
|  |         self.df.setTextFormat(QtCore.Qt.RichText) | ||||||
|  |         self.df.setOpenExternalLinks(True) | ||||||
|  |         self.df.setObjectName("df") | ||||||
|  |         self.hboxlayout.addWidget(self.df) | ||||||
|  |         self.vboxlayout.addLayout(self.hboxlayout) | ||||||
|  | 
 | ||||||
|  |         self.hboxlayout1 = QtGui.QHBoxLayout() | ||||||
|  |         self.hboxlayout1.setSpacing(6) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         self.hboxlayout1.setObjectName("hboxlayout1") | ||||||
|  | 
 | ||||||
|  |         self.label = QtGui.QLabel(self.centralwidget) | ||||||
|  |         self.label.setObjectName("label") | ||||||
|  |         self.hboxlayout1.addWidget(self.label) | ||||||
|  | 
 | ||||||
|  |         self.search = SearchBox(self.centralwidget) | ||||||
|  |         self.search.setEnabled(True) | ||||||
|  |         self.search.setAcceptDrops(False) | ||||||
|  |         self.search.setAutoFillBackground(False) | ||||||
|  |         self.search.setFrame(True) | ||||||
|  |         self.search.setObjectName("search") | ||||||
|  |         self.hboxlayout1.addWidget(self.search) | ||||||
|  | 
 | ||||||
|  |         self.clear_button = QtGui.QToolButton(self.centralwidget) | ||||||
|  |         self.clear_button.setIcon(QtGui.QIcon(":/images/clear.png")) | ||||||
|  |         self.clear_button.setObjectName("clear_button") | ||||||
|  |         self.hboxlayout1.addWidget(self.clear_button) | ||||||
|  |         self.vboxlayout.addLayout(self.hboxlayout1) | ||||||
|  | 
 | ||||||
|  |         self.gridlayout = QtGui.QGridLayout() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         self.gridlayout.setHorizontalSpacing(6) | ||||||
|  |         self.gridlayout.setVerticalSpacing(6) | ||||||
|  |         self.gridlayout.setObjectName("gridlayout") | ||||||
|  | 
 | ||||||
|  |         self.library_view = BooksView(self.centralwidget) | ||||||
|  | 
 | ||||||
|  |         sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,QtGui.QSizePolicy.Preferred) | ||||||
|  |         sizePolicy.setHorizontalStretch(0) | ||||||
|  |         sizePolicy.setVerticalStretch(10) | ||||||
|  |         sizePolicy.setHeightForWidth(self.library_view.sizePolicy().hasHeightForWidth()) | ||||||
|  |         self.library_view.setSizePolicy(sizePolicy) | ||||||
|  |         self.library_view.setAcceptDrops(True) | ||||||
|  |         self.library_view.setDragEnabled(True) | ||||||
|  |         self.library_view.setDragDropOverwriteMode(False) | ||||||
|  |         self.library_view.setDragDropMode(QtGui.QAbstractItemView.DragDrop) | ||||||
|  |         self.library_view.setAlternatingRowColors(True) | ||||||
|  |         self.library_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) | ||||||
|  |         self.library_view.setShowGrid(False) | ||||||
|  |         self.library_view.setObjectName("library_view") | ||||||
|  |         self.gridlayout.addWidget(self.library_view,1,0,1,1) | ||||||
|  |         self.vboxlayout.addLayout(self.gridlayout) | ||||||
|  | 
 | ||||||
|  |         self.hboxlayout2 = QtGui.QHBoxLayout() | ||||||
|  |         self.hboxlayout2.setSpacing(6) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         self.hboxlayout2.setObjectName("hboxlayout2") | ||||||
|  | 
 | ||||||
|  |         self.book_cover = CoverDisplay(self.centralwidget) | ||||||
|  |         self.book_cover.setMaximumSize(QtCore.QSize(60,80)) | ||||||
|  |         self.book_cover.setAcceptDrops(True) | ||||||
|  |         self.book_cover.setScaledContents(True) | ||||||
|  |         self.book_cover.setObjectName("book_cover") | ||||||
|  |         self.hboxlayout2.addWidget(self.book_cover) | ||||||
|  | 
 | ||||||
|  |         self.book_info = QtGui.QLabel(self.centralwidget) | ||||||
|  |         self.book_info.setTextFormat(QtCore.Qt.RichText) | ||||||
|  |         self.book_info.setObjectName("book_info") | ||||||
|  |         self.hboxlayout2.addWidget(self.book_info) | ||||||
|  |         self.vboxlayout.addLayout(self.hboxlayout2) | ||||||
|  |         MainWindow.setCentralWidget(self.centralwidget) | ||||||
|  | 
 | ||||||
|  |         self.tool_bar = QtGui.QToolBar(MainWindow) | ||||||
|  |         self.tool_bar.setMinimumSize(QtCore.QSize(0,0)) | ||||||
|  |         self.tool_bar.setMovable(False) | ||||||
|  |         self.tool_bar.setOrientation(QtCore.Qt.Horizontal) | ||||||
|  |         self.tool_bar.setIconSize(QtCore.QSize(64,64)) | ||||||
|  |         self.tool_bar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) | ||||||
|  |         self.tool_bar.setObjectName("tool_bar") | ||||||
|  |         MainWindow.addToolBar(self.tool_bar) | ||||||
|  | 
 | ||||||
|  |         self.action_add = QtGui.QAction(MainWindow) | ||||||
|  |         self.action_add.setIcon(QtGui.QIcon(":/images/addfile.png")) | ||||||
|  |         self.action_add.setAutoRepeat(False) | ||||||
|  |         self.action_add.setObjectName("action_add") | ||||||
|  | 
 | ||||||
|  |         self.action_del = QtGui.QAction(MainWindow) | ||||||
|  |         self.action_del.setIcon(QtGui.QIcon(":/images/delfile.png")) | ||||||
|  |         self.action_del.setObjectName("action_del") | ||||||
|  | 
 | ||||||
|  |         self.action_edit = QtGui.QAction(MainWindow) | ||||||
|  |         self.action_edit.setIcon(QtGui.QIcon(":/images/edit.png")) | ||||||
|  |         self.action_edit.setAutoRepeat(False) | ||||||
|  |         self.action_edit.setObjectName("action_edit") | ||||||
|  |         self.tool_bar.addAction(self.action_add) | ||||||
|  |         self.tool_bar.addAction(self.action_del) | ||||||
|  |         self.tool_bar.addAction(self.action_edit) | ||||||
|  |         self.label.setBuddy(self.search) | ||||||
|  | 
 | ||||||
|  |         self.retranslateUi(MainWindow) | ||||||
|  |         QtCore.QObject.connect(self.clear_button,QtCore.SIGNAL("clicked()"),self.search.clear) | ||||||
|  |         QtCore.QMetaObject.connectSlotsByName(MainWindow) | ||||||
|  | 
 | ||||||
|  |     def retranslateUi(self, MainWindow): | ||||||
|  |         self.df.setText(QtGui.QApplication.translate("MainWindow", "For help visit <a href=\"https://libprs500.kovidgoyal.net/wiki/GuiUsage\">http://libprs500.kovidgoyal.net</a><br><br><b>libprs500</b>: %1 by <b>Kovid Goyal</b> © 2006<br>%2 %3 %4", None, QtGui.QApplication.UnicodeUTF8)) | ||||||
|  |         self.label.setText(QtGui.QApplication.translate("MainWindow", "&Search:", None, QtGui.QApplication.UnicodeUTF8)) | ||||||
|  |         self.search.setToolTip(QtGui.QApplication.translate("MainWindow", "Search the list of books by title or author<br><br>Words separated by spaces are ANDed", None, QtGui.QApplication.UnicodeUTF8)) | ||||||
|  |         self.search.setWhatsThis(QtGui.QApplication.translate("MainWindow", "Search the list of books by title or author<br><br>Words separated by spaces are ANDed", None, QtGui.QApplication.UnicodeUTF8)) | ||||||
|  |         self.clear_button.setToolTip(QtGui.QApplication.translate("MainWindow", "Reset Quick Search", None, QtGui.QApplication.UnicodeUTF8)) | ||||||
|  |         self.clear_button.setText(QtGui.QApplication.translate("MainWindow", "...", None, QtGui.QApplication.UnicodeUTF8)) | ||||||
|  |         self.book_info.setText(QtGui.QApplication.translate("MainWindow", "<table><tr><td><b>Title: </b>%1</td><td><b> Size:</b> %2</td></tr><tr><td><b>Author: </b>%3</td><td><b> Type: </b>%4</td></tr></table>", None, QtGui.QApplication.UnicodeUTF8)) | ||||||
|  |         self.action_add.setText(QtGui.QApplication.translate("MainWindow", "Add books to Library", None, QtGui.QApplication.UnicodeUTF8)) | ||||||
|  |         self.action_add.setShortcut(QtGui.QApplication.translate("MainWindow", "A", None, QtGui.QApplication.UnicodeUTF8)) | ||||||
|  |         self.action_del.setText(QtGui.QApplication.translate("MainWindow", "Delete books", None, QtGui.QApplication.UnicodeUTF8)) | ||||||
|  |         self.action_del.setShortcut(QtGui.QApplication.translate("MainWindow", "Del", None, QtGui.QApplication.UnicodeUTF8)) | ||||||
|  |         self.action_edit.setText(QtGui.QApplication.translate("MainWindow", "Edit meta-information", None, QtGui.QApplication.UnicodeUTF8)) | ||||||
|  |         self.action_edit.setShortcut(QtGui.QApplication.translate("MainWindow", "E", None, QtGui.QApplication.UnicodeUTF8)) | ||||||
|  | 
 | ||||||
|  | from widgets import DeviceView, CoverDisplay | ||||||
|  | from library import BooksView, SearchBox | ||||||
|  | import images_rc | ||||||
							
								
								
									
										863
									
								
								src/libprs500/gui2/widgets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,863 @@ | |||||||
|  | ##    Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net | ||||||
|  | ##    This program is free software; you can redistribute it and/or modify | ||||||
|  | ##    it under the terms of the GNU General Public License as published by | ||||||
|  | ##    the Free Software Foundation; either version 2 of the License, or | ||||||
|  | ##    (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ##    This program is distributed in the hope that it will be useful, | ||||||
|  | ##    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ##    GNU General Public License for more details. | ||||||
|  | ## | ||||||
|  | ##    You should have received a copy of the GNU General Public License along | ||||||
|  | ##    with this program; if not, write to the Free Software Foundation, Inc., | ||||||
|  | ##    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||||
|  | import re | ||||||
|  | import os | ||||||
|  | import textwrap | ||||||
|  | import time  | ||||||
|  | import traceback | ||||||
|  | from operator import itemgetter, attrgetter | ||||||
|  | from socket import gethostname | ||||||
|  | from urlparse import urlparse, urlunparse | ||||||
|  | from urllib import quote, unquote | ||||||
|  | from math import sin, cos, pi | ||||||
|  | 
 | ||||||
|  | from libprs500.gui import Error, _Warning | ||||||
|  | from libprs500.ptempfile import PersistentTemporaryFile | ||||||
|  | from libprs500 import iswindows | ||||||
|  | 
 | ||||||
|  | from PyQt4.QtCore import Qt, SIGNAL | ||||||
|  | from PyQt4.Qt import QApplication, QString, QFont, QAbstractListModel, \ | ||||||
|  |                      QVariant, QAbstractTableModel, QTableView, QListView, \ | ||||||
|  |                      QLabel, QAbstractItemView, QPixmap, QIcon, QSize, \ | ||||||
|  |                      QSpinBox, QPoint, QPainterPath, QItemDelegate, QPainter, \ | ||||||
|  |                      QPen, QColor, QLinearGradient, QBrush, QStyle, \ | ||||||
|  |                      QByteArray, QBuffer, QMimeData, \ | ||||||
|  |                      QDrag, QRect                      | ||||||
|  | 
 | ||||||
|  | NONE = QVariant() #: Null value to return from the data function of item models | ||||||
|  | TIME_WRITE_FMT  = "%d %b %Y" #: The display format used to show dates | ||||||
|  | 
 | ||||||
|  | class FileDragAndDrop(object): | ||||||
|  |     _drag_start_position = QPoint() | ||||||
|  |     _dragged_files = [] | ||||||
|  |      | ||||||
|  |     @classmethod | ||||||
|  |     def _bytes_to_string(cls, qba): | ||||||
|  |         """  | ||||||
|  |         Assumes qba is encoded in ASCII which is usually fine, since | ||||||
|  |         this method is used mainly for escaped URIs. | ||||||
|  |         @type qba: QByteArray  | ||||||
|  |         """ | ||||||
|  |         return str(QString.fromAscii(qba.data())).strip()         | ||||||
|  |      | ||||||
|  |     @classmethod | ||||||
|  |     def _get_r_ok_files(cls, event): | ||||||
|  |         """  | ||||||
|  |         Return list of paths from event that point to files to | ||||||
|  |         which the user has read permission. | ||||||
|  |         """ | ||||||
|  |         files = [] | ||||||
|  |         md = event.mimeData() | ||||||
|  |         if md.hasFormat("text/uri-list"): | ||||||
|  |             candidates = cls._bytes_to_string(md.data("text/uri-list")).split() | ||||||
|  |             for url in candidates: | ||||||
|  |                 o = urlparse(url)         | ||||||
|  |                 if o.scheme and o.scheme != 'file': | ||||||
|  |                     _Warning(o.scheme +  " not supported in drop events", None) | ||||||
|  |                     continue | ||||||
|  |                 path = unquote(o.path) | ||||||
|  |                 if iswindows and path.startswith('/'): | ||||||
|  |                     path = path[1:] | ||||||
|  |                 if not os.access(path, os.R_OK): | ||||||
|  |                     _Warning("You do not have read permission for: " + path, None) | ||||||
|  |                     continue | ||||||
|  |                 if os.path.isdir(path): | ||||||
|  |                     root, dirs, files2 = os.walk(path) | ||||||
|  |                     for _file in files2: | ||||||
|  |                         path = root + _file | ||||||
|  |                         if os.access(path, os.R_OK):  | ||||||
|  |                             files.append(path) | ||||||
|  |                 else:  | ||||||
|  |                     files.append(path) | ||||||
|  |         return files | ||||||
|  |      | ||||||
|  |     def __init__(self, QtBaseClass, enable_drag=True): | ||||||
|  |         self.QtBaseClass = QtBaseClass | ||||||
|  |         self.enable_drag = enable_drag | ||||||
|  |      | ||||||
|  |     def mousePressEvent(self, event): | ||||||
|  |         self.QtBaseClass.mousePressEvent(self, event) | ||||||
|  |         if self.enable_drag: | ||||||
|  |             if event.button == Qt.LeftButton: | ||||||
|  |                 self._drag_start_position = event.pos() | ||||||
|  |      | ||||||
|  |      | ||||||
|  |     def mouseMoveEvent(self, event): | ||||||
|  |         self.QtBaseClass.mousePressEvent(self, event) | ||||||
|  |         if self.enable_drag: | ||||||
|  |             if event.buttons() & Qt.LeftButton != Qt.LeftButton:  | ||||||
|  |                 return | ||||||
|  |             if (event.pos() - self._drag_start_position).manhattanLength() < \ | ||||||
|  |                                             QApplication.startDragDistance():  | ||||||
|  |                 return | ||||||
|  |             self.start_drag(self._drag_start_position) | ||||||
|  |      | ||||||
|  |      | ||||||
|  |     def start_drag(self, pos):  | ||||||
|  |         raise NotImplementedError() | ||||||
|  |      | ||||||
|  |     def dragEnterEvent(self, event): | ||||||
|  |         if event.mimeData().hasFormat("text/uri-list"):  | ||||||
|  |             event.acceptProposedAction() | ||||||
|  |      | ||||||
|  |     def dragMoveEvent(self, event): | ||||||
|  |         event.acceptProposedAction() | ||||||
|  |      | ||||||
|  |     def dropEvent(self, event): | ||||||
|  |         files = self._get_r_ok_files(event) | ||||||
|  |         if files: | ||||||
|  |             try: | ||||||
|  |                 event.setDropAction(Qt.CopyAction) | ||||||
|  |                 if self.files_dropped(files, event):  | ||||||
|  |                     event.accept() | ||||||
|  |             except Exception, e:         | ||||||
|  |                 Error("There was an error processing the dropped files.", e) | ||||||
|  |                 raise e | ||||||
|  |      | ||||||
|  |      | ||||||
|  |     def files_dropped(self, files, event):  | ||||||
|  |         raise NotImplementedError() | ||||||
|  |      | ||||||
|  |     def drag_object_from_files(self, files): | ||||||
|  |         if files: | ||||||
|  |             drag = QDrag(self) | ||||||
|  |             mime_data = QMimeData() | ||||||
|  |             self._dragged_files, urls = [], [] | ||||||
|  |             for _file in files:         | ||||||
|  |                 urls.append(urlunparse(('file', quote(gethostname()), \ | ||||||
|  |                                 quote(_file.name.encode('utf-8')), '', '', ''))) | ||||||
|  |                 self._dragged_files.append(_file)       | ||||||
|  |             mime_data.setData("text/uri-list", QByteArray("\n".join(urls))) | ||||||
|  |             user = os.getenv('USER') | ||||||
|  |             if user:  | ||||||
|  |                 mime_data.setData("text/x-xdnd-username", QByteArray(user)) | ||||||
|  |             drag.setMimeData(mime_data) | ||||||
|  |             return drag | ||||||
|  |      | ||||||
|  |     def drag_object(self, extensions): | ||||||
|  |         if extensions: | ||||||
|  |             files = [] | ||||||
|  |             for ext in extensions: | ||||||
|  |                 f = PersistentTemporaryFile(suffix="."+ext) | ||||||
|  |                 files.append(f) | ||||||
|  |             return self.drag_object_from_files(files), self._dragged_files | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TableView(FileDragAndDrop, QTableView): | ||||||
|  |     wrapper = textwrap.TextWrapper(width=20) | ||||||
|  |      | ||||||
|  |     def __init__(self, parent): | ||||||
|  |         FileDragAndDrop.__init__(self, QTableView) | ||||||
|  |         QTableView.__init__(self, parent) | ||||||
|  |      | ||||||
|  |     @classmethod | ||||||
|  |     def wrap(cls, s, width=20):          | ||||||
|  |         cls.wrapper.width = width | ||||||
|  |         return cls.wrapper.fill(s)  | ||||||
|  |      | ||||||
|  |     @classmethod | ||||||
|  |     def human_readable(cls, size): | ||||||
|  |         """ Convert a size in bytes into a human readable form """ | ||||||
|  |         if size < 1024:  | ||||||
|  |             divisor, suffix = 1, "B" | ||||||
|  |         elif size < 1024*1024:  | ||||||
|  |             divisor, suffix = 1024., "KB" | ||||||
|  |         elif size < 1024*1024*1024:  | ||||||
|  |             divisor, suffix = 1024*1024, "MB" | ||||||
|  |         elif size < 1024*1024*1024*1024:  | ||||||
|  |             divisor, suffix = 1024*1024, "GB" | ||||||
|  |         size = str(size/divisor) | ||||||
|  |         if size.find(".") > -1:  | ||||||
|  |             size = size[:size.find(".")+2] | ||||||
|  |         return size + " " + suffix | ||||||
|  |      | ||||||
|  |     def render_to_pixmap(self, indices): | ||||||
|  |         rect = self.visualRect(indices[0]) | ||||||
|  |         rects = [] | ||||||
|  |         for i in range(len(indices)): | ||||||
|  |             rects.append(self.visualRect(indices[i])) | ||||||
|  |             rect |= rects[i] | ||||||
|  |         rect = rect.intersected(self.viewport().rect()) | ||||||
|  |         pixmap = QPixmap(rect.size()) | ||||||
|  |         pixmap.fill(self.palette().base().color()) | ||||||
|  |         painter = QPainter(pixmap) | ||||||
|  |         option = self.viewOptions() | ||||||
|  |         option.state |= QStyle.State_Selected | ||||||
|  |         for j in range(len(indices)): | ||||||
|  |             option.rect = QRect(rects[j].topLeft() - rect.topLeft(), \ | ||||||
|  |                                     rects[j].size()) | ||||||
|  |             self.itemDelegate(indices[j]).paint(painter, option, indices[j]) | ||||||
|  |         painter.end() | ||||||
|  |         return pixmap | ||||||
|  |      | ||||||
|  |     def drag_object_from_files(self, files): | ||||||
|  |         drag = FileDragAndDrop.drag_object_from_files(self, files) | ||||||
|  |         drag.setPixmap(self.render_to_pixmap(self.selectedIndexes())) | ||||||
|  |         return drag | ||||||
|  | 
 | ||||||
|  |          | ||||||
|  | 
 | ||||||
|  | class CoverDisplay(FileDragAndDrop, QLabel): | ||||||
|  |     def __init__(self, parent): | ||||||
|  |         FileDragAndDrop.__init__(self, QLabel) | ||||||
|  |         QLabel.__init__(self, parent) | ||||||
|  |     def files_dropped(self, files, event): | ||||||
|  |         pix = QPixmap() | ||||||
|  |         for _file in files: | ||||||
|  |             pix = QPixmap(_file) | ||||||
|  |             if not pix.isNull(): break | ||||||
|  |         if not pix.isNull():       | ||||||
|  |             self.emit(SIGNAL("cover_received(QPixmap)"), pix) | ||||||
|  |             return True | ||||||
|  |      | ||||||
|  |     def start_drag(self, event): | ||||||
|  |         drag, files = self.drag_object(["jpeg"]) | ||||||
|  |         if drag and files: | ||||||
|  |             _file = files[0] | ||||||
|  |             _file.close() | ||||||
|  |             drag.setPixmap(self.pixmap().scaledToHeight(68, \ | ||||||
|  |                                                     Qt.SmoothTransformation)) | ||||||
|  |             self.pixmap().save(os.path.abspath(_file.name)) | ||||||
|  |             drag.start(Qt.MoveAction) | ||||||
|  | 
 | ||||||
|  | class DeviceView(FileDragAndDrop, QListView): | ||||||
|  |     def __init__(self, parent): | ||||||
|  |         FileDragAndDrop.__init__(self, QListView, enable_drag=False) | ||||||
|  |         QListView.__init__(self, parent) | ||||||
|  |      | ||||||
|  |     def hide_reader(self, x): | ||||||
|  |         self.model().update_devices(reader=not x) | ||||||
|  |      | ||||||
|  |     def hide_card(self, x): | ||||||
|  |         self.model().update_devices(card=not x) | ||||||
|  |        | ||||||
|  |     def files_dropped(self, files, event): | ||||||
|  |         ids = [] | ||||||
|  |         md = event.mimeData() | ||||||
|  |         if md.hasFormat("application/x-libprs500-id"): | ||||||
|  |             ids = [ int(id) for id in FileDragAndDrop._bytes_to_string(\ | ||||||
|  |                                 md.data("application/x-libprs500-id")).split()] | ||||||
|  |         index = self.indexAt(event.pos()) | ||||||
|  |         if index.isValid(): | ||||||
|  |             return self.model().files_dropped(files, index, ids) | ||||||
|  | 
 | ||||||
|  | class DeviceBooksView(TableView): | ||||||
|  |     def __init__(self, parent): | ||||||
|  |         TableView.__init__(self, parent) | ||||||
|  |         self.setSelectionBehavior(QAbstractItemView.SelectRows) | ||||||
|  |         self.setSortingEnabled(True) | ||||||
|  | 
 | ||||||
|  | class LibraryBooksView(TableView): | ||||||
|  |     def __init__(self, parent):     | ||||||
|  |         TableView.__init__(self, parent) | ||||||
|  |         self.setSelectionBehavior(QAbstractItemView.SelectRows) | ||||||
|  |         self.setSortingEnabled(True) | ||||||
|  |         self.setItemDelegate(LibraryDelegate(self, rating_column=4)) | ||||||
|  |      | ||||||
|  |     def dragEnterEvent(self, event): | ||||||
|  |         if not event.mimeData().hasFormat("application/x-libprs500-id"): | ||||||
|  |             FileDragAndDrop.dragEnterEvent(self, event) | ||||||
|  |      | ||||||
|  |      | ||||||
|  |     def start_drag(self, pos):     | ||||||
|  |         index = self.indexAt(pos) | ||||||
|  |         if index.isValid(): | ||||||
|  |             rows = frozenset([ index.row() for index in self.selectedIndexes()]) | ||||||
|  |             files = self.model().extract_formats(rows)             | ||||||
|  |             drag = self.drag_object_from_files(files) | ||||||
|  |             if drag: | ||||||
|  |                 ids = [ str(self.model().id_from_row(row)) for row in rows ] | ||||||
|  |                 drag.mimeData().setData("application/x-libprs500-id", \ | ||||||
|  |                         QByteArray("\n".join(ids))) | ||||||
|  |                 drag.start() | ||||||
|  |      | ||||||
|  |      | ||||||
|  |     def files_dropped(self, files, event): | ||||||
|  |         if not files: return | ||||||
|  |         index = self.indexAt(event.pos())     | ||||||
|  |         if index.isValid(): | ||||||
|  |             self.model().add_formats(files, index)       | ||||||
|  |         else: self.emit(SIGNAL('books_dropped'), files)       | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class LibraryDelegate(QItemDelegate): | ||||||
|  |     COLOR = QColor("blue") | ||||||
|  |     SIZE     = 16 | ||||||
|  |     PEN      = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) | ||||||
|  |      | ||||||
|  |     def __init__(self, parent, rating_column=-1): | ||||||
|  |         QItemDelegate.__init__(self, parent) | ||||||
|  |         self.rating_column = rating_column | ||||||
|  |         self.star_path = QPainterPath() | ||||||
|  |         self.star_path.moveTo(90, 50) | ||||||
|  |         for i in range(1, 5): | ||||||
|  |             self.star_path.lineTo(50 + 40 * cos(0.8 * i * pi), \ | ||||||
|  |                                   50 + 40 * sin(0.8 * i * pi)) | ||||||
|  |         self.star_path.closeSubpath() | ||||||
|  |         self.star_path.setFillRule(Qt.WindingFill) | ||||||
|  |         gradient = QLinearGradient(0, 0, 0, 100) | ||||||
|  |         gradient.setColorAt(0.0, self.COLOR) | ||||||
|  |         gradient.setColorAt(1.0, self.COLOR) | ||||||
|  |         self. brush = QBrush(gradient) | ||||||
|  |         self.factor = self.SIZE/100. | ||||||
|  |      | ||||||
|  |      | ||||||
|  |     def sizeHint(self, option, index): | ||||||
|  |         if index.column() != self.rating_column: | ||||||
|  |             return QItemDelegate.sizeHint(self, option, index) | ||||||
|  |         num = index.model().data(index, Qt.DisplayRole).toInt()[0] | ||||||
|  |         return QSize(num*(self.SIZE), self.SIZE+4) | ||||||
|  |      | ||||||
|  |     def paint(self, painter, option, index): | ||||||
|  |         if index.column() != self.rating_column: | ||||||
|  |             return QItemDelegate.paint(self, painter, option, index) | ||||||
|  |         num = index.model().data(index, Qt.DisplayRole).toInt()[0] | ||||||
|  |         def draw_star():  | ||||||
|  |             painter.save() | ||||||
|  |             painter.scale(self.factor, self.factor) | ||||||
|  |             painter.translate(50.0, 50.0) | ||||||
|  |             painter.rotate(-20) | ||||||
|  |             painter.translate(-50.0, -50.0) | ||||||
|  |             painter.drawPath(self.star_path) | ||||||
|  |             painter.restore() | ||||||
|  |          | ||||||
|  |         painter.save() | ||||||
|  |         try: | ||||||
|  |             if option.state & QStyle.State_Selected: | ||||||
|  |                 painter.fillRect(option.rect, option.palette.highlight()) | ||||||
|  |             painter.setRenderHint(QPainter.Antialiasing) | ||||||
|  |             y = option.rect.center().y()-self.SIZE/2.  | ||||||
|  |             x = option.rect.right()  - self.SIZE | ||||||
|  |             painter.setPen(self.PEN)       | ||||||
|  |             painter.setBrush(self.brush)       | ||||||
|  |             painter.translate(x, y) | ||||||
|  |             for i in range(num): | ||||||
|  |                 draw_star() | ||||||
|  |                 painter.translate(-self.SIZE, 0) | ||||||
|  |         except Exception, e: | ||||||
|  |             traceback.print_exc(e) | ||||||
|  |         painter.restore() | ||||||
|  |      | ||||||
|  |     def createEditor(self, parent, option, index): | ||||||
|  |         if index.column() != 4: | ||||||
|  |             return QItemDelegate.createEditor(self, parent, option, index) | ||||||
|  |         editor = QSpinBox(parent) | ||||||
|  |         editor.setSuffix(" stars") | ||||||
|  |         editor.setMinimum(0) | ||||||
|  |         editor.setMaximum(5) | ||||||
|  |         editor.installEventFilter(self) | ||||||
|  |         return editor | ||||||
|  |      | ||||||
|  |     def setEditorData(self, editor, index): | ||||||
|  |         if index.column() != 4: | ||||||
|  |             return QItemDelegate.setEditorData(self, editor, index) | ||||||
|  |         val = index.model()._data[index.row()]["rating"] | ||||||
|  |         if not val: val = 0 | ||||||
|  |         editor.setValue(val) | ||||||
|  |      | ||||||
|  |     def setModelData(self, editor, model, index): | ||||||
|  |         if index.column() != 4: | ||||||
|  |             return QItemDelegate.setModelData(self, editor, model, index) | ||||||
|  |         editor.interpretText() | ||||||
|  |         index.model().setData(index, QVariant(editor.value()), Qt.EditRole) | ||||||
|  |      | ||||||
|  |     def updateEditorGeometry(self, editor, option, index): | ||||||
|  |         if index.column() != 4: | ||||||
|  |             return QItemDelegate.updateEditorGeometry(self, editor, option, index) | ||||||
|  |         editor.setGeometry(option.rect) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class LibraryBooksModel(QAbstractTableModel): | ||||||
|  |     FIELDS = ["id", "title", "authors", "size", "date", "rating", "publisher", \ | ||||||
|  |               "tags", "comments"]   | ||||||
|  |     TIME_READ_FMT = "%Y-%m-%d %H:%M:%S" | ||||||
|  |     def __init__(self, parent): | ||||||
|  |         QAbstractTableModel.__init__(self, parent) | ||||||
|  |         self.db    = None  | ||||||
|  |         self._data = None | ||||||
|  |         self._orig_data = None | ||||||
|  |      | ||||||
|  |     def extract_formats(self, rows): | ||||||
|  |         files = [] | ||||||
|  |         for row in rows:       | ||||||
|  |             _id = self.id_from_row(row) | ||||||
|  |             au = self._data[row]["authors"] if self._data[row]["authors"] \ | ||||||
|  |                                             else "Unknown" | ||||||
|  |             basename = re.sub("\n", "", "_"+str(_id)+"_"+\ | ||||||
|  |                                 self._data[row]["title"]+" by "+ au) | ||||||
|  |             exts = self.db.get_extensions(_id) | ||||||
|  |             for ext in exts: | ||||||
|  |                 fmt = self.db.get_format(_id, ext) | ||||||
|  |                 if not ext:  | ||||||
|  |                     ext ="" | ||||||
|  |                 else:  | ||||||
|  |                     ext = "."+ext | ||||||
|  |                 name = basename+ext                 | ||||||
|  |                 file = PersistentTemporaryFile(suffix=name) | ||||||
|  |                 if not fmt:  | ||||||
|  |                     continue | ||||||
|  |                 file.write(fmt) | ||||||
|  |                 file.close()  | ||||||
|  |                 files.append(file) | ||||||
|  |         return files | ||||||
|  |      | ||||||
|  |     def update_cover(self, index, pix): | ||||||
|  |         spix = pix.scaledToHeight(68, Qt.SmoothTransformation) | ||||||
|  |         _id = self.id_from_index(index) | ||||||
|  |         qb, sqb = QBuffer(), QBuffer() | ||||||
|  |         qb.open(QBuffer.ReadWrite) | ||||||
|  |         sqb.open(QBuffer.ReadWrite) | ||||||
|  |         pix.save(qb, "JPG") | ||||||
|  |         spix.save(sqb, "JPG") | ||||||
|  |         data = str(qb.data()) | ||||||
|  |         sdata = str(sqb.data()) | ||||||
|  |         qb.close() | ||||||
|  |         sqb.close() | ||||||
|  |         self.db.update_cover(_id, data, scaled=sdata) | ||||||
|  |         self.refresh_row(index.row()) | ||||||
|  |      | ||||||
|  |     def add_formats(self, paths, index): | ||||||
|  |         for path in paths: | ||||||
|  |             f = open(path, "rb")       | ||||||
|  |             title = os.path.basename(path) | ||||||
|  |             ext = title[title.rfind(".")+1:].lower() if "." in title > -1 else None | ||||||
|  |             _id = self.id_from_index(index) | ||||||
|  |             self.db.add_format(_id, ext, f) | ||||||
|  |             f.close() | ||||||
|  |         self.refresh_row(index.row()) | ||||||
|  |         self.emit(SIGNAL('formats_added'), index) | ||||||
|  |      | ||||||
|  |     def rowCount(self, parent):  | ||||||
|  |         return len(self._data) | ||||||
|  |          | ||||||
|  |     def columnCount(self, parent):  | ||||||
|  |         return len(self.FIELDS)-3 | ||||||
|  |      | ||||||
|  |     def setData(self, index, value, role): | ||||||
|  |         done = False | ||||||
|  |         if role == Qt.EditRole: | ||||||
|  |             row = index.row() | ||||||
|  |             _id = self._data[row]["id"] | ||||||
|  |             col = index.column() | ||||||
|  |             val = unicode(value.toString().toUtf8(), 'utf-8').strip() | ||||||
|  |             if col == 0:  | ||||||
|  |                 col = "title" | ||||||
|  |             elif col == 1:  | ||||||
|  |                 col = "authors" | ||||||
|  |             elif col == 2:  | ||||||
|  |                 return False | ||||||
|  |             elif col == 3:  | ||||||
|  |                 return False | ||||||
|  |             elif col == 4:  | ||||||
|  |                 col, val = "rating", int(value.toInt()[0]) | ||||||
|  |                 if val < 0:   | ||||||
|  |                     val = 0 | ||||||
|  |                 if val > 5:  | ||||||
|  |                     val = 5 | ||||||
|  |             elif col == 5:  | ||||||
|  |                 col = "publisher" | ||||||
|  |             else:  | ||||||
|  |                 return False | ||||||
|  |             self.db.set_metadata_item(_id, col, val) | ||||||
|  |             self._data[row][col] = val       | ||||||
|  |             self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \ | ||||||
|  |                                 index, index) | ||||||
|  |             for i in range(len(self._orig_data)): | ||||||
|  |                 if self._orig_data[i]["id"] == self._data[row]["id"]: | ||||||
|  |                     self._orig_data[i][col] = self._data[row][col] | ||||||
|  |                     break       | ||||||
|  |             done = True | ||||||
|  |         return done | ||||||
|  |      | ||||||
|  |     def update_tags_and_comments(self, index, tags, comments): | ||||||
|  |         _id = self.id_from_index(index) | ||||||
|  |         self.db.set_metadata_item(_id, "tags", tags) | ||||||
|  |         self.db.set_metadata_item(_id, "comments", comments) | ||||||
|  |         self.refresh_row(index.row()) | ||||||
|  |      | ||||||
|  |     def flags(self, index): | ||||||
|  |         flags = QAbstractTableModel.flags(self, index) | ||||||
|  |         if index.isValid():        | ||||||
|  |             if index.column() not in [2, 3]:   | ||||||
|  |                 flags |= Qt.ItemIsEditable     | ||||||
|  |         return flags | ||||||
|  |      | ||||||
|  |     def set_data(self, db):     | ||||||
|  |         self.db    = db | ||||||
|  |         self._data = self.db.get_table(self.FIELDS)     | ||||||
|  |         self._orig_data = self._data | ||||||
|  |         self.sort(0, Qt.DescendingOrder) | ||||||
|  |         self.reset() | ||||||
|  |      | ||||||
|  |     def headerData(self, section, orientation, role):     | ||||||
|  |         if role != Qt.DisplayRole: | ||||||
|  |             return NONE | ||||||
|  |         text = "" | ||||||
|  |         if orientation == Qt.Horizontal:       | ||||||
|  |             if   section == 0: text = "Title" | ||||||
|  |             elif section == 1: text = "Author(s)" | ||||||
|  |             elif section == 2: text = "Size" | ||||||
|  |             elif section == 3: text = "Date" | ||||||
|  |             elif section == 4: text = "Rating" | ||||||
|  |             elif section == 5: text = "Publisher" | ||||||
|  |             return QVariant(self.trUtf8(text)) | ||||||
|  |         else: return QVariant(str(1+section)) | ||||||
|  |      | ||||||
|  |     def info(self, row): | ||||||
|  |         row = self._data[row] | ||||||
|  |         cover = self.db.get_cover(row["id"]) | ||||||
|  |         exts = ",".join(self.db.get_extensions(row["id"]))     | ||||||
|  |         if cover: | ||||||
|  |             pix = QPixmap() | ||||||
|  |             pix.loadFromData(cover, "", Qt.AutoColor) | ||||||
|  |             cover = None if pix.isNull() else pix       | ||||||
|  |         tags = row["tags"] | ||||||
|  |         if not tags: tags = "" | ||||||
|  |         comments = row["comments"] | ||||||
|  |         if not comments:  | ||||||
|  |             comments = "" | ||||||
|  |         comments = TableView.wrap(comments, width=80) | ||||||
|  |         return exts, tags, comments, cover | ||||||
|  |      | ||||||
|  |     def id_from_index(self, index): return self._data[index.row()]["id"] | ||||||
|  |     def id_from_row(self, row): return self._data[row]["id"] | ||||||
|  |      | ||||||
|  |     def refresh_row(self, row): | ||||||
|  |         datum = self.db.get_row_by_id(self._data[row]["id"], self.FIELDS) | ||||||
|  |         self._data[row:row+1] = [datum] | ||||||
|  |         for i in range(len(self._orig_data)): | ||||||
|  |             if self._orig_data[i]["id"] == datum["id"]: | ||||||
|  |                 self._orig_data[i:i+1] = [datum] | ||||||
|  |                 break | ||||||
|  |         self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \ | ||||||
|  |                     self.index(row, 0), self.index(row, self.columnCount(0)-1)) | ||||||
|  |      | ||||||
|  |     def book_info(self, _id): | ||||||
|  |         """ Return title, authors and cover in a dict """ | ||||||
|  |         cover = self.db.get_cover(_id) | ||||||
|  |         info = self.db.get_row_by_id(_id, ["title", "authors"]) | ||||||
|  |         info["cover"] = cover | ||||||
|  |         return info | ||||||
|  |      | ||||||
|  |     def data(self, index, role): | ||||||
|  |         if role == Qt.DisplayRole or role == Qt.EditRole:       | ||||||
|  |             row, col = index.row(), index.column() | ||||||
|  |             text = None | ||||||
|  |             row = self._data[row] | ||||||
|  |             if col == 4:  | ||||||
|  |                 r = row["rating"] if row["rating"] else 0 | ||||||
|  |                 if r < 0:  | ||||||
|  |                     r = 0 | ||||||
|  |                 if r > 5:  | ||||||
|  |                     r = 5 | ||||||
|  |                 return QVariant(r) | ||||||
|  |             if   col == 0:  | ||||||
|  |                 text = TableView.wrap(row["title"], width=35) | ||||||
|  |             elif col == 1:  | ||||||
|  |                 au = row["authors"] | ||||||
|  |                 if au: | ||||||
|  |                     au = au.split("&") | ||||||
|  |                     jau = [ TableView.wrap(a, width=30).strip() for a in au ] | ||||||
|  |                     text = "\n".join(jau) | ||||||
|  |             elif col == 2:  | ||||||
|  |                 text = TableView.human_readable(row["size"]) | ||||||
|  |             elif col == 3:  | ||||||
|  |                 text = time.strftime(TIME_WRITE_FMT, \ | ||||||
|  |                                 time.strptime(row["date"], self.TIME_READ_FMT)) | ||||||
|  |             elif col == 5:  | ||||||
|  |                 pub = row["publisher"] | ||||||
|  |                 if pub:  | ||||||
|  |                     text = TableView.wrap(pub, 20) | ||||||
|  |             if text == None:  | ||||||
|  |                 text = "Unknown" | ||||||
|  |             return QVariant(text) | ||||||
|  |         elif role == Qt.TextAlignmentRole and index.column() in [2, 3, 4]: | ||||||
|  |             return QVariant(Qt.AlignRight | Qt.AlignVCenter) | ||||||
|  |         elif role == Qt.ToolTipRole and index.isValid(): | ||||||
|  |             if index.column() in [0, 1, 4, 5]: | ||||||
|  |                 edit = "Double click to <b>edit</b> me<br><br>" | ||||||
|  |             else:  | ||||||
|  |                 edit = "" | ||||||
|  |             return QVariant(edit + "You can <b>drag and drop</b> me to the \ | ||||||
|  |                             desktop to save all my formats to your hard disk.") | ||||||
|  |         return NONE | ||||||
|  |      | ||||||
|  |     def sort(self, col, order): | ||||||
|  |         descending = order != Qt.AscendingOrder | ||||||
|  |         def getter(key, func):   | ||||||
|  |             return lambda x : func(itemgetter(key)(x)) | ||||||
|  |         if col == 0: key, func = "title", lambda x : x.lower() | ||||||
|  |         if col == 1: key, func = "authors", lambda x : x.split()[-1:][0].lower()\ | ||||||
|  |                                                        if x else "" | ||||||
|  |         if col == 2: key, func = "size", int | ||||||
|  |         if col == 3: key, func = "date", lambda x: time.mktime(\ | ||||||
|  |                                             time.strptime(x, self.TIME_READ_FMT)) | ||||||
|  |         if col == 4: key, func = "rating", lambda x: x if x else 0 | ||||||
|  |         if col == 5: key, func = "publisher", lambda x : x.lower() if x else "" | ||||||
|  |         self.emit(SIGNAL("layoutAboutToBeChanged()")) | ||||||
|  |         self._data.sort(key=getter(key, func)) | ||||||
|  |         if descending: self._data.reverse() | ||||||
|  |         self.emit(SIGNAL("layoutChanged()")) | ||||||
|  |         self.emit(SIGNAL("sorted()")) | ||||||
|  |      | ||||||
|  |     def search(self, query): | ||||||
|  |         def query_in(book, q): | ||||||
|  |             au = book["authors"] | ||||||
|  |             if not au : au = "unknown" | ||||||
|  |             pub = book["publisher"] | ||||||
|  |             if not pub : pub = "unknown" | ||||||
|  |             return q in book["title"].lower() or q in au.lower() or \ | ||||||
|  |                                                                 q in pub.lower() | ||||||
|  |         queries = unicode(query, 'utf-8').lower().split() | ||||||
|  |         self.emit(SIGNAL("layoutAboutToBeChanged()")) | ||||||
|  |         self._data = [] | ||||||
|  |         for book in self._orig_data: | ||||||
|  |             match = True | ||||||
|  |             for q in queries: | ||||||
|  |                 if query_in(book, q) : continue | ||||||
|  |                 else: | ||||||
|  |                     match = False | ||||||
|  |                     break | ||||||
|  |             if match: self._data.append(book) | ||||||
|  |         self.emit(SIGNAL("layoutChanged()")) | ||||||
|  |         self.emit(SIGNAL("searched()")) | ||||||
|  |      | ||||||
|  |     def delete(self, indices): | ||||||
|  |         if len(indices): self.emit(SIGNAL("layoutAboutToBeChanged()")) | ||||||
|  |         items = [ self._data[index.row()] for index in indices ]     | ||||||
|  |         for item in items: | ||||||
|  |             _id = item["id"] | ||||||
|  |             try: | ||||||
|  |                 self._data.remove(item) | ||||||
|  |             except ValueError: continue | ||||||
|  |             self.db.delete_by_id(_id) | ||||||
|  |             for x in self._orig_data: | ||||||
|  |                 if x["id"] == _id: self._orig_data.remove(x) | ||||||
|  |         self.emit(SIGNAL("layoutChanged()")) | ||||||
|  |         self.emit(SIGNAL("deleted()")) | ||||||
|  |         self.db.commit()     | ||||||
|  |      | ||||||
|  |     def add_book(self, path): | ||||||
|  |         """ Must call search and sort on this models view after this """ | ||||||
|  |         _id = self.db.add_book(path)     | ||||||
|  |         self._orig_data.append(self.db.get_row_by_id(_id, self.FIELDS)) | ||||||
|  | 
 | ||||||
|  | class DeviceBooksModel(QAbstractTableModel): | ||||||
|  |     @apply | ||||||
|  |     def booklist():  | ||||||
|  |         doc = """ The booklist this model is based on """ | ||||||
|  |         def fget(self): | ||||||
|  |             return self._orig_data | ||||||
|  |         return property(doc=doc, fget=fget) | ||||||
|  |      | ||||||
|  |     def __init__(self, parent): | ||||||
|  |         QAbstractTableModel.__init__(self, parent)   | ||||||
|  |         self._data = [] | ||||||
|  |         self._orig_data = [] | ||||||
|  |      | ||||||
|  |     def set_data(self, book_list):     | ||||||
|  |         self._data = book_list | ||||||
|  |         self._orig_data = book_list | ||||||
|  |         self.reset() | ||||||
|  |      | ||||||
|  |     def rowCount(self, parent):  | ||||||
|  |         return len(self._data) | ||||||
|  |      | ||||||
|  |     def columnCount(self, parent):  | ||||||
|  |         return 4 | ||||||
|  |      | ||||||
|  |     def headerData(self, section, orientation, role): | ||||||
|  |         if role != Qt.DisplayRole: | ||||||
|  |             return NONE | ||||||
|  |         text = "" | ||||||
|  |         if orientation == Qt.Horizontal:       | ||||||
|  |             if section == 0: text = "Title" | ||||||
|  |             elif section == 1: text = "Author(s)" | ||||||
|  |             elif section == 2: text = "Size" | ||||||
|  |             elif section == 3: text = "Date" | ||||||
|  |             return QVariant(self.trUtf8(text)) | ||||||
|  |         else: return QVariant(str(1+section)) | ||||||
|  |      | ||||||
|  |     def data(self, index, role):     | ||||||
|  |         if role == Qt.DisplayRole: | ||||||
|  |             row, col = index.row(), index.column() | ||||||
|  |             book = self._data[row] | ||||||
|  |             if col == 0:  | ||||||
|  |                 text = TableView.wrap(book.title, width=40) | ||||||
|  |             elif col == 1: | ||||||
|  |                 au = book.author | ||||||
|  |                 au = au.split("&") | ||||||
|  |                 jau = [ TableView.wrap(a, width=25).strip() for a in au ] | ||||||
|  |                 text = "\n".join(jau)                 | ||||||
|  |             elif col == 2:  | ||||||
|  |                 text = TableView.human_readable(book.size) | ||||||
|  |             elif col == 3:  | ||||||
|  |                 text = time.strftime(TIME_WRITE_FMT, book.datetime) | ||||||
|  |             return QVariant(text) | ||||||
|  |         elif role == Qt.TextAlignmentRole and index.column() in [2, 3]: | ||||||
|  |             return QVariant(Qt.AlignRight | Qt.AlignVCenter) | ||||||
|  |         return NONE | ||||||
|  |      | ||||||
|  |     def info(self, row): | ||||||
|  |         row = self._data[row] | ||||||
|  |         cover = None | ||||||
|  |         try: | ||||||
|  |             cover = row.thumbnail | ||||||
|  |             pix = QPixmap() | ||||||
|  |             pix.loadFromData(cover, "", Qt.AutoColor) | ||||||
|  |             cover = None if pix.isNull() else pix | ||||||
|  |         except:  | ||||||
|  |             traceback.print_exc() | ||||||
|  |         au = row.author if row.author else "Unknown" | ||||||
|  |         return row.title, au, TableView.human_readable(row.size), row.mime, cover | ||||||
|  |      | ||||||
|  |     def sort(self, col, order): | ||||||
|  |         def getter(key, func):  | ||||||
|  |             return lambda x : func(attrgetter(key)(x)) | ||||||
|  |         if col == 0: key, func = "title", lambda x : x.lower() | ||||||
|  |         if col == 1: key, func = "author", lambda x :  x.split()[-1:][0].lower() | ||||||
|  |         if col == 2: key, func = "size", int | ||||||
|  |         if col == 3: key, func = "datetime", lambda x: x | ||||||
|  |         descending = order != Qt.AscendingOrder | ||||||
|  |         self.emit(SIGNAL("layoutAboutToBeChanged()")) | ||||||
|  |         self._data.sort(key=getter(key, func)) | ||||||
|  |         if descending: self._data.reverse() | ||||||
|  |         self.emit(SIGNAL("layoutChanged()")) | ||||||
|  |         self.emit(SIGNAL("sorted()")) | ||||||
|  |      | ||||||
|  |     def search(self, query): | ||||||
|  |         queries = unicode(query, 'utf-8').lower().split() | ||||||
|  |         self.emit(SIGNAL("layoutAboutToBeChanged()")) | ||||||
|  |         self._data = [] | ||||||
|  |         for book in self._orig_data: | ||||||
|  |             match = True | ||||||
|  |             for q in queries: | ||||||
|  |                 if q in book.title.lower() or q in book.author.lower(): continue | ||||||
|  |                 else: | ||||||
|  |                     match = False | ||||||
|  |                     break | ||||||
|  |             if match: self._data.append(book) | ||||||
|  |         self.emit(SIGNAL("layoutChanged()")) | ||||||
|  |         self.emit(SIGNAL("searched()")) | ||||||
|  |      | ||||||
|  |     def delete(self, indices): | ||||||
|  |         paths = [] | ||||||
|  |         rows = [ index.row() for index in indices ] | ||||||
|  |         if not rows:  | ||||||
|  |             return | ||||||
|  |         self.emit(SIGNAL("layoutAboutToBeChanged()")) | ||||||
|  |         elems = [ self._data[row] for row in rows ] | ||||||
|  |         for e in elems: | ||||||
|  |             _id = e.id | ||||||
|  |             paths.append(e.path) | ||||||
|  |             self._orig_data.delete_book(_id) | ||||||
|  |             try:  | ||||||
|  |                 self._data.remove(e) | ||||||
|  |             except ValueError:  | ||||||
|  |                 pass | ||||||
|  |         self.emit(SIGNAL("layoutChanged()")) | ||||||
|  |         return paths | ||||||
|  |      | ||||||
|  |     def path(self, index):   | ||||||
|  |         return self._data[index.row()].path | ||||||
|  |     def title(self, index):  | ||||||
|  |         return self._data[index.row()].title | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class DeviceModel(QAbstractListModel): | ||||||
|  |      | ||||||
|  |     memory_free      = 0 | ||||||
|  |     card_free        = 0 | ||||||
|  |     show_reader      = False | ||||||
|  |     show_card        = False | ||||||
|  |      | ||||||
|  |     def update_devices(self, reader=None, card=None): | ||||||
|  |         if reader != None:  | ||||||
|  |             self.show_reader = reader | ||||||
|  |         if card != None:  | ||||||
|  |             self.show_card = card | ||||||
|  |         self.emit(SIGNAL("layoutChanged()")) | ||||||
|  |      | ||||||
|  |     def rowCount(self, parent): | ||||||
|  |         base = 1 | ||||||
|  |         if self.show_reader: | ||||||
|  |             base += 1 | ||||||
|  |         if self.show_card: | ||||||
|  |             base += 1 | ||||||
|  |         return base | ||||||
|  |      | ||||||
|  |     def update_free_space(self, reader, card): | ||||||
|  |         self.memory_free = reader | ||||||
|  |         self.card_free = card | ||||||
|  |         self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \ | ||||||
|  |                         self.index(1), self.index(2)) | ||||||
|  |      | ||||||
|  |     def data(self, index, role): | ||||||
|  |         row = index.row()     | ||||||
|  |         data = NONE | ||||||
|  |         if role == Qt.DisplayRole: | ||||||
|  |             text = None | ||||||
|  |             if row == 0:  | ||||||
|  |                 text = "Library"   | ||||||
|  |             if row == 1 and self.show_reader:  | ||||||
|  |                 text = "Reader\n" + TableView.human_readable(self.memory_free) \ | ||||||
|  |                                   + " available"         | ||||||
|  |             elif row == 2 and self.show_card:  | ||||||
|  |                 text = "Card\n" + TableView.human_readable(self.card_free) \ | ||||||
|  |                                 + " available" | ||||||
|  |             if text:  | ||||||
|  |                 data = QVariant(text) | ||||||
|  |         elif role == Qt.DecorationRole:       | ||||||
|  |             icon = None | ||||||
|  |             if row == 0:  | ||||||
|  |                 icon = QIcon(":/library") | ||||||
|  |             elif row == 1 and self.show_reader:  | ||||||
|  |                 icon =  QIcon(":/reader") | ||||||
|  |             elif self.show_card:  | ||||||
|  |                 icon = QIcon(":/card") | ||||||
|  |             if icon:  | ||||||
|  |                 data = QVariant(icon) | ||||||
|  |         elif role == Qt.SizeHintRole: | ||||||
|  |             if row == 1:  | ||||||
|  |                 return QVariant(QSize(150, 70)) | ||||||
|  |         elif role == Qt.FontRole:  | ||||||
|  |             font = QFont() | ||||||
|  |             font.setBold(True) | ||||||
|  |             data =  QVariant(font) | ||||||
|  |         return data | ||||||
|  |      | ||||||
|  |     def is_library(self, index):  | ||||||
|  |         return index.row() == 0 | ||||||
|  |     def is_reader(self, index):  | ||||||
|  |         return index.row() == 1 | ||||||
|  |     def is_card(self, index):  | ||||||
|  |         return index.row() == 2 | ||||||
|  |      | ||||||
|  |     def files_dropped(self, files, index, ids):     | ||||||
|  |         ret = False | ||||||
|  |         if self.is_library(index) and not ids:  | ||||||
|  |             self.emit(SIGNAL("books_dropped"), files) | ||||||
|  |             ret = True | ||||||
|  |         elif self.is_reader(index): | ||||||
|  |             self.emit(SIGNAL("upload_books"), "reader", files, ids)  | ||||||
|  |         elif self.is_card(index): | ||||||
|  |             self.emit(SIGNAL("upload_books"), "card", files, ids)  | ||||||
|  |         return ret | ||||||