From d5c38927fb951c5e06f743cda3305ad90e4d1527 Mon Sep 17 00:00:00 2001 From: Jim Miller Date: Fri, 16 Dec 2011 10:57:40 -0600 Subject: [PATCH] Add initial version of plugin code--proof of concept stage. --- calibre-plugin/__init__.py | 81 ++++++++++ calibre-plugin/about.txt | 8 + calibre-plugin/config.py | 41 +++++ calibre-plugin/images/icon.png | Bin 0 -> 5785 bytes ...mport-name-fanfictiondownloader_plugin.txt | 0 calibre-plugin/plugin.py | 145 ++++++++++++++++++ calibre-plugin/ui.py | 71 +++++++++ 7 files changed, 346 insertions(+) create mode 100644 calibre-plugin/__init__.py create mode 100644 calibre-plugin/about.txt create mode 100644 calibre-plugin/config.py create mode 100644 calibre-plugin/images/icon.png create mode 100644 calibre-plugin/plugin-import-name-fanfictiondownloader_plugin.txt create mode 100644 calibre-plugin/plugin.py create mode 100644 calibre-plugin/ui.py diff --git a/calibre-plugin/__init__.py b/calibre-plugin/__init__.py new file mode 100644 index 0000000..544fa3a --- /dev/null +++ b/calibre-plugin/__init__.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +# -*- coding: utf-8 -*- +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'Apache License' +__copyright__ = '2011, Fanficdownloader team' +__docformat__ = 'restructuredtext en' + +# The class that all Interface Action plugin wrappers must inherit from +from calibre.customize import InterfaceActionBase + +class InterfacePluginDemo(InterfaceActionBase): + ''' + This class is a simple wrapper that provides information about the actual + plugin class. The actual interface plugin class is called InterfacePlugin + and is defined in the ui.py file, as specified in the actual_plugin field + below. + + The reason for having two classes is that it allows the command line + calibre utilities to run without needing to load the GUI libraries. + ''' + name = 'aa FanFictionDownLoader Plugin' + description = 'UI plugin to download and maintain FanFiction from various sites.' + supported_platforms = ['windows', 'osx', 'linux'] + author = 'Jim Miller' + version = (1, 0, 0) + minimum_calibre_version = (0, 7, 53) + + #: This field defines the GUI plugin class that contains all the code + #: that actually does something. Its format is module_path:class_name + #: The specified class must be defined in the specified module. + actual_plugin = 'calibre_plugins.fanfictiondownloader_plugin.ui:InterfacePlugin' + + def is_customizable(self): + ''' + This method must return True to enable customization via + Preferences->Plugins + ''' + return True + + def config_widget(self): + ''' + Implement this method and :meth:`save_settings` in your plugin to + use a custom configuration dialog. + + This method, if implemented, must return a QWidget. The widget can have + an optional method validate() that takes no arguments and is called + immediately after the user clicks OK. Changes are applied if and only + if the method returns True. + + If for some reason you cannot perform the configuration at this time, + return a tuple of two strings (message, details), these will be + displayed as a warning dialog to the user and the process will be + aborted. + + The base class implementation of this method raises NotImplementedError + so by default no user configuration is possible. + ''' + # It is important to put this import statement here rather than at the + # top of the module as importing the config class will also cause the + # GUI libraries to be loaded, which we do not want when using calibre + # from the command line + from calibre_plugins.fanfictiondownloader_plugin.config import ConfigWidget + return ConfigWidget() + + def save_settings(self, config_widget): + ''' + Save the settings specified by the user with config_widget. + + :param config_widget: The widget returned by :meth:`config_widget`. + ''' + config_widget.save_settings() + + # Apply the changes + ac = self.actual_plugin_ + if ac is not None: + ac.apply_settings() + + diff --git a/calibre-plugin/about.txt b/calibre-plugin/about.txt new file mode 100644 index 0000000..ae4d602 --- /dev/null +++ b/calibre-plugin/about.txt @@ -0,0 +1,8 @@ +FanFictionDownLoader Plugin Demo +=========================== + +Created by Jim Miller, borrowing heavily from Kovid Goyal's 'The +InterfacePlugin Demo' + +Requires calibre >= 0.7.53 + diff --git a/calibre-plugin/config.py b/calibre-plugin/config.py new file mode 100644 index 0000000..f36cb71 --- /dev/null +++ b/calibre-plugin/config.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'Apache License' +__copyright__ = '2011, Fanficdownloader team' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QWidget, QHBoxLayout, QLabel, QLineEdit + +from calibre.utils.config import JSONConfig + +# This is where all preferences for this plugin will be stored +# Remember that this name (i.e. plugins/fanfictiondownloader_plugin) is also +# in a global namespace, so make it as unique as possible. +# You should always prefix your config file name with plugins/, +# so as to ensure you dont accidentally clobber a calibre config file +prefs = JSONConfig('plugins/fanfictiondownloader_plugin') + +# Set defaults +prefs.defaults['hello_world_msg'] = 'Hello, World!' + +class ConfigWidget(QWidget): + + def __init__(self): + QWidget.__init__(self) + self.l = QHBoxLayout() + self.setLayout(self.l) + + self.label = QLabel('Hello world &message:') + self.l.addWidget(self.label) + + self.msg = QLineEdit(self) + self.msg.setText(prefs['hello_world_msg']) + self.l.addWidget(self.msg) + self.label.setBuddy(self.msg) + + def save_settings(self): + prefs['hello_world_msg'] = unicode(self.msg.text()) + diff --git a/calibre-plugin/images/icon.png b/calibre-plugin/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ad823e2ff4d53c376632ccb0e2fe60d96cc063ea GIT binary patch literal 5785 zcmV;K7G~**P)g@Il=sYFBw5pBmc%b4Il=J;xC0t2o4nd|H;U}JMAu57Yj`AYqjEkMlJ>n z@c%0#5(jAV=RC#!ug&uRK^O%7Jvs@de=f-f(?I$EC~p4ICl6-)eZvHFEF+5P+#UD8 z0!%OgMvngw%noF8{zt*z8WsPeC}z)k#Q5(s6G9ULH*(eAXN0>M8GoNBrvRn@V+-f= zKY#u*`1buf!`H8089sgf%<%2&HwG3K76x8k9tK_>9tJKhP6kd+P6mJg!WMJ$am6Y; zG(P&PtOrs7OyUqehx2zZ%^Lm;9yIVsVUqqIz{rk>z@H~Lpo$Se2oDV=!G8>lT>nup zDE2<5DgQ@N{H;adKPWu8TknH0n{f=o|1XU2aQ>37)Q^&o;4%1$86$nbVi4@+Z>1|q=!*G!IltL_}YYw+hx<^N0~|A6ts$UqWc`_J(26%zyR z?7NH@sR#skr{85{1d6~?5qI}JjFbf~dA>2C;t!~XVi^3j5>&`Q4B%?K56Q%?d0_TG zU{Yl@jD=VRO^Liy?=o;FwLwy432I4(5)@x+6%Ck0|DA{N|9xg;_)mPWC~`O7e}Fgg zfGIlxNX;kGz(io;UVt^#V+eBozW~G_4U9mV0UL(O6$0@AVv-xE8mRyK_b8Itff&eVW(H^OMyP%igBySb)c^VOm*LOfKMa3< z|6u?b{s)M`^q)T{t^pYY!mPkD6GXGH0%;~7A4mf&1X}ahP7 zXy6?_djjk7qb_I8lB(p0Qd)nG{k+4Vc=?hI0H;ZfuWqsII~l1Jnb8T!rezJd-jyByO^kh+Rf$~61wlSA60#345aGf%Omseieq zjeaI+Xd-XwnyAb}dOhdLWxDK&{WjgX?_G+2-$s-<}+Zjrwq z0cf$B&?bT?{AM@Vwl>&+P!yZCNku^@coGVNsi3H!n434N*NR3Dfg@D6vg8ogLqs{pohQY0_jJ7&1GP-Tmh6y!XEERn1XV zbJ+f#ZvDwtp+Daua6I8zo1pp9qYg2DpeNesyQ=0shteL|7R4V54d>cke4TVc#w8S2 z9J9+$JRjRRgcG`Rk3Q7|d={;`87-(Yh}_IF*YlD@H?XJhdHK3sz_*1V-L$WqmWHEo_RO~GthD+(xbc*uR4gq*F=)O=de(FvR#aCL|&2s_)i$X1@ zwjm$-tb+a_yu!C@ZKe0xO22M3=(&>(g+ZwmPW1@2(*V*7N(8el$ItpP3_&+-XBjgE zr8fLZDERqzlmw5zvGrQEUo5gQKC2>5)v{vXSfTtS45CZ}kms7-yQXCIYq+~{<2Osm zROr(&pMo0O0hQKQb(`k-bmE&fPO=IrS129t?U=K|FFNo2z;Z3g;6eO1n75 z)YY3Hpo`bK<+$Y{(IqB<+H=HrjX#Wv22dUyg3{Y(5V@75XPWd0$6DV}bci`94EH&6 zK7@N3@5$iW$(kHY#?T81Q45tdeo-YXm^0jZBg^UwU15>O&=jq-%qf0y;(Y=@4Fqex zuF$n`Ic}CYd?lSp$o^MDYR}dM)&YGbzKhCytXT{z8x5I_#1ab{ABpMF4MWqDby?Qi z(20^5=*EINo9=jy&N?0}HweMmwIg%gjR;LmU~9H)rB=M%HBW^_kCUVvpPuQj)q>T)+FUhB?A@(B3}G>PyygZs z=j9xgyzrXhti}2(=5*32qy|X4NUg$TrHZtRts-QJj457C1J(w~2zdfamB9gb&5b}w z0Nk+T2M4~y<=8Hpal3@Z%{6nAT;Ah|DqE^hS&ih1RbF#2-*DW{X90;d8?|e4KT}0u`KC(T>c3_ ztQCdEQH0@tX7?_4*R*WKwo(LhNuVOsT&0Ni#T1m5zNDcFLZRfr7iqPPP<)U^t1m4T z4EDhXAEJF0IekzcD$Sd3UZ5ZdX>u)C#42}bN%oq%-Esbzox9o1Zf^H(lP>)1-puaI ze>3yX{Qvj;eXrU2>!T&EXT|z;k%ZJ)yspOeJY?NmTopN_)fpwnEf^|@J0*lp9@f!ScArI`P#~fQQ;w(2i>{ct2Wp-^WvR;uOtyI z?hsWTeNJm-w|1Oa8@GRvfnol*?LX=9V7caamN4&-VEVh-zLgKGc4BjY%))M71pczxV8ENt8v~@4= zT>>x7OtL?u?fkJuirdq}(<3D=mF$>|-_w+VBkS1yE$pKwW8!<_C&qsEvGI&F0x|Q8 z+<@$K#}p|W^I1oRKmQsMz_?GQcip%%8*l0p?Vsa?x>pNBPFX=`h zg+Y;Q5mpx^s2pMi7~u-oC*&RMGqIwN&W@DGPA1l6n5Z?onc#lL5SPNpwn@6~0@!CV zyz0H-3b9xzi2N01mi@=-r$v9+gFj*ItK%Eq)7pjX_-V+V`pj1rxaJ41FCFbVqL^Pr z_qipJr9(TCePxPQ{)4$TV1Is40xn~x{2%W*AdLdsIX?`|?=MMQia+B$Gj>!cxpw)8 zC_|tv|JH}WB`~gYi;>18SkI8NTRH4lr>343b_(U~>o0qIAudLu45Kp)M%zF=D}Uto z4~!m8rXAOoi1QzPPlH z5B85&gCknybtLvax_WVViazbmbv~%!U9y84%$%W_pbm7u+ zIUSJq+7QF7h@}kjh9yw+7c;qA<2-K-Qu2h9OdN$i$t!mE#9&n}WiG}1#VSGJ&kM*1 z#*}&OyQ0@tb=#ow#_WbX9Th=d&$^3-8RY;=OlN!N9U6zq-gxPr!2nM#Uk`zl(_ zZ4eq)Itf36;sE zF(igyK_pYsW=Iq}B9e&PL<2T~cx_{yTn~%NOYxTnfIKEW6ri7QBhNm9t$SrmDI>G#x|dNM_VR+PCZd%nM$YY zOg-)($O$ZLwvY1oSn^DWwFiyKXHX$LlWtHkjH&8nAtKlGO_(zO3sBCr$3{_v@o)C_ zp1rn53@R$L*ET9Das&gYF|-J%G3pT|h+-&ei1B^#8H4l=iAqdp0gd62w8Tez#I%@b zlw#V7h~T3o6>Xpou@FK0<66qK+q>O$9=m&c+g`6n_j;{PGP&Kk*`1x=eDlpW-+bSP z#$(u0rSOuHTH8&c-&zud{(3t|f1wuKw=&P(Y9gB%D4><~Ln$+XP#RUBMgpH3sS46LG8J-#19Ur%~=_N*eNRvwlD$2=8FM;d6em7S}C|7_h7*D zWw83<#-s_)+zn>@gn7*k(mRiqN+IFdG8XK}8lU zug6dY0cwa9D={{XB6Xjeao#4Ab*z8*_PAUu1OCI2?=>A%Q*{l!4-|WVlPfLLB&z+* zuqtp&nG7yJQzXYg6AM0lJGSpd;r50s@Yr=hL$$PJ(E@Pnft}briy;3P(FpP6nB9&?~l7{@iIox1b^qi_&HCOf!h}#v=-Py3;I+3@j zeNPqz@go&n9*d|m;)q?f7_4)b1G;i4BcARAH8=ye~2v{^_#%DSV|!ts?l}q$KVv@FR)53@YdZ$D=Oj2EXMT0DuY)jncsKhfyBi!UchLb5zugQ% z(>8Dg4$*oPu=ifXg8g}Lt$l|9i2)KmdIh4b4PcvhH`rD&3H^6U#A1gl~LCG32JK07i}RKyCox1K#rUKGyJa`Eg@UCRipzWX$a<8KRs>(wrroiDa(6pf}{!;~k=;ETt<#{)!7 z?CG^8`W$Uw)<-{mPC=&6=Hy`t^;1AeS!CA}n(!~|{{ZB!A6VPIC8;+I2+QOMDlr}n zW8YD6Fh%M`Rn*D|Gk$8*l_HDXKeA3up9jtrJE`p#DYs0!E}>CxRh?yHUZ~ugI|A?plZx#{KN_$C9y~qPaPz|QouI%VX(}93>*Pw zuO^>79Pcjy_3Ur7p7q-0V41yKt4I0b&mU7tgWl6am|WcOE`?GC5pUSjfWI4m%ROLS zu!4EJl5hFD4WfJ2gYez^sTQ*u(s%b;o#BivRBAf^Qc*RQRm;aLtBQ)P-NX}lt8zAV z2F6*WlEY+rU8uH++60CXGsr=*`JN18mW$xTxGJg7uk%(TE0lAd3$US*8D+{bnD_}n zC}(3=Fyo+}U+-<;oN_60r#~1(SuCBKJjxaY*t(nzUCS~CY9#2b#amN9QShb(rlgqv zij!8i5^{w`PqFFR_^puz)FcWPa)?%_WEQ{5LIc3iBa;L`2U%FeY-|_DnEp`m9@qM= zs*Fzm5h;12*Mnhf##`7z0`4T4?kyOGGS|AUuxT8_?9m zE29e{k$I6Sx%R45U6dxtboZG3Li3G`(WJ{hvmwX?r4}URT{7%uJd7$Q$SI~TgA~|^ z+lyPMAZnrF8xPXYsx8$(WkETNKB90sX_+H%qApwg%Bqh6?syH+{EVO#B3t1(xE@k7 zxu@mHp9y6X(5zFcMzPxpnhjHvu!=Ir(G(bRaRC~o#Uw!%d*`cvp_A> zb*@zT%{Kr=u06?O5y^l3f;S4ifMzUAfBXAq6QJBH0HzctxEN^Dky+x06@IRhPoYQS zzlj2WMnF{^HV6<4jew}ns3zcQnb8bfUk3CV62cM1>jUU7(ivzCM9ri;UXDO-X0ES; zhWUSJ*dzV>4Fl7p{5HZcaq;QF&#C5OV@h%M=@X>Lft^*7=U8 z4g~slNyLNCq&{1E>Rt+^d1D5X7T!safxM0Q6odr%t8HJ;d$&QNp{?NDejX zr;+V!i29`@15+RDiUE}fr0P|nzt#h&d17c;d5>f$SG+QqLXM8`N_{53cI-q7DQC#j8ViZ2(v+(}AlFvH@4jNBRCW(pr_^LXj*?OdJD>p`M_Q;plKN most_recent: + most_recent = timestamp + most_recent_id = record[0] + + if most_recent_id is not None: + # Get the row number of the id as shown in the GUI + row_number = self.db.row(most_recent_id) + # Get a reference to the View plugin + view_plugin = self.gui.iactions['View'] + # Ask the view plugin to launch the viewer for row_number + view_plugin._view_books([row_number]) + + def config(self): + self.do_user_config(parent=self) + # Apply the changes + self.label.setText(prefs['hello_world_msg']) + diff --git a/calibre-plugin/ui.py b/calibre-plugin/ui.py new file mode 100644 index 0000000..d978581 --- /dev/null +++ b/calibre-plugin/ui.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'Apache License' +__copyright__ = '2011, Fanficdownloader team' +__docformat__ = 'restructuredtext en' + +if False: + # This is here to keep my python error checker from complaining about + # the builtin functions that will be defined by the plugin loading system + # You do not need this code in your plugins + get_icons = get_resources = None + +# The class that all interface action plugins must inherit from +from calibre.gui2.actions import InterfaceAction +from calibre_plugins.fanfictiondownloader_plugin.plugin import DemoDialog + +class InterfacePlugin(InterfaceAction): + + name = 'FanFictionDownLoader' + + # Declare the main action associated with this plugin + # The keyboard shortcut can be None if you dont want to use a keyboard + # shortcut. Remember that currently calibre has no central management for + # keyboard shortcuts, so try to use an unusual/unused shortcut. + action_spec = ('FanFictionDownLoader', None, + 'Run the FanFictionDownLoader Plugin', None) + + def genesis(self): + # This method is called once per plugin, do initial setup here + + # Set the icon for this interface action + # The get_icons function is a builtin function defined for all your + # plugin code. It loads icons from the plugin zip file. It returns + # QIcon objects, if you want the actual data, use the analogous + # get_resources builtin function. + # + # Note that if you are loading more than one icon, for performance, you + # should pass a list of names to get_icons. In this case, get_icons + # will return a dictionary mapping names to QIcons. Names that + # are not found in the zip file will result in null QIcons. + icon = get_icons('images/icon.png') + + # The qaction is automatically created from the action_spec defined + # above + self.qaction.setIcon(icon) + self.qaction.triggered.connect(self.show_dialog) + + def show_dialog(self): + # The base plugin object defined in __init__.py + base_plugin_object = self.interface_action_base_plugin + # Show the config dialog + # The config dialog can also be shown from within + # Preferences->Plugins, which is why the do_user_config + # method is defined on the base plugin class + do_user_config = base_plugin_object.do_user_config + + # self.gui is the main calibre GUI. It acts as the gateway to access + # all the elements of the calibre user interface, it should also be the + # parent of the dialog + d = DemoDialog(self.gui, self.qaction.icon(), do_user_config) + d.show() + + def apply_settings(self): + from calibre_plugins.fanfictiondownloader_plugin.config import prefs + # In an actual non trivial plugin, you would probably need to + # do something based on the settings in prefs + prefs +