From 3d3bc6e8d7ecfe9ec531a08c5b25938f8bd96696 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 20 Dec 2010 20:05:59 +0000 Subject: [PATCH 01/43] #7964: Allow setting the series index in bulk metadata search&replace --- src/calibre/gui2/custom_column_widgets.py | 2 -- src/calibre/gui2/dialogs/metadata_bulk.py | 6 +----- src/calibre/gui2/library/models.py | 4 +--- src/calibre/library/custom_columns.py | 3 +++ src/calibre/library/database2.py | 22 +++++++++++++++++++++- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 6e4fc0a0ac..ca9243e51e 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -587,8 +587,6 @@ class BulkSeries(BulkBase): else: s_index = self.db.get_custom_extra(book_id, num=self.col_id, index_is_id=True) - if s_index is None: - s_index = 1.0 extras.append(s_index) self.db.set_custom_bulk(book_ids, val, extras=extras, num=self.col_id, notify=notify) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index e0f1f83c73..976a753254 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -375,7 +375,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): else: val = mi.get(field, None) if val is None: - val = [] + val = [''] elif not fm['is_multiple']: val = [val] elif field == 'authors': @@ -547,11 +547,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): if not dest: dest = source dfm = self.db.field_metadata[dest] - mi = self.db.get_metadata(id, index_is_id=True,) - val = mi.get(source) - if val is None: - return val = self.s_r_do_regexp(mi) val = self.s_r_do_destination(mi, val) if dfm['is_multiple']: diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 661f21e53d..0d70fbc610 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -725,9 +725,7 @@ class BooksModel(QAbstractTableModel): # {{{ return False val = qt_to_dt(val, as_utc=False) elif typ == 'series': - val, s_index = parse_series_string(self.db, label, value.toString()) - if not val: - val = s_index = None + val = unicode(value.toString()).strip() elif typ == 'composite': tmpl = unicode(value.toString()).strip() disp = cc['display'] diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index e95ace2cd4..07ea407460 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -445,6 +445,9 @@ class CustomColumns(object): index_is_id=True) val = self.custom_data_adapters[data['datatype']](val, data) + if data['datatype'] == 'series' and extra is None: + (val, extra) = self._get_series_values(val) + if data['normalized']: if data['datatype'] == 'enumeration' and ( val and val not in data['display']['enum_values']): diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index cba49ae6ae..eb72c1b407 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -2133,9 +2133,27 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.conn.execute('DELETE FROM tags WHERE id=?', (id,)) self.conn.commit() + series_index_pat = re.compile(r'(.*)\[([.0-9]+)\]') + + def _get_series_values(self, val): + if not val: + return (val, None) + match = self.series_index_pat.match(val) + if match is not None: + idx = match.group(2) + try: + idx = float(idx) + return (match.group(1).strip(), idx) + except: + pass + return (val, None) + def set_series(self, id, series, notify=True, commit=True): self.conn.execute('DELETE FROM books_series_link WHERE book=?',(id,)) - self.conn.execute('DELETE FROM series WHERE (SELECT COUNT(id) FROM books_series_link WHERE series=series.id) < 1') + self.conn.execute('''DELETE FROM series + WHERE (SELECT COUNT(id) FROM books_series_link + WHERE series=series.id) < 1''') + (series, idx) = self._get_series_values(series) if series: if not isinstance(series, unicode): series = series.decode(preferred_encoding, 'replace') @@ -2147,6 +2165,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): else: aid = self.conn.execute('INSERT INTO series(name) VALUES (?)', (series,)).lastrowid self.conn.execute('INSERT INTO books_series_link(book, series) VALUES (?,?)', (id, aid)) + if idx: + self.set_series_index(id, idx, notify=notify, commit=commit) self.dirtied([id], commit=False) if commit: self.conn.commit() From c235e3745c7316c18441ed2186215bbb5758e27b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 18:42:49 -0700 Subject: [PATCH 02/43] Comments editor: Add colors --- resources/images/format-fill-color.png | Bin 0 -> 16588 bytes resources/images/format-text-color.png | Bin 0 -> 5106 bytes src/calibre/gui2/comments_editor.py | 34 ++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 resources/images/format-fill-color.png create mode 100644 resources/images/format-text-color.png diff --git a/resources/images/format-fill-color.png b/resources/images/format-fill-color.png new file mode 100644 index 0000000000000000000000000000000000000000..946bead5dae2abe62866d7bb9ae901cfbda262f7 GIT binary patch literal 16588 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

mSz=;69k8xmJ>5fIHoe{IAkj+Y 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 literal 0 HcmV?d00001 diff --git a/resources/images/format-text-color.png b/resources/images/format-text-color.png new file mode 100644 index 0000000000000000000000000000000000000000..2ec27d559de328c0e62ab3f0240bfd675a0b4630 GIT binary patch literal 5106 zcmVEojiN_!D6pi%*LxhZY((^=okDiZ)44~rCvMUSP{VGmtX$p zz~9-hl@A}6*HTxL)FL0m9K<-p+~^Mx{DS9t-Dl(Ne@EGAAs+7BP9)Y_zKyZWi#+t$ zV~>3}sGW$7%s7q{RaG0N@^Ub>B+$(V7+@-g{Z^(&I>$UuQ!)I53j(EuI(YjQ5}nrx zQdn>$n^%q6uwz94A`%s;TxpWhCXV_SVJ=iv8Dw~`h>>d-7AEeEXG8^YFq zZbbmbn2HMExIf#<(?7%s|6Hb<7jhm;_nZal1b+HYl59e}2E^^RWbh>65kO_@0K$My zv1+l;pNcUi=P?~^l3$1#>QbWXPBpHHbPxN5ptB zujSCgK#z#b0?q+01TH1mO$?6!%B%z?f&T`!IF9r42OoU!&Aex}{^fSmT$17=va^l!a@BH?oFMV^*@ck=RtcZ!oWvY6$hH^P_4N<4YSngf`QqWI^Bd4h%gI|V0i0F0 zJFfuT=3c}AYNyN=*z+ta_hj$ivSrHv@GIa~D^{%dqHEk6O<(=ryT_a3G7!V0BdGE4 zqe)WDJ*1nCk8oI_Y6fO!W*`>h&Dpa#I%Q<-T}9GIr%dVWudC|8B;SCvT<9iKdCQ9AV(_SwWKmKrufpaEi>@^09Xb-Xe{p zx2C2XyG)JOIQ)eAeY5c06#cXIF)*zQM8T2=uu?NI#zc)V7-Kj-X%agYE~LAq1ra&9 zVT^Zd(xfK`Z~iG%=V8p3Xm9@`G|yxA>*WblA1Ehp!}A~_%xQXth~qPq-&|Ycxd#xV zHfrwsd+7h4dCeu$laA8(#(6Zq@D3uWI%|C| zRs`TUPShAfUp&sv`SbB2qa90cW8-rJ(P(#01FC}RA9C-#pU9iY_9g!X8fbLwthznS zXxKgG%{Rv2HYSPB*%u@!fV4r^y~1rd`<&ZopHxTUp9Ebpe7~ffMC5q%5=}*;ymFv+&o zO^Do%@(5_Yo0E1;^0CF!A7O6u4jfb3%^%vf)Al1lZdG^$#u!3`V_^Qh0r%Eztjt?f z-N=1$;Nu1V1_>omZc;smb^KzX=-m(#fj*z}?HZdt;KuOMG;m zAT61r(rxgbY2u<6f5}w^V*wq!_bA#}=sW=Ft$Txpf%AuTER8%aL{bcV5ZGM&uNjFI z0UV3R>vGkY2V8f5TE={8@^KMs*G5^pwy^8{_3{Mp!r(TN>#l3E*#-M+ZchMD4Q^%P zw-$S*c+P+%BX3cco|*du4475|2*6Ge5RJOI_V>kNyUR4`GHqH*fGnM)mk+H?N@#bCt)c#bp5MX50ZiD>k2*+v~F4g`**sK6jhP)Zux zb`te(r6(tn$mr2OF(=*bB;ZgvnXw{(sPCIpR@=Ydbq`Rei+3y>rzc>xfG`R`(xv<@ zs;bf--A6<6ywRpI9^Aq6Mja~>EB{ViGC9glzv~tqemx42G<)|R4V0;qrovVPXztFi zv3^4$nlq>{Qv~46;9$w~9fk<(0ovNy++B%;9ErzI?G({|*F6%z?2e);fCY%hIC*D;p=7$^ItS2F6CJFJ zlq~=e#rN;$s=^&A#S+j1Sg!*Ny6f+id|@IoxRgWqBu%wInBgX`jIyn|FBZ2$A-GPhBz?Hys zI-PJ`cN4(0WRitPj!b&3x!DKe1ui{>h$8ZRR<8UWt5yZg(g?!W49xHf2fj0(v!{~j z_AUn1720vzXeQC$K| z2OLBc)uV_!4Lm_x+i$2$mn(PNamUnTG8sq!_uO+&MQ1$$+|0)5Y0Sq3y_`(1UN-%} zHXrW?_i@WBHq#!@^#O$S>l^9n>Syivte|nn9d}GkrBZWHm*0mY!)a!X|<%a1|8I>5Njc9Bf9=gWiJxKNq^I+%pdg&1Ne zN`!-W>}3gk70O>=!deLo3xx&Hfs61yd_DrqIoWU&Ak*yk=-?@YhiD~qOQKYA-GVUC zLHjvA{uKh}2{J@mYfeY9da> z<<t{0G9!E zHWC3qcqcEDdzQ~QQ7uJ)q93qjOVBw_a_@Y|7JxF(Rw9E$=w`lUnX18rg84fgKUOi+k5l=SnEEr82-4du7i zQt|m;YqrXux3?GH_XBNV;9Axu4PrA`w$BiG8glgn8oqBO3FR$%DYQaX#i<4X0Qh)Y z6g#1FivDB?tNI|909xrp`ANVOe3Wl;m0h=04gm!ajxqpP%9@uH|7XE1$>BKB9}IoV z9S<;>%^YJIu()LVielJED^Hi5M~isnPn-=MF&ghn(RTqbw>%4y7R=ojantdkJ-Hjn z^BDmC7Ukz9rqGAOH)ti$bS0ZqOA!F@PCk7Ceu2xMu()K=RFr_NfY1~HLo8v{v0WVaydpo=;+v^2`gmVhMBCQv1BGJ( zTj>Nk>ELY$t%KtQL*2Va@p%D52TRLap`wx;KYkq7ozRZRif}D!lR*0uZ2WNy^HGJR zpd;3{&Ahsf4hQp#OKI!K6%GcaL9?0B`5iq8#18Zh^gpt{qbV z>y%gwu1~`U6=K4ON1_RsDw(%meY{Z%?(9D#)D}j#z=jA!)P2geR zVS${15qSmhEoJrq{Fy>g%aJS0_u=k)mG+w@JYB&7#{xoG1Sre_Zij+Fyl(^AfE5bg z5O^v3gq&T$o0W5uaF-N$^M_?D0^D-1PW*g;A*vxDWH<_ofV(Mt^M^AV_=;x2{zouM z0N+;XeE1Rsist>yW}Wz1K~PK5U@3&~{!$bFZgaES32Mf%9Kt z9D>~ZutXDSrZykwgaw+xA;>D12YM6DO=fJou3Ym-E-rhiC0U?@JR*AC6g)9Lm>>*FL zVmSzCw21!bN>U{r;6w#%9){5Z*eS)YUR@A{y*J3j&J746fDi?RR$`!SH)IK*4jxIu zr))I^4Op1S5R9nlA90J7Z4)wfM!CzR#JS&T@v_K z_RWU^Az^ETU8;Pc9a;^%U*QrRSrhFS*a+M+di(srq>V(P*GPt|gs@?_E1aO8^6TlfxO#o*&RV{$p^zgz|wE%!vl>~%TjH(uZ=Xv!3 zO@t;Yv{F>H0D!uHbpTr-SfS;LQTV880eGG_C#Ve(Sp@vT zLUuw)RSUrP{R@KHP}NIqCdq zDNFW3L)8gj(xgdqec%5P_SZYwr>gJo?d^TRvfT-#Y6MVMS2r&biQJ&7pJUiMc$Vo0 zzUlk^J&8o(w0nI{E>2qkOrAV>a({pSVi8#kd=yyD`28LQwyEk5fyblK=rcV%J%MLb zPXwVCKz)7vGF83M_x(8{axO57%+ly(zC5vF>?X75!fsXFZ;aWas{0291|9}N8 Date: Mon, 20 Dec 2010 19:51:20 -0700 Subject: [PATCH 03/43] Comments editor completed --- resources/images/format-text-heading.png | Bin 0 -> 965 bytes resources/images/insert-link.png | Bin 0 -> 3696 bytes src/calibre/gui2/comments_editor.py | 87 ++++++++++++++++++++++- 3 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 resources/images/format-text-heading.png create mode 100644 resources/images/insert-link.png diff --git a/resources/images/format-text-heading.png b/resources/images/format-text-heading.png new file mode 100644 index 0000000000000000000000000000000000000000..970acb7d604b8ce0ea7c852c8a5c7d31f4c27ca3 GIT binary patch literal 965 zcmV;$13LVPP)*G|i&j z6}sZ_D{PREy6p!*c5Q)D350}DNy;x&ZGi=_V8b_5`~ek}K&Xh84QlhS<0LlsUV9wd zsT+ditQcu(*JozVnfscF004bwXXk?u;+E^W;CUWo*^mdMl*IFKd3pIZeY?E7yX#H6 z5hro$_{X4~Q~UbuJ1Wf706+#_yt)7Zr$GXQGP{(5O(Zf>K|G2pcnUr4AIT!J^uO!I zV=OK}X1Dtx2c!#-B6|S|{)7b*0tBW@RE{_Foj*2-1;HE)@NsEts9HvdHM>(k-9`F8>O3~{#uR*KT0veLU zZXnj_SaAIF7+$@81E;5FD69;YW%=19hihNpf_TuGrUChU9tzm~{PnnwQ!5rtC=?0- z<}yyZ+u#4h!B{L7Rn1QXxVSh7qwXL=!$cz5CHgg|=Wxd^FG4Y5A_)off_X%?v~-94 z{i43b?(hQ2p?k*!Vf1vN@DUOfaK{#p9nKf*dlE#}P}q4jF4YC}uAvW` z%_i@P7UZ1nb^`%L475{>6O%C-tL7)?h1hrth(t;PG%@O_h$sQkLK4ON^@S>e=VD?K z0V>+9cAL&?OtDT_AyrwO1dC?G*g_=HUld$NM?d_UKotzXAACLtOYFa3ejJDk1ZY9Q zz^BwCJbv<&gQ$(Wu`L_wTQB*$!~mQa(-G7BwUcdbdwZMjX&u}4I+n$~>p17${*Zi!~0vx{cbW$#4_ct z1Y~tD{4XHA6iW%yZZfQ3Q*Me=S)jGIw|9wZ_Y}!nDpf1k7qQQdTvsyuiHQ9-{TwAi nxPz4vY1)3a%$ z)`pfD2?WB}28?ZD5ePAh068{j0a6%Dk&skE5$8wBIF1L$RgNnOCaD0jDKNoFED7Nh z5h5v!pja#n1yUR&4VyGtM$1Su-90ni{rcVa?mhW2i&{fVjKs{3RQjtrb^G0Z@4WMU z_uTE(1?s4yjyme7qmDZ2sH2WL>ZqfRI{u%50HCw8(=VgkMkbjSM@gHGk~SUcHl64; zt#zA@b(;Xmi8!|7uH*K(uG{A- zx6cjSKG#!yj;H!qs{RyGeFOl{Jo8NEK<|NBP0bBUU}+H>GvDZBjy6eFSK?_p(M?iF z-*sI_O5rKxI7CE32nZsO5MXAowJ_GAdvACAZr8hx5VRB4-nM@IFSeZt^VO@rb|Fdm zq%n5sMHgKhcXYHnG{T?Rf&my~z{Vsxj!j}rlEjIQlO&GgI5tV5UB?}8JSV55>hn_G zVIgr)39%oZyW0$z_lcPaKvShsX^x_(-LRf-EzJen*-Xg7qKydCgpf@_hz21X*L>|3g81jFuezF6tz07;8X9We zXN-X{2^(XKwKg`!L>64O))d+D0GM-3oFnEwKpagt>6M^ER*4>uy#3YbL~rhVGpm@h zHsP!QvLxj^faVCoY$A~rg4zJl3Q!9(6I%;w?MM&?aS|ie8onQ3;erm#n4ZOpFTH@Z zKVHiWTD@`o#-}Di{-)J8tt9aJZ{PQq^yv#egL1iqT&^FLr~=Cj=Hd5RV_>WWn9Iyu zG}aCg@c;vPYdOcTeXx8iw#gAkihdF*Z#l_JOL-ko{?etF=0DnWBT&lmg_1rqd_lq^ zkSnBc1PMt(K#%|dAVFXTOcEo~F`}q~N~MAwJAZ@hjG4IN@++YnMGrmn(7KhYZ@iO$ z!BdfEpiv0=a_4vNrzPi{hr&PsVOWAx3Tc&wF&4%oW1<^lz--7`7Q(Wyu#%XS6;N!9 z0|^I$9I&a)69AZ*cJ12bP(U6`9KfNJ5GqZAQfxV;gyS*T6_S)7A*2)l2!aHJ5MTgS zIRI4<#}P({slEHV5mrK6_NB|w)zu}oZQp+NB^Q70l&$~P+i&5jtFFc{`8>3a;5r_J z5HQ9-Nd~hEV-1oKA*?ZinU%HdGT1ZL1VV7iCbq&r=#a^nn2V01qQt}%1uA=i<8*d* z(r5szt>n(Ht$G>AeW2NDuQ zK;Q@Py#Pcs)(x0>I0LMe##+}}Yr)LSJbVu^U@RM0BNzz_A`-AxlV*=g*I$4A@aT2_ z{rCH!3FoFf?`#Gyjw-r6j`Ue!7|tn`%h@mtr!n)`G)Oc|+!%}FbwE>7GtNDC36@`b z9a@^F;k^&K5Qb&E{Mr_L{rB&}Wy_Y~+0C1g$z*B@u=%;?@Rb!S;JOaF_w9zY21Ejl zjZFXu>o=^!+uPnoE|&xI@!3@hsX$NwQq0^slGOlU(&^MO-w%3&z(42*L67S>`+U#Y zL(((-NBVQ48BhsiaG*F)>Q9nnduYOxNlcK0l~inWij7TKA~E{HlpkCahNZu_;rbhp z&CWny-!TA;W5@dN&W;^8c<>`# zWAEN>-1DdRAfL};XlM{b0)g+tQ4St{?Q<3 z`iJGP{C2fZ!EALLm0`J@uq~H$6b3RVmy{}})3%&SrNRj>Dynh!AKkSHgzG#1suKeP z1u$C>2{2nUHn!m2Z{CZA?F%t~-h90Keis1X+;h&uqK>oilSkL0y`vpL5S-vM3`6YM zy$5&y;UDAhkwZApvmb&4X3v?64?lPxU0q#x@PP->*VjAtjO%)6Zf-&6_c{T_vuhuD z^oG;ra?)AgR4^uKKl|(@a2yZ5pMp{fLJ+KFG&MD2`L)Zj`MJ&5vwP3jGlvfz!HX}x z2mmM4kH^{{KYU`vTWLP%IUJT2Y4=4RY-^Q~v%`jn_tLZnjZv1=lN<2Z0#7Z&WCiBAOpot>R- z6h#>!L<2KtG?z21xj`G7N#Z19Y}}BBQEszj;1-Q8d&NU5Ndgy#i_q7aoz z1g#?wol);PDXg`K;~1#g3P`B{5cclf2ZCt0<<>QiC?$$Qs-jXO=Exx_Wzi9;sFWPC zN){d8DVm}w=5o1WjSSFeX>M%D1TAx*1a{~sD&9bvN&}3Fb*E-!P2GY%f`mW-xg97g%CqZ$pIl$ zUP?N~#K(lON3>4BN?Pei-G8WG(>7q#Zt+9`@Zk& z2XhfbgCG=$C@+cng;Ga_5WT+Z9QM4_VGOTC2LKEbk*+Dg)~#EE5Miq=Ra(P1YORD( zYq?Txt&}P)VN_`i!*c7;;9y7ho_)XD-rj+)tXP3erV+-Nv8C`-qgX0p%a$$Z>FL4Z z#T}b6na16IDkylqSMWTy;Q6US%6AKn8|0O*3K>$V^AQn~odI48? zxfwIFzuDW}eZd1idVm(pn~zLmBa$SZT&6WJR2)K2&q273!)MK%`-bbd{b%Z{rU2=5 zS~NSpDh8qNl;U(Ms5B!!A_Ql33~_It;AkIw1O=hD4Ldxh(| z@O&SRQi!z1;NT!Cl?q(f9r8SPU_wzbnwUA`I1Yj!fKm#^Sj4f0F@}|Lk_GJx2U?n2 zx*JldA>a2if$y{OJ@~E%YJM*_ajv8ly@SL{TNEM3K>vj>9nY^7$P4`ucF7=RjO26dVG! zG10yM`m>+EdO9t0)0&%>3FZg$g~ElIOeW4|v(Buh*=TE&SxGj0G- z`>O~5BGRto4!N!xRLUthu9KHa=9F@dDXESU+g@fmOpeM)N9Lf>KQuIyBLLX2VT0VY zYnKPW1&Z$Ap!H%IyD~Pe*0E=;b+u*JM3I*;+$2d{V~jgK2*DgPi@gBedFrXBYHz^L z5Gz-%lmI?OWEWVXT8kdzDXEoGS_l!juA_wzT1sC_DYfGmZH%LR-`Cb!9RxuHKu>0j zb<|Nu9d*=E$Nw!%?pF%{p zKf#Se*wAv#y(0MRjo?GC0E4^72y+bqsHq(^=L(d!KaIlk4_jueI_sbC^i;(-wZj#F z?z?e21CP)6>V1@2`epFZH-XY2oRIdTS&^Cv%6<{$ozLUwGk?bfd}HBSJTaB=P3YXu zX@UTV5tIM$7T)>B1<04XUcJVj*$#K!=K&J|uz>VN%mq@jK+`Wm>AhERaO2;goZy>F z9>rrLeh5?4Sf5k?8u1}9Jfn=7Q*CRv z&iY+={l;@>MrGG4OEVoy$!Y5Vyk?XSzK7jU+;8)tUAKHAe?K`+u$pA`@9J(b-hk6M znm17as{3_ya1*gHaS}j1{UN{hdmX$m?Ap4hY4N8iDdzFP6Mt=UrT9NrJj359o3VGD ziZF0;0ZvPViKdRIVdDclS=~qK(+;B!`Q;HE|cy%=1%mnMk z*MF=C6BVFV2Gk-t5hDO%P8#mI74jzn?5zF|dwWj_dL-cL;A;^)75VZ1Pl(t=Ft)F!IP1n$UCcdMYS@5aU$VQbQpW8mcpkZUrPwCQP#3a$NvH(ga3nVptMy0 O0000 Date: Mon, 20 Dec 2010 19:52:01 -0700 Subject: [PATCH 04/43] ... --- src/calibre/gui2/comments_editor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index a21390bcd0..fb09de984e 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -452,7 +452,7 @@ class Highlighter(QSyntaxHighlighter): # }}} -class Editor(QWidget): +class Editor(QWidget): # {{{ def __init__(self, parent=None): QWidget.__init__(self, parent) @@ -544,6 +544,8 @@ class Editor(QWidget): def code_dirtied(self, *args): self.source_dirty = True +# }}} + if __name__ == '__main__': app = QApplication([]) w = Editor() From 1b36225c7c96f72a43d7948f3079faa09c89ed2a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 20:13:22 -0700 Subject: [PATCH 05/43] ... --- src/calibre/gui2/comments_editor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index fb09de984e..5c4e3f4cb0 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -524,7 +524,10 @@ class Editor(QWidget): # {{{ def html(self): def fset(self, v): self.editor.html = v - return property(fget=lambda self:self.editor.html, fset=fset) + def fget(self): + self.tabs.setCurrentIndex(0) + return self.editor.html + return property(fget=fget, fset=fset) def change_tab(self, index): #print 'reloading:', (index and self.wyswyg_dirty) or (not index and From 3bd7e3457a95dd5693ffcd10c882ad9fc17ac69d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 20:28:59 -0700 Subject: [PATCH 06/43] ... --- src/calibre/gui2/comments_editor.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index 5c4e3f4cb0..e7647a8da3 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -202,6 +202,9 @@ class EditorWidget(QWebView): # {{{ def fget(self): ans = u'' + check = unicode(self.page().mainFrame().toPlainText()).strip() + if not check: + return ans try: raw = unicode(self.page().mainFrame().toHtml()) raw = xml_to_unicode(raw, strip_encoding_pats=True, @@ -534,11 +537,11 @@ class Editor(QWidget): # {{{ # self.source_dirty) if index == 1: # changing to code view if self.wyswyg_dirty: - self.code_edit.setPlainText(self.html) + self.code_edit.setPlainText(self.editor.html) self.wyswyg_dirty = False elif index == 0: #changing to wyswyg if self.source_dirty: - self.html = unicode(self.code_edit.toPlainText()) + self.editor.html = unicode(self.code_edit.toPlainText()) self.source_dirty = False def wyswyg_dirtied(self, *args): From 0968eef89469f15c19ab50f22b8659552cb78714 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 20:34:30 -0700 Subject: [PATCH 07/43] Redesign edit metadata single dialog, using the new WYSWYG comments editor widget --- src/calibre/gui2/dialogs/metadata_single.py | 23 +- src/calibre/gui2/dialogs/metadata_single.ui | 278 ++++++++++---------- 2 files changed, 155 insertions(+), 146 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 7c5a9f95d4..9cb9f7bbbc 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -34,6 +34,7 @@ from calibre.customize.ui import run_plugins_on_import, get_isbndb_key from calibre.gui2.preferences.social import SocialMetadata from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre import strftime +from calibre.library.comments import comments_to_html class CoverFetcher(Thread): # {{{ @@ -195,7 +196,6 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): _file + _(" is not a valid picture")) d.exec_() else: - self.cover_path.setText(_file) self.cover.setPixmap(pix) self.update_cover_tooltip() self.cover_changed = True @@ -409,7 +409,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): if mi.series_index is not None: self.series_index.setValue(float(mi.series_index)) if mi.comments and mi.comments.strip(): - self.comments.setPlainText(mi.comments) + comments = comments_to_html(mi.comments) + self.comments.html = comments def sync_formats(self): @@ -556,7 +557,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): if rating > 0: self.rating.setValue(int(rating/2.)) comments = self.db.comments(row) - self.comments.setPlainText(comments if comments else '') + if comments and comments.strip(): + comments = comments_to_html(comments) + self.comments.html = comments cover = self.db.cover(row) pubdate = db.pubdate(self.id, index_is_id=True) self.pubdate.setDate(QDate(pubdate.year, pubdate.month, @@ -806,10 +809,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.pubdate.setDate(QDate(dt.year, dt.month, dt.day)) summ = book.comments if summ: - prefix = unicode(self.comments.toPlainText()) + prefix = self.comment.html if prefix: prefix += '\n' - self.comments.setPlainText(prefix + summ) + self.comments.html = prefix + comments_to_html(summ) if book.rating is not None: self.rating.setValue(int(book.rating)) if book.tags: @@ -899,7 +902,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.db.set_series_index(self.id, self.series_index.value(), notify=False, commit=False) self.db.set_comment(self.id, - unicode(self.comments.toPlainText()).strip(), + self.comments.html, notify=False, commit=False) d = self.pubdate.date() d = qt_to_dt(d) @@ -936,16 +939,16 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): QDialog.reject(self, *args) def read_state(self): - wg = dynamic.get('metasingle_window_geometry', None) - ss = dynamic.get('metasingle_splitter_state', None) + wg = dynamic.get('metasingle_window_geometry2', None) + ss = dynamic.get('metasingle_splitter_state2', None) if wg is not None: self.restoreGeometry(wg) if ss is not None: self.splitter.restoreState(ss) def save_state(self): - dynamic.set('metasingle_window_geometry', bytes(self.saveGeometry())) - dynamic.set('metasingle_splitter_state', + dynamic.set('metasingle_window_geometry2', bytes(self.saveGeometry())) + dynamic.set('metasingle_splitter_state2', bytes(self.splitter.saveState())) def break_cycles(self): diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui index 0355dc0fe6..dfa8c45797 100644 --- a/src/calibre/gui2/dialogs/metadata_single.ui +++ b/src/calibre/gui2/dialogs/metadata_single.ui @@ -6,8 +6,8 @@ 0 0 - 887 - 750 + 994 + 726 @@ -43,8 +43,8 @@ 0 0 - 879 - 711 + 986 + 687 @@ -66,8 +66,8 @@ &Basic metadata - - + + Qt::Horizontal @@ -495,29 +495,132 @@ Using this button to create author sort will change author sort from red to gree + + + + - - - &Comments + + + + 0 + 10 + - - - - - true - - - false + + Book Cover + + + + + + + 0 + 100 + + + + + 6 + + + QLayout::SetMaximumSize + + + 0 + + + + + Change &cover image: + + + cover_button + + + + + + + 6 + + + 0 + + + + + &Browse + + + + :/images/document_open.png:/images/document_open.png + + + + + + + Remove border (if any) from cover + + + T&rim + + + + :/images/trim.png:/images/trim.png + + + + + + + Reset cover to default + + + &Remove + + + + :/images/trash.png:/images/trash.png + + + + + + + + + + + + + Download co&ver + + + + + + + Generate a default cover based on the title and author + + + &Generate cover + + + + + - - + + @@ -546,6 +649,12 @@ Using this button to create author sort will change author sort from red to gree 140 + + + 100 + 0 + + QAbstractItemView::DropOnly @@ -644,129 +753,22 @@ Using this button to create author sort will change author sort from red to gree - + - + 0 10 - Book Cover + &Comments - + + + 0 + - - - - 0 - 100 - - - - - - - - 6 - - - QLayout::SetMaximumSize - - - 0 - - - - - Change &cover image: - - - cover_path - - - - - - - 6 - - - 0 - - - - - true - - - - - - - &Browse - - - - :/images/document_open.png:/images/document_open.png - - - - - - - Remove border (if any) from cover - - - T&rim - - - - :/images/trim.png:/images/trim.png - - - Qt::ToolButtonTextBesideIcon - - - - - - - Reset cover to default - - - ... - - - - :/images/trash.png:/images/trash.png - - - - - - - - - - - - - Download co&ver - - - - - - - Generate a default cover based on the title and author - - - &Generate cover - - - - + @@ -828,6 +830,12 @@ Using this button to create author sort will change author sort from red to gree

calibre/gui2/widgets.h
1 + + Editor + QWidget +
calibre/gui2/comments_editor.h
+ 1 +
title @@ -848,13 +856,11 @@ Using this button to create author sort will change author sort from red to gree date pubdate fetch_metadata_button - comments button_set_cover button_set_metadata formats add_format_button remove_format_button - cover_path cover_button trim_cover_button reset_cover From f5eaa0034d384325b2ee39978ff78e33f2b5d41d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 21:24:34 -0700 Subject: [PATCH 08/43] ... --- src/calibre/gui2/comments_editor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index e7647a8da3..d19c97e87b 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -127,6 +127,7 @@ class EditorWidget(QWebView): # {{{ (_('Heading') +' 5', 'h5'), (_('Heading') +' 6', 'h6'), (_('Pre-formatted'), 'pre'), + (_('Blockquote'), 'blockquote'), (_('Address'), 'address'), ]: ac = BlockStyleAction(text, name, self) From 4489730bd16942baa7fe59c5ccc35f68b5a9dd08 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 21:54:37 -0700 Subject: [PATCH 09/43] CND and wenxuecity - znjy by Derek Liang --- resources/recipes/cnd.recipe | 67 ++++++++++++++++++++++++ resources/recipes/wenxuecity-znjy.recipe | 62 ++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 resources/recipes/cnd.recipe create mode 100644 resources/recipes/wenxuecity-znjy.recipe diff --git a/resources/recipes/cnd.recipe b/resources/recipes/cnd.recipe new file mode 100644 index 0000000000..0e8206d07a --- /dev/null +++ b/resources/recipes/cnd.recipe @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2010, Derek Liang ' +''' +cnd.org +''' +import re + +from calibre.web.feeds.news import BasicNewsRecipe + +class TheCND(BasicNewsRecipe): + + title = 'CND' + __author__ = 'Derek Liang' + description = '' + INDEX = 'http://cnd.org' + language = 'zh' + conversion_options = {'linearize_tables':True} + + remove_tags_before = dict(name='div', id='articleHead') + remove_tags_after = dict(id='copyright') + remove_tags = [dict(name='table', attrs={'align':'right'}), dict(name='img', attrs={'src':'http://my.cnd.org/images/logo.gif'}), dict(name='hr', attrs={}), dict(name='small', attrs={})] + no_stylesheets = True + + preprocess_regexps = [(re.compile(r'', re.DOTALL), lambda m: '')] + + def print_version(self, url): + if url.find('news/article.php') >= 0: + return re.sub("^[^=]*", "http://my.cnd.org/modules/news/print.php?storyid", url) + else: + return re.sub("^[^=]*", "http://my.cnd.org/modules/wfsection/print.php?articleid", url) + + def parse_index(self): + soup = self.index_to_soup(self.INDEX) + + feeds = [] + articles = {} + + for a in soup.findAll('a', attrs={'target':'_cnd'}): + url = a['href'] + if url.find('article.php') < 0 : + continue + if url.startswith('/'): + url = 'http://cnd.org'+url + title = self.tag_to_string(a) + self.log('\tFound article: ', title, 'at', url) + date = a.nextSibling + if (date is not None) and len(date)>2: + if not articles.has_key(date): + articles[date] = [] + articles[date].append({'title':title, 'url':url, 'description': '', 'date':''}) + self.log('\t\tAppend to : ', date) + + self.log('log articles', articles) + mostCurrent = sorted(articles).pop() + self.title = 'CND ' + mostCurrent + + feeds.append((self.title, articles[mostCurrent])) + + return feeds + + def populate_article_metadata(self, article, soup, first): + header = soup.find('h3') + self.log('header: ' + self.tag_to_string(header)) + pass + diff --git a/resources/recipes/wenxuecity-znjy.recipe b/resources/recipes/wenxuecity-znjy.recipe new file mode 100644 index 0000000000..ecce80222e --- /dev/null +++ b/resources/recipes/wenxuecity-znjy.recipe @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2010, Derek Liang ' +''' +wenxuecity.com +''' +import re + +from calibre.web.feeds.news import BasicNewsRecipe + +class TheCND(BasicNewsRecipe): + + title = 'wenxuecity - znjy' + __author__ = 'Derek Liang' + description = '' + INDEX = 'http://bbs.wenxuecity.com/znjy/?elite=1' + language = 'zh' + conversion_options = {'linearize_tables':True} + + remove_tags_before = dict(name='div', id='message') + remove_tags_after = dict(name='div', id='message') + remove_tags = [dict(name='div', id='postmeta'), dict(name='div', id='footer')] + no_stylesheets = True + + preprocess_regexps = [(re.compile(r'', re.DOTALL), lambda m: '')] + + def print_version(self, url): + return url + '?print' + + def parse_index(self): + soup = self.index_to_soup(self.INDEX) + + feeds = [] + articles = {} + + for a in soup.findAll('a', attrs={'class':'post'}): + url = a['href'] + if url.startswith('/'): + url = 'http://bbs.wenxuecity.com'+url + title = self.tag_to_string(a) + self.log('\tFound article: ', title, ' at:', url) + dateReg = re.search( '(\d\d?)/(\d\d?)/(\d\d)', self.tag_to_string(a.parent) ) + date = '%(y)s/%(m)02d/%(d)02d' % {'y' : dateReg.group(3), 'm' : int(dateReg.group(1)), 'd' : int(dateReg.group(2)) } + if not articles.has_key(date): + articles[date] = [] + articles[date].append({'title':title, 'url':url, 'description': '', 'date':''}) + self.log('\t\tAppend to : ', date) + + self.log('log articles', articles) + mostCurrent = sorted(articles).pop() + self.title = '文学城 - 子女教育 - ' + mostCurrent + + feeds.append((self.title, articles[mostCurrent])) + + return feeds + + def populate_article_metadata(self, article, soup, first): + header = soup.find('h3') + self.log('header: ' + self.tag_to_string(header)) + pass + From fd300956f47313a260bf7d828a060b00b4d3b262 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 21 Dec 2010 07:18:33 +0000 Subject: [PATCH 10/43] Fix title sort problem correctly this time. --- src/calibre/ebooks/metadata/__init__.py | 4 ++-- src/calibre/library/save_to_disk.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index 25127ee591..e5aa1471cf 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -55,9 +55,9 @@ except: _ignore_starts = u'\'"'+u''.join(unichr(x) for x in range(0x2018, 0x201e)+[0x2032, 0x2033]) -def title_sort(title): +def title_sort(title, order='library_order'): title = title.strip() - if tweaks['title_series_sorting'] == 'strictly_alphabetic': + if order == 'strictly_alphabetic': return title if title and title[0] in _ignore_starts: title = title[1:] diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py index 7090a2afa8..3179551b45 100644 --- a/src/calibre/library/save_to_disk.py +++ b/src/calibre/library/save_to_disk.py @@ -7,6 +7,7 @@ __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' import os, traceback, cStringIO, re, shutil +from functools import partial from calibre.constants import DEBUG from calibre.utils.config import Config, StringConfig, tweaks @@ -139,8 +140,7 @@ class SafeFormat(TemplateFormatter): def get_components(template, mi, id, timefmt='%b %Y', length=250, sanitize_func=ascii_filename, replace_whitespace=False, to_lowercase=False): - library_order = tweaks['save_template_title_series_sorting'] == 'library_order' - tsfmt = title_sort if library_order else lambda x: x + tsfmt = partial(title_sort, order=tweaks['save_template_title_series_sorting']) format_args = FORMAT_ARGS.copy() format_args.update(mi.all_non_none_fields()) if mi.title: From 32b9b3f9a20088fae34f3a7fb30fb0dee4c59833 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 21 Dec 2010 07:29:53 +0000 Subject: [PATCH 11/43] Ensure that S/R doesn't create blank list items --- src/calibre/gui2/dialogs/metadata_bulk.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 976a753254..2b70321539 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -283,8 +283,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): self.all_fields.append(f) self.all_fields.sort() self.writable_fields.sort() - self.search_field.setMaxVisibleItems(20) - self.destination_field.setMaxVisibleItems(20) + self.search_field.setMaxVisibleItems(25) + self.destination_field.setMaxVisibleItems(25) offset = 10 self.s_r_number_of_books = min(10, len(self.ids)) for i in range(1,self.s_r_number_of_books+1): @@ -375,7 +375,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): else: val = mi.get(field, None) if val is None: - val = [''] + val = [] if fm['is_multiple'] else [''] elif not fm['is_multiple']: val = [val] elif field == 'authors': From fa8aae5fc775f1ff2b594c2d26870d24d379fde7 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 21 Dec 2010 08:41:39 +0000 Subject: [PATCH 12/43] Add the 'template' function to the formatter. --- src/calibre/manual/template_lang.rst | 9 +++++---- src/calibre/utils/formatter.py | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index ed665eee5a..0f3e543bee 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -203,16 +203,17 @@ All the functions listed under single-function mode can be used in program mode, The following functions are available in addition to those described in single-function mode. With the exception of the ``id`` parameter of assign, all parameters can be statements (sequences of expressions): - * ``add(x, y)`` -- returns x + y. Throws an exception if either x or y are not numbers. + * ``add(x, y)`` -- returns x + y. Throws an exception if either x or y are not numbers. * ``assign(id, val)`` -- assigns val to id, then returns val. id must be an identifier, not an expression * ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``. - * ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers. + * ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers. * ``field(name)`` -- returns the metadata field named by ``name``. - * ``multiply`` -- returns x * y. Throws an exception if either x or y are not numbers. + * ``multiply(x, y)`` -- returns x * y. Throws an exception if either x or y are not numbers. * ``strcat(a, b, ...)`` -- can take any number of arguments. Returns a string formed by concatenating all the arguments. * ``strcmp(x, y, lt, eq, gt)`` -- does a case-insensitive comparison x and y as strings. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``. * ``substr(str, start, end)`` -- returns the ``start``'th through the ``end``'th characters of ``str``. The first character in ``str`` is the zero'th character. If end is negative, then it indicates that many characters counting from the right. If end is zero, then it indicates the last character. For example, ``substr('12345', 1, 0)`` returns ``'2345'``, and ``substr('12345', 1, -1)`` returns ``'234'``. - * ``subtract`` -- returns x - y. Throws an exception if either x or y are not numbers. + * ``subtract(x, y)`` -- returns x - y. Throws an exception if either x or y are not numbers. + * ``template(x)`` -- evaluates x as a template. The evaluation is done in its own context, meaning that variables are not shared between the caller and the template evaluation. Because the `{` and `}` characters are special, you must use `[[` for the `{` character and `]]` for the '}' character; they are converted automatically. For example, ``template('[[title_sort]]') will evaluate the template ``{title_sort}`` and return its value. Special notes for save/send templates ------------------------------------- diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index 9e095af7b9..182aff5a7a 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -57,6 +57,11 @@ class _Parser(object): y = float(y if y else 0) return ops[op](x, y) + def _template(self, template): + template = template.replace('[[', '{').replace(']]', '}') + return self.parent.safe_format(template, self.parent.kwargs, 'TEMPLATE', + self.parent.book) + local_functions = { 'add' : (2, partial(_math, op='+')), 'assign' : (2, _assign), @@ -68,6 +73,7 @@ class _Parser(object): 'strcmp' : (5, _strcmp), 'substr' : (3, lambda s, x, y, z: x[int(y): len(x) if int(z) == 0 else int(z)]), 'subtract' : (2, partial(_math, op='-')), + 'template' : (1, _template) } def __init__(self, val, prog, parent): From 4d2fe7db7d4dbf89e0a3758f647c345e867978fb Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 21 Dec 2010 10:31:53 +0000 Subject: [PATCH 13/43] Add 'template' as a search/replace input field in regexp mode --- src/calibre/gui2/dialogs/metadata_bulk.py | 18 +++++++++ src/calibre/gui2/dialogs/metadata_bulk.ui | 47 +++++++++++++++++------ src/calibre/gui2/widgets.py | 6 +++ 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 2b70321539..4a6acf0a5e 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -12,6 +12,7 @@ from PyQt4 import QtGui from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.ebooks.metadata import string_to_authors, authors_to_string +from calibre.ebooks.metadata.book.base import composite_formatter from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.gui2 import error_dialog from calibre.gui2.progress_indicator import ProgressIndicator @@ -268,6 +269,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): def prepare_search_and_replace(self): self.search_for.initialize('bulk_edit_search_for') self.replace_with.initialize('bulk_edit_replace_with') + self.s_r_template.initialize('bulk_edit_template') self.test_text.initialize('bulk_edit_test_test') self.all_fields = [''] self.writable_fields = [''] @@ -282,6 +284,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): if f in ['sort'] or fm[f]['datatype'] == 'composite': self.all_fields.append(f) self.all_fields.sort() + self.all_fields.insert(1, '{template}') self.writable_fields.sort() self.search_field.setMaxVisibleItems(25) self.destination_field.setMaxVisibleItems(25) @@ -360,15 +363,21 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): self.test_text.editTextChanged[str].connect(self.s_r_paint_results) self.comma_separated.stateChanged.connect(self.s_r_paint_results) self.case_sensitive.stateChanged.connect(self.s_r_paint_results) + self.s_r_template.lost_focus.connect(self.s_r_template_changed) self.central_widget.setCurrentIndex(0) self.search_for.completer().setCaseSensitivity(Qt.CaseSensitive) self.replace_with.completer().setCaseSensitivity(Qt.CaseSensitive) + self.s_r_template.completer().setCaseSensitivity(Qt.CaseSensitive) self.s_r_search_mode_changed(self.search_mode.currentIndex()) def s_r_get_field(self, mi, field): if field: + if field == '{template}': + v = composite_formatter.safe_format\ + (unicode(self.s_r_template.text()), mi, _('S/R TEMPLATE ERROR'), mi) + return [v] fm = self.db.metadata_for_field(field) if field == 'sort': val = mi.get('title_sort', None) @@ -384,7 +393,16 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): val = [] return val + def s_r_template_changed(self): + self.s_r_search_field_changed(self.search_field.currentIndex()) + def s_r_search_field_changed(self, idx): + if self.search_mode.currentIndex() != 0 and idx == 1: # Template + self.s_r_template.setVisible(True) + self.template_label.setVisible(True) + else: + self.s_r_template.setVisible(False) + self.template_label.setVisible(False) for i in range(0, self.s_r_number_of_books): w = getattr(self, 'book_%d_text'%(i+1)) mi = self.db.get_metadata(self.ids[i], index_is_id=True) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index ecb34d8e5b..8422c84ccb 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -501,6 +501,29 @@ Future conversion of these books will use the default settings. + + + Te&mplate: + + + s_r_template + + + + + + + + 100 + 0 + + + + Enter a template to be used as the source for the search/replace + + + + &Search for: @@ -510,7 +533,7 @@ Future conversion of these books will use the default settings. - + @@ -523,7 +546,7 @@ Future conversion of these books will use the default settings. - + Check this box if the search string must match exactly upper and lower case. Uncheck it if case is to be ignored @@ -536,7 +559,7 @@ Future conversion of these books will use the default settings. - + &Replace with: @@ -546,14 +569,14 @@ Future conversion of these books will use the default settings. - + The replacement text. The matched search text will be replaced with this string - + @@ -588,7 +611,7 @@ field is processed. In regular expression mode, only the matched text is process - + &Destination field: @@ -598,14 +621,15 @@ field is processed. In regular expression mode, only the matched text is process - + - The field that the text will be put into after all replacements. If blank, the source field is used. + The field that the text will be put into after all replacements. +If blank, the source field is used if the field is modifiable - + @@ -653,7 +677,7 @@ nothing should be put between the original text and the inserted text - + Test &text @@ -663,7 +687,7 @@ nothing should be put between the original text and the inserted text - + Test re&sult @@ -784,6 +808,7 @@ nothing should be put between the original text and the inserted text central_widget search_field search_mode + s_r_template search_for case_sensitive replace_with diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 12d64bbbcd..8ca1df917c 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -524,6 +524,8 @@ class EnComboBox(QComboBox): class HistoryLineEdit(QComboBox): + lost_focus = pyqtSignal() + def __init__(self, *args): QComboBox.__init__(self, *args) self.setEditable(True) @@ -559,6 +561,10 @@ class HistoryLineEdit(QComboBox): def text(self): return self.currentText() + def focusOutEvent(self, e): + QComboBox.focusOutEvent(self, e) + self.lost_focus.emit() + class ComboBoxWithHelp(QComboBox): ''' A combobox where item 0 is help text. CurrentText will return '' for item 0. From 30c9bd11435618f62ffa22f8c013ca82001b7475 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 21 Dec 2010 11:03:20 +0000 Subject: [PATCH 14/43] ... --- src/calibre/ebooks/metadata/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index e5aa1471cf..f087f861ba 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -55,7 +55,7 @@ except: _ignore_starts = u'\'"'+u''.join(unichr(x) for x in range(0x2018, 0x201e)+[0x2032, 0x2033]) -def title_sort(title, order='library_order'): +def title_sort(title, order=tweaks['title_series_sorting']): title = title.strip() if order == 'strictly_alphabetic': return title From 44e340f1deea02c26ce16c74bce61e7f3578ac46 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Dec 2010 09:35:37 -0700 Subject: [PATCH 15/43] ... --- src/calibre/gui2/library/models.py | 1 - src/calibre/library/sqlite.py | 1 - 2 files changed, 2 deletions(-) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 0d70fbc610..9da9a2f538 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -23,7 +23,6 @@ from calibre.ebooks.metadata.meta import set_metadata as _set_metadata from calibre.utils.search_query_parser import SearchQueryParser from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \ REGEXP_MATCH, MetadataBackup -from calibre.library.cli import parse_series_string from calibre import strftime, isbytestring, prepare_string_for_xml from calibre.constants import filesystem_encoding, DEBUG from calibre.gui2.library import DEFAULT_SORT diff --git a/src/calibre/library/sqlite.py b/src/calibre/library/sqlite.py index 521c275efe..0458ada27b 100644 --- a/src/calibre/library/sqlite.py +++ b/src/calibre/library/sqlite.py @@ -16,7 +16,6 @@ from datetime import datetime from functools import partial from calibre.ebooks.metadata import title_sort, author_to_author_sort -from calibre.utils.config import tweaks from calibre.utils.date import parse_date, isoformat from calibre import isbytestring, force_unicode from calibre.constants import iswindows, DEBUG From 2ace9fb854d6814dfc650595c23d0b5410768649 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Dec 2010 09:45:18 -0700 Subject: [PATCH 16/43] Fix #8005 (Device Support: AluraTek Colour eReader) --- src/calibre/customize/builtins.py | 3 ++- src/calibre/devices/misc.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 793c1fa0de..40b78f3d14 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -477,7 +477,7 @@ from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS, \ SOVOS, PICO from calibre.devices.sne.driver import SNE from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN, \ - GEMEI, VELOCITYMICRO, PDNOVEL_KOBO, Q600, LUMIREAD + GEMEI, VELOCITYMICRO, PDNOVEL_KOBO, Q600, LUMIREAD, ALURATEK_COLOR from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG from calibre.devices.kobo.driver import KOBO @@ -600,6 +600,7 @@ plugins += [ VELOCITYMICRO, PDNOVEL_KOBO, LUMIREAD, + ALURATEK_COLOR, ITUNES, ] plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ diff --git a/src/calibre/devices/misc.py b/src/calibre/devices/misc.py index 52952356f8..1989fb7c61 100644 --- a/src/calibre/devices/misc.py +++ b/src/calibre/devices/misc.py @@ -204,3 +204,23 @@ class LUMIREAD(USBMS): with open(cfilepath+'.jpg', 'wb') as f: f.write(metadata.thumbnail[-1]) +class ALURATEK_COLOR(USBMS): + + name = 'Aluratek Color Device Interface' + gui_name = 'Aluratek Color' + description = _('Communicate with the Aluratek Color') + author = 'Kovid Goyal' + supported_platforms = ['windows', 'osx', 'linux'] + + # Ordered list of supported formats + FORMATS = ['epub', 'fb2', 'txt', 'pdf'] + + VENDOR_ID = [0x1f3a] + PRODUCT_ID = [0x1000] + BCD = [0x0002] + + EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'books' + + VENDOR_NAME = 'USB_2.0' + WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'USB_FLASH_DRIVER' + From eb091dcdf1498237b21fbd7a0201e9aeaa752c54 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Dec 2010 09:53:37 -0700 Subject: [PATCH 17/43] Fix #8000 (Sunstech EB700 v2) --- src/calibre/customize/builtins.py | 4 ++-- src/calibre/devices/teclast/driver.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 40b78f3d14..09ea30f993 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -474,7 +474,7 @@ from calibre.devices.binatone.driver import README from calibre.devices.hanvon.driver import N516, EB511, ALEX, AZBOOKA, THEBOOK from calibre.devices.edge.driver import EDGE from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS, \ - SOVOS, PICO + SOVOS, PICO, SUNTECH_EB700 from calibre.devices.sne.driver import SNE from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN, \ GEMEI, VELOCITYMICRO, PDNOVEL_KOBO, Q600, LUMIREAD, ALURATEK_COLOR @@ -579,7 +579,7 @@ plugins += [ ELONEX, TECLAST_K3, NEWSMY, - PICO, + PICO, SUNTECH_EB700, IPAPYRUS, SOVOS, EDGE, diff --git a/src/calibre/devices/teclast/driver.py b/src/calibre/devices/teclast/driver.py index b9ec554cee..450e068bad 100644 --- a/src/calibre/devices/teclast/driver.py +++ b/src/calibre/devices/teclast/driver.py @@ -72,3 +72,13 @@ class SOVOS(TECLAST_K3): VENDOR_NAME = 'RK28XX' WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'USB-MSC' +class SUNTECH_EB700(TECLAST_K3): + name = 'Suntech EB700 device interface' + gui_name = 'EB700' + description = _('Communicate with the Suntech EB700 reader.') + + FORMATS = ['epub', 'fb2', 'pdf', 'pdb', 'txt'] + + VENDOR_NAME = 'SUNEB700' + WINDOWS_MAIN_MEM = 'USB-MSC' + From a6070c99c3b6a5c1b743008c095d20011fe51996 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Dec 2010 09:54:35 -0700 Subject: [PATCH 18/43] ... --- src/calibre/customize/builtins.py | 4 ++-- src/calibre/devices/teclast/driver.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 09ea30f993..17e22a0c0f 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -474,7 +474,7 @@ from calibre.devices.binatone.driver import README from calibre.devices.hanvon.driver import N516, EB511, ALEX, AZBOOKA, THEBOOK from calibre.devices.edge.driver import EDGE from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS, \ - SOVOS, PICO, SUNTECH_EB700 + SOVOS, PICO, SUNSTECH_EB700 from calibre.devices.sne.driver import SNE from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN, \ GEMEI, VELOCITYMICRO, PDNOVEL_KOBO, Q600, LUMIREAD, ALURATEK_COLOR @@ -579,7 +579,7 @@ plugins += [ ELONEX, TECLAST_K3, NEWSMY, - PICO, SUNTECH_EB700, + PICO, SUNSTECH_EB700, IPAPYRUS, SOVOS, EDGE, diff --git a/src/calibre/devices/teclast/driver.py b/src/calibre/devices/teclast/driver.py index 450e068bad..f406448ad2 100644 --- a/src/calibre/devices/teclast/driver.py +++ b/src/calibre/devices/teclast/driver.py @@ -72,10 +72,10 @@ class SOVOS(TECLAST_K3): VENDOR_NAME = 'RK28XX' WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'USB-MSC' -class SUNTECH_EB700(TECLAST_K3): - name = 'Suntech EB700 device interface' +class SUNSTECH_EB700(TECLAST_K3): + name = 'Sunstech EB700 device interface' gui_name = 'EB700' - description = _('Communicate with the Suntech EB700 reader.') + description = _('Communicate with the Sunstech EB700 reader.') FORMATS = ['epub', 'fb2', 'pdf', 'pdb', 'txt'] From d4b2938c0726891ec1f9bc1b5eb6bcbe650e105e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Dec 2010 10:43:37 -0700 Subject: [PATCH 19/43] Add a success message after the integrity check --- src/calibre/gui2/actions/choose_library.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index e789ae62e6..b1f0bd6b0e 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -138,6 +138,10 @@ class CheckIntegrity(QProgressDialog): 'You should check them manually. This can ' 'happen if you manipulate the files in the ' 'library folder directly.'), det_msg=det_msg, show=True) + else: + info_dialog(self, _('No errors found'), + _('The integrity check completed with no uncorrectable errors found.'), + show=True) self.reset() # }}} From 041f4645fae00cce5435ef82ac9db5169c510819 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 21 Dec 2010 19:54:00 +0000 Subject: [PATCH 20/43] Tweak to build collections of all books by title and by author --- resources/default_tweaks.py | 17 ++++++++++++ src/calibre/devices/prs505/sony_cache.py | 3 +++ src/calibre/devices/usbms/books.py | 34 ++++++++++++++++-------- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index a420cd7d44..d15ffd4339 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -180,6 +180,23 @@ sony_collection_renaming_rules={} # Default: empty (no rules), so no collection attributes are named. sony_collection_sorting_rules = [] +# Specify whether special collections are to be made. The two available are +# all_by_author and all_by_title. These collections work around various device +# idiosyncrasies regarding sorting of lists. The all by author collection is +# sorted by author(s) then title. The by title collection is sorted by title +# then authors(s) +# Enable a collection by entering a collection name in the variable. That +# collection name must be unique. +# Examples: +# sony_all_books_by_author_collection = '%All by author' +# create a collection of all books sorted by author +# sony_all_books_by_title_collection = '%All by title' +# create a collection of books sorted by title, respecting the order tweaks +# sony_all_books_by_author_collection = '' +# disable the collection +sony_all_books_by_author_collection = '' +sony_all_books_by_title_collection = '' + # Create search terms to apply a query across several built-in search terms. # Syntax: {'new term':['existing term 1', 'term 2', ...], 'new':['old'...] ...} diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py index f271329fc8..841f6bc346 100644 --- a/src/calibre/devices/prs505/sony_cache.py +++ b/src/calibre/devices/prs505/sony_cache.py @@ -410,6 +410,9 @@ class XMLCache(object): newmi = book.deepcopy_metadata() newmi.template_to_attribute(book, plugboard) newmi.set('_new_book', getattr(book, '_new_book', False)) + book.set('_pb_title_sort', + newmi.get('title_sort', newmi.get('title', None))) + book.set('_pb_author_sort', newmi.get('author_sort', '')) else: newmi = book (gtz_count, ltz_count, use_tz_var) = \ diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index 3372f5c8a5..0f78b85a57 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -132,9 +132,14 @@ class CollectionsBookList(BookList): use_renaming_rules = prefs['manage_device_metadata'] == 'on_connect' collections = {} - # This map of sets is used to avoid linear searches when testing for - # book equality + + all_by_author = tweaks['sony_all_books_by_author_collection'] + all_by_title = tweaks['sony_all_books_by_title_collection'] + for book in self: + tsval = book.get('_pb_title_sort', + book.get('title_sort', book.get('title', 'zzzz'))) + asval = book.get('_pb_author_sort', book.get('author_sort', '')) # Make sure we can identify this book via the lpath lpath = getattr(book, 'lpath', None) if lpath is None: @@ -211,22 +216,29 @@ class CollectionsBookList(BookList): collections[cat_name] = {} if use_renaming_rules and sort_attr: sort_val = book.get(sort_attr, None) - collections[cat_name][lpath] = \ - (book, sort_val, book.get('title_sort', 'zzzz')) + collections[cat_name][lpath] = (book, sort_val, tsval) elif is_series: if doing_dc: collections[cat_name][lpath] = \ - (book, book.get('series_index', sys.maxint), - book.get('title_sort', 'zzzz')) + (book, book.get('series_index', sys.maxint), tsval) else: collections[cat_name][lpath] = \ - (book, book.get(attr+'_index', sys.maxint), - book.get('title_sort', 'zzzz')) + (book, book.get(attr+'_index', sys.maxint), tsval) else: if lpath not in collections[cat_name]: - collections[cat_name][lpath] = \ - (book, book.get('title_sort', 'zzzz'), - book.get('title_sort', 'zzzz')) + collections[cat_name][lpath] = (book, tsval, tsval) + + # All books by author + if all_by_author: + if all_by_author not in collections: + collections[all_by_author] = {} + collections[all_by_author][lpath] = (book, asval, tsval) + # All books by title + if all_by_title: + if all_by_title not in collections: + collections[all_by_title] = {} + collections[all_by_title][lpath] = (book, tsval, asval) + # Sort collections result = {} From 47cc6f8a259797d6990519edee440056d25b92ad Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Dec 2010 13:12:58 -0700 Subject: [PATCH 21/43] SONY driver: Don't upload thumbnails as the slow down post disconnect processing on older models --- src/calibre/devices/prs505/driver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 44ecd5cfd0..23f59e4737 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -179,6 +179,8 @@ class PRS505(USBMS): self.plugboard_func = pb_func def upload_cover(self, path, filename, metadata, filepath): + return # Disabled as the SONY's don't need this thumbnail anyway and + # older models don't auto delete it if metadata.thumbnail and metadata.thumbnail[-1]: path = path.replace('/', os.sep) is_main = path.startswith(self._main_prefix) From d7986e12baff8b98ec6fd8c280ced9385c6bd626 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 21 Dec 2010 20:37:19 +0000 Subject: [PATCH 22/43] Changes to the sony auto-collections tweak requested by Kovid. Also take the author_sort from the device_db plugboard, if it is available. --- resources/default_tweaks.py | 24 ++++++++++++------------ src/calibre/devices/usbms/books.py | 11 +++++++++-- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index d15ffd4339..3835bcd656 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -180,22 +180,22 @@ sony_collection_renaming_rules={} # Default: empty (no rules), so no collection attributes are named. sony_collection_sorting_rules = [] -# Specify whether special collections are to be made. The two available are -# all_by_author and all_by_title. These collections work around various device -# idiosyncrasies regarding sorting of lists. The all by author collection is -# sorted by author(s) then title. The by title collection is sorted by title -# then authors(s) +# Specify whether special collections are to be made. This option is primarily +# of use on a Sony. The two available are all_by_author and all_by_title. These +# collections work around various device idiosyncrasies regarding sorting of +# lists, especially the sony *50 models. The author collection is sorted by +# author(s) then title. The title collection is sorted by title then authors(s) # Enable a collection by entering a collection name in the variable. That # collection name must be unique. # Examples: -# sony_all_books_by_author_collection = '%All by author' -# create a collection of all books sorted by author -# sony_all_books_by_title_collection = '%All by title' -# create a collection of books sorted by title, respecting the order tweaks -# sony_all_books_by_author_collection = '' +# device_special_collections = {'title':'', 'author':'%All by author'} +# create a collection named '%All by author' of all books sorted by author +# device_special_collections = {'title':'%All by title', 'author':''} +# create a collection named '%All by title' of books sorted by title, +# respecting the order tweaks +# sony_all_books_by_author_collection = {'title':'', 'author':''} # disable the collection -sony_all_books_by_author_collection = '' -sony_all_books_by_title_collection = '' +device_special_collections = {'title':'', 'author':''} # Create search terms to apply a query across several built-in search terms. diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index 0f78b85a57..84b8585d5c 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -133,8 +133,15 @@ class CollectionsBookList(BookList): collections = {} - all_by_author = tweaks['sony_all_books_by_author_collection'] - all_by_title = tweaks['sony_all_books_by_title_collection'] + # get the special collection names + try: + all_by_author = tweaks['device_special_collections']['author'] + except: + all_by_author = '' + try: + all_by_title = tweaks['device_special_collections']['title'] + except: + all_by_title = '' for book in self: tsval = book.get('_pb_title_sort', From 5fe71e5c3f5aff91e148e6287ef652e341bcc0e4 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 21 Dec 2010 20:38:41 +0000 Subject: [PATCH 23/43] Clean up tweaks comments --- resources/default_tweaks.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 3835bcd656..d2e4758ba6 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -192,9 +192,11 @@ sony_collection_sorting_rules = [] # create a collection named '%All by author' of all books sorted by author # device_special_collections = {'title':'%All by title', 'author':''} # create a collection named '%All by title' of books sorted by title, -# respecting the order tweaks +# respecting the order tweaks +# device_special_collections = {'title':'%All by title', 'author':'%All by author'} +# make both collections # sony_all_books_by_author_collection = {'title':'', 'author':''} -# disable the collection +# disable both collections device_special_collections = {'title':'', 'author':''} From 3d30edcc83a335f79910a6284b0a45416dd9df30 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Dec 2010 13:48:32 -0700 Subject: [PATCH 24/43] Fix #8008 (QWebPage import error) --- src/calibre/gui2/comments_editor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index d19c97e87b..ca93145915 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -11,9 +11,9 @@ from lxml import html from lxml.html import soupparser from PyQt4.Qt import QApplication, QFontInfo, QSize, QWidget, QPlainTextEdit, \ - QToolBar, QVBoxLayout, QAction, QIcon, QWebPage, Qt, QTabWidget, QUrl, \ + QToolBar, QVBoxLayout, QAction, QIcon, Qt, QTabWidget, QUrl, \ QSyntaxHighlighter, QColor, QChar, QColorDialog, QMenu, QInputDialog -from PyQt4.QtWebKit import QWebView +from PyQt4.QtWebKit import QWebView, QWebPage from calibre.ebooks.chardet import xml_to_unicode from calibre import xml_replace_entities From fc5934436c7335f64ebe69b412081a2f764f5d5f Mon Sep 17 00:00:00 2001 From: Marcell van Geest Date: Tue, 21 Dec 2010 23:39:25 +0100 Subject: [PATCH 25/43] Added possibility to sort fetched metadata using the column headers. --- src/calibre/gui2/dialogs/fetch_metadata.py | 57 ++++++++++++++++------ 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/src/calibre/gui2/dialogs/fetch_metadata.py b/src/calibre/gui2/dialogs/fetch_metadata.py index 3da0e67e3d..e6f5e6ae9a 100644 --- a/src/calibre/gui2/dialogs/fetch_metadata.py +++ b/src/calibre/gui2/dialogs/fetch_metadata.py @@ -16,6 +16,8 @@ from calibre.gui2 import error_dialog, NONE, info_dialog, config from calibre.gui2.widgets import ProgressIndicator from calibre import strftime, force_unicode from calibre.customize.ui import get_isbndb_key, set_isbndb_key +from calibre.utils.icu import sort_key +from calibre import force_unicode _hung_fetchers = set([]) @@ -72,25 +74,32 @@ class Matches(QAbstractTableModel): def summary(self, row): return self.matches[row].comments + def data_as_text(self, book, col): + if col == 0 and book.title is not None: + return book.title + elif col == 1: + return ', '.join(book.authors) + elif col == 2 and book.author_sort is not None: + return book.author_sort + elif col == 3 and book.publisher is not None: + return book.publisher + elif col == 4 and book.isbn is not None: + return book.isbn + elif col == 5 and hasattr(book.pubdate, 'timetuple'): + return strftime('%b %Y', book.pubdate.timetuple()) + elif col == 6 and book.has_cover: + return 'has_cover' + elif col == 7 and book.comments is not None: + return book.comments + else: + return '' + def data(self, index, role): row, col = index.row(), index.column() book = self.matches[row] if role == Qt.DisplayRole: - res = None - if col == 0: - res = book.title - elif col == 1: - res = ', '.join(book.authors) - elif col == 2: - res = book.author_sort - elif col == 3: - res = book.publisher - elif col == 4: - res = book.isbn - elif col == 5: - if hasattr(book.pubdate, 'timetuple'): - res = strftime('%b %Y', book.pubdate.timetuple()) - if not res: + res = self.data_as_text(book, col) + if not (col <= 5 and res): return NONE return QVariant(res) elif role == Qt.DecorationRole: @@ -100,6 +109,16 @@ class Matches(QAbstractTableModel): return self.yes_icon return NONE + def sort(self, col, order, reset=True): + if not self.matches: + return + descending = order == Qt.DescendingOrder + self.matches.sort(None, + lambda x: sort_key(unicode(force_unicode(self.data_as_text(x, col)))), + descending) + if reset: + self.reset() + class FetchMetadata(QDialog, Ui_FetchMetadata): HANG_TIME = 75 #seconds @@ -136,6 +155,11 @@ class FetchMetadata(QDialog, Ui_FetchMetadata): self.connect(self.matches, SIGNAL('entered(QModelIndex)'), self.show_summary) self.matches.setMouseTracking(True) + # Enabling sorting and setting a sort column will not change the initial + # order of the results, as they are filled in later + self.matches.setSortingEnabled(True) + QObject.connect(self.matches.horizontalHeader(), SIGNAL('sectionClicked(int)'), self.show_sort_indicator) + self.matches.horizontalHeader().setSortIndicatorShown(False) self.fetch_metadata() self.opt_get_social_metadata.setChecked(config['get_social_metadata']) self.opt_overwrite_author_title_metadata.setChecked(config['overwrite_author_title_metadata']) @@ -243,3 +267,6 @@ class FetchMetadata(QDialog, Ui_FetchMetadata): def chosen(self, index): self.matches.setCurrentIndex(index) self.accept() + + def show_sort_indicator(self): + self.matches.horizontalHeader().setSortIndicatorShown(True) From ef1fc13d570d3b9f7fe0767ced659403807c3f5e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Dec 2010 15:56:19 -0700 Subject: [PATCH 26/43] ... --- src/calibre/gui2/viewer/documentview.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index d5f881d4b4..683951400c 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -3,8 +3,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' -''' -''' +# Imports {{{ import os, math, re, glob, sys from base64 import b64encode from functools import partial @@ -24,6 +23,8 @@ from calibre.constants import iswindows from calibre import prints, guess_type from calibre.gui2.viewer.keys import SHORTCUTS +# }}} + bookmarks = referencing = hyphenation = jquery = jquery_scrollTo = \ hyphenator = images = hyphen_pats = None @@ -33,6 +34,7 @@ def load_builtin_fonts(): QFontDatabase.addApplicationFont(f) return 'Liberation Serif', 'Liberation Sans', 'Liberation Mono' +# Config {{{ def config(defaults=None): desc = _('Options to customize the ebook viewer') if defaults is None: @@ -137,8 +139,9 @@ class ConfigDialog(QDialog, Ui_Dialog): str(self.hyphenate_default_lang.itemData(idx).toString())) return QDialog.accept(self, *args) +# }}} -class Document(QWebPage): +class Document(QWebPage): # {{{ def set_font_settings(self): opts = config().parse() @@ -449,7 +452,9 @@ class Document(QWebPage): self.height+amount) self.setPreferredContentsSize(s) -class EntityDeclarationProcessor(object): +# }}} + +class EntityDeclarationProcessor(object): # {{{ def __init__(self, html): self.declared_entities = {} @@ -460,8 +465,9 @@ class EntityDeclarationProcessor(object): self.processed_html = html for key, val in self.declared_entities.iteritems(): self.processed_html = self.processed_html.replace('&%s;'%key, val) +# }}} -class DocumentView(QWebView): +class DocumentView(QWebView): # {{{ DISABLED_BRUSH = QBrush(Qt.lightGray, Qt.Dense5Pattern) @@ -961,4 +967,5 @@ class DocumentView(QWebView): self.manager.scrolled(self.scroll_fraction) return ret +# }}} From e414c67486b700313b0c4b02c550505c7ea49a9d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Dec 2010 17:47:04 -0700 Subject: [PATCH 27/43] ... --- src/calibre/gui2/dialogs/metadata_single.ui | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui index dfa8c45797..6d31342dcf 100644 --- a/src/calibre/gui2/dialogs/metadata_single.ui +++ b/src/calibre/gui2/dialogs/metadata_single.ui @@ -7,7 +7,7 @@ 0 0 994 - 726 + 716 @@ -44,7 +44,7 @@ 0 0 986 - 687 + 677 @@ -495,6 +495,22 @@ Using this button to create author sort will change author sort from red to gree + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 40 + + + + From 4a41f77eb32fa7f9ecd0273b763e2f4b48251d95 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Dec 2010 17:51:46 -0700 Subject: [PATCH 28/43] Preliminary code for touchscreen swipe based page flipping --- src/calibre/gui2/viewer/documentview.py | 25 ++++++++++ src/calibre/gui2/viewer/gestures.py | 61 +++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 src/calibre/gui2/viewer/gestures.py diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index 683951400c..343d85e63e 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -18,6 +18,7 @@ from calibre.utils.config import Config, StringConfig from calibre.utils.localization import get_language from calibre.gui2.viewer.config_ui import Ui_Dialog from calibre.gui2.viewer.flip import SlideFlip +from calibre.gui2.viewer.gestures import Gestures from calibre.gui2.shortcuts import Shortcuts, ShortcutConfig from calibre.constants import iswindows from calibre import prints, guess_type @@ -474,6 +475,7 @@ class DocumentView(QWebView): # {{{ def __init__(self, *args): QWebView.__init__(self, *args) self.flipper = SlideFlip(self) + self.gestures = Gestures() self.is_auto_repeat_event = False self.debug_javascript = False self.shortcuts = Shortcuts(SHORTCUTS, 'shortcuts/viewer') @@ -959,6 +961,29 @@ class DocumentView(QWebView): # {{{ self.manager.viewport_resized(self.scroll_fraction) return ret + def event(self, ev): + typ = ev.type() + if typ == ev.TouchBegin: + try: + self.gestures.start_gesture('touch', ev) + except: + import traceback + traceback.print_exc() + elif typ == ev.TouchEnd: + try: + gesture = self.gestures.end_gesture('touch', ev, self.rect()) + except: + import traceback + traceback.print_exc() + if gesture is not None: + ev.accept() + if gesture == 'lineleft': + self.next_page() + elif gesture == 'lineright': + self.previous_page() + return True + return QWebView.event(self, ev) + def mouseReleaseEvent(self, ev): opos = self.document.ypos ret = QWebView.mouseReleaseEvent(self, ev) diff --git a/src/calibre/gui2/viewer/gestures.py b/src/calibre/gui2/viewer/gestures.py new file mode 100644 index 0000000000..86d2f842b9 --- /dev/null +++ b/src/calibre/gui2/viewer/gestures.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import time + +class Gestures(object): + + def __init__(self): + self.in_progress = {} + + def get_boundary_point(self, event): + t = time.time() + id_ = None + if hasattr(event, 'touchPoints'): + tps = list(event.touchPoints()) + tp = None + for t in tps: + if t.isPrimary(): + tp = t + break + if tp is None: + tp = tps[0] + gp, p = tp.screenPos(), tp.pos() + id_ = tp.id() + else: + gp, p = event.globalPos(), event.pos() + return (t, gp, p, id_) + + def start_gesture(self, typ, event): + self.in_progress[typ] = self.get_boundary_point(event) + + def is_in_progress(self, typ): + return typ in self.in_progress + + def end_gesture(self, typ, event, widget_rect): + if not self.is_in_progress(typ): + return + start = self.in_progress[typ] + end = self.get_boundary_point(event) + if start[3] != end[3]: + return + timespan = end[0] - start[0] + start_pos, end_pos = start[1], end[1] + xspan = end_pos.x() - start_pos.x() + yspan = end_pos.y() - start_pos.y() + + width = widget_rect.width() + + if timespan < 1.1 and abs(xspan) >= width/5. and \ + abs(yspan) < abs(xspan)/5.: + # Quick horizontal gesture + return 'line'+('left' if xspan < 0 else 'right') + + return None + + + From 6abc12cf18764e6c052ace6fedf7c9591c827352 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Dec 2010 18:56:36 -0700 Subject: [PATCH 29/43] Fix #7995 (fonta replacement through extra css stopped working) --- src/calibre/ebooks/oeb/transforms/filenames.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/oeb/transforms/filenames.py b/src/calibre/ebooks/oeb/transforms/filenames.py index 46f9fc5539..bad75b9a6f 100644 --- a/src/calibre/ebooks/oeb/transforms/filenames.py +++ b/src/calibre/ebooks/oeb/transforms/filenames.py @@ -6,7 +6,7 @@ __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' import posixpath -from urlparse import urldefrag +from urlparse import urldefrag, urlparse from lxml import etree import cssutils @@ -67,6 +67,10 @@ class RenameFiles(object): # {{{ def url_replacer(self, orig_url): url = urlnormalize(orig_url) + parts = urlparse(url) + if parts.scheme: + # Only rewrite local URLs + return orig_url path, frag = urldefrag(url) if self.renamed_items_map: orig_item = self.renamed_items_map.get(self.current_item.href, self.current_item) From 523185f7a94fc5a6eef32716dc55d10c6f941de1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Dec 2010 19:16:10 -0700 Subject: [PATCH 30/43] Conversion pipeline: Fix broken link rewriting for inline CSS embedded in HTML --- src/calibre/ebooks/oeb/base.py | 51 ++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index 0f364b8030..d1bd1cab78 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -11,12 +11,11 @@ import os, re, uuid, logging from mimetypes import types_map from collections import defaultdict from itertools import count -from urlparse import urldefrag, urlparse, urlunparse +from urlparse import urldefrag, urlparse, urlunparse, urljoin from urllib import unquote as urlunquote -from urlparse import urljoin from lxml import etree, html -from cssutils import CSSParser +from cssutils import CSSParser, parseString, parseStyle, replaceUrls from cssutils.css import CSSRule import calibre @@ -88,11 +87,11 @@ def XLINK(name): def CALIBRE(name): return '{%s}%s' % (CALIBRE_NS, name) -_css_url_re = re.compile(r'url\((.*?)\)', re.I) +_css_url_re = re.compile(r'url\s*\((.*?)\)', re.I) _css_import_re = re.compile(r'@import "(.*?)"') _archive_re = re.compile(r'[^ ]+') -def iterlinks(root): +def iterlinks(root, find_links_in_css=True): ''' Iterate over all links in a OEB Document. @@ -134,6 +133,8 @@ def iterlinks(root): yield (el, attr, attribs[attr], 0) + if not find_links_in_css: + continue if tag == XHTML('style') and el.text: for match in _css_url_re.finditer(el.text): yield (el, None, match.group(1), match.start(1)) @@ -180,7 +181,7 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False): ''' if resolve_base_href: resolve_base_href(root) - for el, attrib, link, pos in iterlinks(root): + for el, attrib, link, pos in iterlinks(root, find_links_in_css=False): new_link = link_repl_func(link.strip()) if new_link == link: continue @@ -203,6 +204,44 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False): new = cur[:pos] + new_link + cur[pos+len(link):] el.attrib[attrib] = new + def set_property(v): + if v.CSS_PRIMITIVE_VALUE == v.cssValueType and \ + v.CSS_URI == v.primitiveType: + v.setStringValue(v.CSS_URI, + link_repl_func(v.getStringValue())) + + for el in root.iter(): + try: + tag = el.tag + except UnicodeDecodeError: + continue + + if tag == XHTML('style') and el.text and \ + (_css_url_re.search(el.text) is not None or '@import' in + el.text): + stylesheet = parseString(el.text) + replaceUrls(stylesheet, link_repl_func) + el.text = '\n'+stylesheet.cssText + '\n' + + if 'style' in el.attrib: + text = el.attrib['style'] + if _css_url_re.search(text) is not None: + stext = parseStyle(text) + changed = False + for p in stext.getProperties(all=True): + v = p.cssValue + if v.CSS_VALUE_LIST == v.cssValueType: + for item in v: + changed = True + set_property(item) + elif v.CSS_PRIMITIVE_VALUE == v.cssValueType: + changed = True + set_property(v) + if changed: + el.attrib['style'] = stext.cssText.replace('\n', ' ').replace('\r', + ' ') + + EPUB_MIME = types_map['.epub'] XHTML_MIME = types_map['.xhtml'] From be17dd8e4b9cc128af685e6d547add6aed18cbb0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Dec 2010 19:17:29 -0700 Subject: [PATCH 31/43] ... --- src/calibre/ebooks/oeb/base.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index d1bd1cab78..35d565606d 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -227,18 +227,14 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False): text = el.attrib['style'] if _css_url_re.search(text) is not None: stext = parseStyle(text) - changed = False for p in stext.getProperties(all=True): v = p.cssValue if v.CSS_VALUE_LIST == v.cssValueType: for item in v: - changed = True set_property(item) elif v.CSS_PRIMITIVE_VALUE == v.cssValueType: - changed = True set_property(v) - if changed: - el.attrib['style'] = stext.cssText.replace('\n', ' ').replace('\r', + el.attrib['style'] = stext.cssText.replace('\n', ' ').replace('\r', ' ') From f78767db15e4410a1393a954ec126dcf26db5b38 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 22 Dec 2010 13:24:39 +0000 Subject: [PATCH 32/43] Try harder to keep the tags view positioned after an item edit --- src/calibre/gui2/tag_view.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 478f6b042f..3d43d49a75 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -695,8 +695,10 @@ class TagsModel(QAbstractItemModel): # {{{ def setData(self, index, value, role=Qt.EditRole): if not index.isValid(): return NONE - # set up to position at the category label - path = self.path_for_index(self.parent(index)) + # set up to reposition at the same item. We can do this except if + # working with the last item and that item is deleted, in which case + # we position at the parent label + path = index.model().path_for_index(index) val = unicode(value.toString()) if not val: error_dialog(self.tags_view, _('Item is blank'), @@ -947,18 +949,22 @@ class TagBrowserMixin(object): # {{{ for old_id in to_rename[text]: rename_func(old_id, new_name=unicode(text)) - # Clean up everything, as information could have changed for many books. - self.library_view.model().refresh() - self.tags_view.set_new_model() - self.tags_view.recount() - self.saved_search.clear() - self.search.clear() + # Clean up the library view + self.do_tag_item_renamed() + self.tags_view.set_new_model() # does a refresh for free def do_tag_item_renamed(self): # Clean up library view and search - self.library_view.model().refresh() - self.saved_search.clear() - self.search.clear() + # get information to redo the selection + rows = [r.row() for r in \ + self.library_view.selectionModel().selectedRows()] + m = self.library_view.model() + ids = [m.id(r) for r in rows] + + m.refresh(reset=False) + m.research() + self.library_view.select_rows(ids) + # refreshing the tags view happens at the emit()/call() site def do_author_sort_edit(self, parent, id): db = self.library_view.model().db From d59a20fc2a2d9ec6e4d5abc17324631f845a047d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Dec 2010 10:39:14 -0700 Subject: [PATCH 33/43] Tag browser: When renaming items dont reset the library view and try not to scroll the Tag Browser itself --- src/calibre/gui2/tag_view.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 478f6b042f..3d43d49a75 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -695,8 +695,10 @@ class TagsModel(QAbstractItemModel): # {{{ def setData(self, index, value, role=Qt.EditRole): if not index.isValid(): return NONE - # set up to position at the category label - path = self.path_for_index(self.parent(index)) + # set up to reposition at the same item. We can do this except if + # working with the last item and that item is deleted, in which case + # we position at the parent label + path = index.model().path_for_index(index) val = unicode(value.toString()) if not val: error_dialog(self.tags_view, _('Item is blank'), @@ -947,18 +949,22 @@ class TagBrowserMixin(object): # {{{ for old_id in to_rename[text]: rename_func(old_id, new_name=unicode(text)) - # Clean up everything, as information could have changed for many books. - self.library_view.model().refresh() - self.tags_view.set_new_model() - self.tags_view.recount() - self.saved_search.clear() - self.search.clear() + # Clean up the library view + self.do_tag_item_renamed() + self.tags_view.set_new_model() # does a refresh for free def do_tag_item_renamed(self): # Clean up library view and search - self.library_view.model().refresh() - self.saved_search.clear() - self.search.clear() + # get information to redo the selection + rows = [r.row() for r in \ + self.library_view.selectionModel().selectedRows()] + m = self.library_view.model() + ids = [m.id(r) for r in rows] + + m.refresh(reset=False) + m.research() + self.library_view.select_rows(ids) + # refreshing the tags view happens at the emit()/call() site def do_author_sort_edit(self, parent, id): db = self.library_view.model().db From a722155d7638803838f8a2f90178afcf252d4063 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Dec 2010 10:52:36 -0700 Subject: [PATCH 34/43] oops --- src/calibre/gui2/dialogs/metadata_single.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 9cb9f7bbbc..c2588f57a8 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -809,7 +809,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.pubdate.setDate(QDate(dt.year, dt.month, dt.day)) summ = book.comments if summ: - prefix = self.comment.html + prefix = self.comments.html if prefix: prefix += '\n' self.comments.html = prefix + comments_to_html(summ) From bf661325db67fe0394e80e83dea8d1db51c43eba Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Dec 2010 11:01:48 -0700 Subject: [PATCH 35/43] ... --- resources/recipes/le_monde.recipe | 2 +- src/calibre/ebooks/oeb/base.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/resources/recipes/le_monde.recipe b/resources/recipes/le_monde.recipe index 18be6ca711..c14b8eeeff 100644 --- a/resources/recipes/le_monde.recipe +++ b/resources/recipes/le_monde.recipe @@ -4,7 +4,7 @@ from calibre.web.feeds.recipes import BasicNewsRecipe class LeMonde(BasicNewsRecipe): title = 'Le Monde' __author__ = 'veezh' - description = 'Actualités' + description = u'Actualit\xe9s' oldest_article = 1 max_articles_per_feed = 100 no_stylesheets = True diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index 35d565606d..c015868992 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -657,7 +657,10 @@ class Metadata(object): attrib[key] = prefixname(value, nsrmap) if namespace(self.term) == DC11_NS: elem = element(parent, self.term, attrib=attrib) - elem.text = self.value + try: + elem.text = self.value + except: + elem.text = repr(self.value) else: elem = element(parent, OPF('meta'), attrib=attrib) elem.attrib['name'] = prefixname(self.term, nsrmap) From cea5261e1207593889ef2be1165de11852b9b1bd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Dec 2010 11:05:12 -0700 Subject: [PATCH 36/43] NRC EPUB version by veezh --- resources/recipes/nrc-nl-epub.recipe | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 resources/recipes/nrc-nl-epub.recipe diff --git a/resources/recipes/nrc-nl-epub.recipe b/resources/recipes/nrc-nl-epub.recipe new file mode 100644 index 0000000000..da9b9195ce --- /dev/null +++ b/resources/recipes/nrc-nl-epub.recipe @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +#Based on Lars Jacob's Taz Digiabo recipe + +__license__ = 'GPL v3' +__copyright__ = '2010, veezh' + +''' +www.nrc.nl +''' +import os, urllib2, zipfile +import time +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ptempfile import PersistentTemporaryFile + + +class NRCHandelsblad(BasicNewsRecipe): + + title = u'NRC Handelsblad' + description = u'De EPUB-versie van NRC' + language = 'nl' + lang = 'nl-NL' + + __author__ = 'veezh' + + conversion_options = { + 'no_default_epub_cover' : True + } + + def build_index(self): + today = time.strftime("%Y%m%d") + domain = "http://digitaleeditie.nrc.nl" + + url = domain + "/digitaleeditie/helekrant/epub/nrc_" + today + ".epub" +# print url + + try: + f = urllib2.urlopen(url) + except urllib2.HTTPError: + self.report_progress(0,_('Kan niet inloggen om editie te downloaden')) + raise ValueError('Krant van vandaag nog niet beschikbaar') + + tmp = PersistentTemporaryFile(suffix='.epub') + self.report_progress(0,_('downloading epub')) + tmp.write(f.read()) + tmp.close() + + zfile = zipfile.ZipFile(tmp.name, 'r') + self.report_progress(0,_('extracting epub')) + + zfile.extractall(self.output_dir) + + tmp.close() + index = os.path.join(self.output_dir, 'content.opf') + + self.report_progress(1,_('epub downloaded and extracted')) + + return index From 1a0cbaaf522d7046a507adeed84327c4b925fd4c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Dec 2010 11:09:00 -0700 Subject: [PATCH 37/43] Fix #8017 (WSJ Not Working) --- resources/recipes/wsj.recipe | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/recipes/wsj.recipe b/resources/recipes/wsj.recipe index 88e07bcea3..4ce315200c 100644 --- a/resources/recipes/wsj.recipe +++ b/resources/recipes/wsj.recipe @@ -46,7 +46,7 @@ class WallStreetJournal(BasicNewsRecipe): br = BasicNewsRecipe.get_browser() if self.username is not None and self.password is not None: br.open('http://commerce.wsj.com/auth/login') - br.select_form(nr=0) + br.select_form(nr=1) br['user'] = self.username br['password'] = self.password res = br.submit() From 2afb62c9f7b46c83b836f030e6a60414c5d8a8ba Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 22 Dec 2010 18:11:11 +0000 Subject: [PATCH 38/43] Another try at the special collection stuff --- src/calibre/devices/prs505/driver.py | 11 +++++++++-- src/calibre/devices/usbms/books.py | 19 +++++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 23f59e4737..6652d581d4 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -58,9 +58,16 @@ class PRS505(USBMS): SUPPORTS_USE_AUTHOR_SORT = True EBOOK_DIR_MAIN = 'database/media/books' + ALL_BY_TITLE = _('All by title') + ALL_BY_AUTHOR = _('All by author') + EXTRA_CUSTOMIZATION_MESSAGE = _('Comma separated list of metadata fields ' 'to turn into collections on the device. Possibilities include: ')+\ - 'series, tags, authors' + 'series, tags, authors' +\ + _('. Two special collections are available: %s:%s and %s:%s. Add ' + 'these values to the list to enable them. The collections will be ' + 'given the name provided after the ":" character.')%( + 'abt', ALL_BY_TITLE, 'aba', ALL_BY_AUTHOR) EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(['series', 'tags']) plugboard = None @@ -151,7 +158,7 @@ class PRS505(USBMS): blists[i] = booklists[i] opts = self.settings() if opts.extra_customization: - collections = [x.lower().strip() for x in + collections = [x.strip() for x in opts.extra_customization.split(',')] else: collections = [] diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index 84b8585d5c..a9c980c31b 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -134,14 +134,17 @@ class CollectionsBookList(BookList): collections = {} # get the special collection names - try: - all_by_author = tweaks['device_special_collections']['author'] - except: - all_by_author = '' - try: - all_by_title = tweaks['device_special_collections']['title'] - except: - all_by_title = '' + all_by_author = '' + all_by_title = '' + ca = [] + for c in collection_attributes: + if c.startswith('aba:') and c[4:]: + all_by_author = c[4:] + elif c.startswith('abt:') and c[4:]: + all_by_title = c[4:] + else: + ca.append(c.lower()) + collection_attributes = ca for book in self: tsval = book.get('_pb_title_sort', From 252488739fa70e354ee9f98b0b6908c0cc809b64 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 22 Dec 2010 18:16:02 +0000 Subject: [PATCH 39/43] Remove special collections tweak --- resources/default_tweaks.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index d2e4758ba6..a420cd7d44 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -180,25 +180,6 @@ sony_collection_renaming_rules={} # Default: empty (no rules), so no collection attributes are named. sony_collection_sorting_rules = [] -# Specify whether special collections are to be made. This option is primarily -# of use on a Sony. The two available are all_by_author and all_by_title. These -# collections work around various device idiosyncrasies regarding sorting of -# lists, especially the sony *50 models. The author collection is sorted by -# author(s) then title. The title collection is sorted by title then authors(s) -# Enable a collection by entering a collection name in the variable. That -# collection name must be unique. -# Examples: -# device_special_collections = {'title':'', 'author':'%All by author'} -# create a collection named '%All by author' of all books sorted by author -# device_special_collections = {'title':'%All by title', 'author':''} -# create a collection named '%All by title' of books sorted by title, -# respecting the order tweaks -# device_special_collections = {'title':'%All by title', 'author':'%All by author'} -# make both collections -# sony_all_books_by_author_collection = {'title':'', 'author':''} -# disable both collections -device_special_collections = {'title':'', 'author':''} - # Create search terms to apply a query across several built-in search terms. # Syntax: {'new term':['existing term 1', 'term 2', ...], 'new':['old'...] ...} From f182953c354774f065499bdf95cb853894952f5c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Dec 2010 11:53:20 -0700 Subject: [PATCH 40/43] Add an entry to the menu for the calibre library to pick a random book. Fixes #8010 (random book) --- src/calibre/gui2/actions/choose_library.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index b1f0bd6b0e..6f4e883b1a 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -166,6 +166,7 @@ class ChooseLibraryAction(InterfaceAction): self.choose_menu = QMenu(self.gui) self.qaction.setMenu(self.choose_menu) + if not os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', None): self.choose_menu.addAction(self.action_choose) @@ -176,6 +177,11 @@ class ChooseLibraryAction(InterfaceAction): self.delete_menu = QMenu(_('Delete library')) self.delete_menu_action = self.choose_menu.addMenu(self.delete_menu) + ac = self.create_action(spec=(_('Pick a random book'), 'catalog.png', + None, None), attr='action_pick_random') + ac.triggered.connect(self.pick_random) + self.choose_menu.addAction(ac) + self.rename_separator = self.choose_menu.addSeparator() self.switch_actions = [] @@ -213,6 +219,12 @@ class ChooseLibraryAction(InterfaceAction): self.maintenance_menu.addAction(ac) self.choose_menu.addMenu(self.maintenance_menu) + def pick_random(self, *args): + import random + pick = random.randint(0, self.gui.library_view.model().rowCount(None)) + self.gui.library_view.set_current_row(pick) + self.gui.library_view.scroll_to_row(pick) + def library_name(self): db = self.gui.library_view.model().db path = db.library_path From c1e01e107b5e55c8b97a518c856ac23b208829d1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Dec 2010 12:57:24 -0700 Subject: [PATCH 41/43] OTA instruction for iBooks --- src/calibre/manual/faq.rst | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 55451206b6..b05d4d2a5a 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -181,16 +181,17 @@ How do I use |app| with my iPad/iPhone/iTouch? Over the air ^^^^^^^^^^^^^^ -The easiest way to browse your |app| collection on your Apple device (iPad/iPhone/iPod) is by using the *free* Stanza app, available from the Apple app store. You need at least Stanza version 3.0. Stanza allows you to access your |app| collection wirelessly, over the air. - -First perform the following steps in |app| +The easiest way to browse your |app| collection on your Apple device (iPad/iPhone/iPod) is by using the calibre sontent server, which makes your collection available over the net. First perform the following steps in |app| * Set the Preferred Output Format in |app| to EPUB (The output format can be set under :guilabel:`Preferences->Interface->Behavior`) * Set the output profile to iPad (this will work for iPhone/iPods as well), under :guilabel:`Preferences->Conversion->Common Options->Page Setup` * Convert the books you want to read on your iPhone to EPUB format by selecting them and clicking the Convert button. * Turn on the Content Server in |app|'s preferences and leave |app| running. -Install the free Stanza reader app on your iPad/iPhone/iTouch using iTunes. +Now on your iPad/iPhone you have two choices, use either iBooks (version 1.2 and later) or Stanza (version 3.0 and later). Both are available free from the app store. + +Using Stanza +*************** Now you should be able to access your books on your iPhone by opening Stanza. Go to "Get Books" and then click the "Shared" tab. Under Shared you will see an entry "Books in calibre". If you don't, make sure your iPad/iPhone is connected using the WiFi network in your house, not 3G. If the |app| catalog is still not detected in Stanza, you can add it manually in Stanza. To do this, click the "Shared" tab, then click the "Edit" button and then click "Add book source" to add a new book source. In the Add Book Source screen enter whatever name you like and in the URL field, enter the following:: @@ -200,6 +201,18 @@ Replace ``192.168.1.2`` with the local IP address of the computer running |app|. If you get timeout errors while browsing the calibre catalog in Stanza, try increasing the connection timeout value in the stanza settings. Go to Info->Settings and increase the value of Download Timeout. +Using iBooks +************** + +Start the Safari browser and type in the IP address and port of the computer running the calibre server, like this:: + + http://192.168.1.2:8080/ + +Replace ``192.168.1.2`` with the local IP address of the computer running |app|. If you have changed the port the |app| content server is running on, you will have to change ``8080`` as well to the new port. The local IP address is the IP address you computer is assigned on your home network. A quick Google search will tell you how to find out your local IP address. + +You wills ee a list of books in Safari, just click on the epub link for whichever book you want to read, Safari will then prompt you to open it with iBooks. + + With the USB cable ^^^^^^^^^^^^^^^^^^^^^^^^^^^ From d93b5395ada27625924a3eb19db963cdc5ac173c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Dec 2010 15:37:19 -0700 Subject: [PATCH 42/43] Fix #7930 (BusinessWeek not fetching) --- resources/recipes/bwmagazine.recipe | 130 ++++++++++++++++++---------- 1 file changed, 85 insertions(+), 45 deletions(-) diff --git a/resources/recipes/bwmagazine.recipe b/resources/recipes/bwmagazine.recipe index 26dbc459d3..e3a4e3337a 100644 --- a/resources/recipes/bwmagazine.recipe +++ b/resources/recipes/bwmagazine.recipe @@ -1,64 +1,104 @@ - __license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' +__copyright__ = '2008 Kovid Goyal kovid@kovidgoyal.net, 2010 Darko Miletic ' ''' -http://www.businessweek.com/magazine/news/articles/business_news.htm +www.businessweek.com ''' -from calibre import strftime +import re from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import BeautifulSoup -class BWmagazine(BasicNewsRecipe): - title = 'BusinessWeek Magazine' - __author__ = 'Darko Miletic' - description = 'Stay up to date with BusinessWeek magazine articles. Read news on international business, personal finances & the economy in the BusinessWeek online magazine.' +class BusinessWeek(BasicNewsRecipe): + title = 'Business Week' + __author__ = 'Kovid Goyal and Darko Miletic' + description = 'Read the latest international business news & stock market news. Get updated company profiles, financial advice, global economy and technology news.' publisher = 'Bloomberg L.P.' - category = 'news, International Business News, current news in international business,international business articles, personal business, business week magazine, business week magazine articles, business week magazine online, business week online magazine' - oldest_article = 10 - max_articles_per_feed = 100 + category = 'Business, business news, stock market, stock market news, financial advice, company profiles, financial advice, global economy, technology news' + oldest_article = 7 + max_articles_per_feed = 200 no_stylesheets = True - encoding = 'utf-8' + encoding = 'utf8' use_embedded_content = False language = 'en' - INDEX = 'http://www.businessweek.com/magazine/news/articles/business_news.htm' + remove_empty_feeds = True + publication_type = 'magazine' cover_url = 'http://images.businessweek.com/mz/covers/current_120x160.jpg' - + masthead_url = 'http://assets.businessweek.com/images/bw-logo.png' + extra_css = """ + body{font-family: Helvetica,Arial,sans-serif } + img{margin-bottom: 0.4em; display:block} + .tagline{color: gray; font-style: italic} + .photoCredit{font-size: small; color: gray} + """ conversion_options = { - 'comment' : description - , 'tags' : category - , 'publisher' : publisher - , 'language' : language + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language } + remove_tags = [ + dict(attrs={'class':'inStory'}) + ,dict(name=['meta','link','iframe','base','embed','object','table','th','tr','td']) + ,dict(attrs={'id':['inset','videoDisplay']}) + ] + keep_only_tags = [dict(name='div', attrs={'id':['story-body','storyBody']})] + remove_attributes = ['lang'] + match_regexps = [r'http://www.businessweek.com/.*_page_[1-9].*'] - def parse_index(self): - articles = [] - soup = self.index_to_soup(self.INDEX) - ditem = soup.find('div',attrs={'id':'column2'}) - if ditem: - for item in ditem.findAll('h3'): - title_prefix = '' - description = '' - feed_link = item.find('a') - if feed_link and feed_link.has_key('href'): - url = 'http://www.businessweek.com/magazine/' + feed_link['href'].partition('../../')[2] - title = title_prefix + self.tag_to_string(feed_link) - date = strftime(self.timefmt) - articles.append({ - 'title' :title - ,'date' :date - ,'url' :url - ,'description':description - }) - return [(soup.head.title.string, articles)] - keep_only_tags = dict(name='div', attrs={'id':'storyBody'}) + feeds = [ + (u'Top Stories', u'http://www.businessweek.com/topStories/rss/topStories.rss'), + (u'Top News' , u'http://www.businessweek.com/rss/bwdaily.rss' ), + (u'Asia', u'http://www.businessweek.com/rss/asia.rss'), + (u'Autos', u'http://www.businessweek.com/rss/autos/index.rss'), + (u'Classic Cars', u'http://rss.businessweek.com/bw_rss/classiccars'), + (u'Hybrids', u'http://rss.businessweek.com/bw_rss/hybrids'), + (u'Europe', u'http://www.businessweek.com/rss/europe.rss'), + (u'Auto Reviews', u'http://rss.businessweek.com/bw_rss/autoreviews'), + (u'Innovation & Design', u'http://www.businessweek.com/rss/innovate.rss'), + (u'Architecture', u'http://www.businessweek.com/rss/architecture.rss'), + (u'Brand Equity', u'http://www.businessweek.com/rss/brandequity.rss'), + (u'Auto Design', u'http://www.businessweek.com/rss/carbuff.rss'), + (u'Game Room', u'http://rss.businessweek.com/bw_rss/gameroom'), + (u'Technology', u'http://www.businessweek.com/rss/technology.rss'), + (u'Investing', u'http://rss.businessweek.com/bw_rss/investor'), + (u'Small Business', u'http://www.businessweek.com/rss/smallbiz.rss'), + (u'Careers', u'http://rss.businessweek.com/bw_rss/careers'), + (u'B-Schools', u'http://www.businessweek.com/rss/bschools.rss'), + (u'Magazine Selections', u'http://www.businessweek.com/rss/magazine.rss'), + (u'CEO Guide to Tech', u'http://www.businessweek.com/rss/ceo_guide_tech.rss'), + ] + + def get_article_url(self, article): + url = article.get('guid', None) + if 'podcasts' in url: + return None + if 'surveys' in url: + return None + if 'images' in url: + return None + if 'feedroom' in url: + return None + if '/magazine/toc/' in url: + return None + rurl, sep, rest = url.rpartition('?') + if rurl: + return rurl + return rest def print_version(self, url): - rurl = url.rpartition('?')[0] - if rurl == '': - rurl = url - return rurl.replace('.com/magazine/','.com/print/magazine/') - - + if '/news/' in url or '/blog/ in url': + return url + rurl = url.replace('http://www.businessweek.com/','http://www.businessweek.com/print/') + return rurl.replace('/investing/','/investor/') + + def preprocess_html(self, soup): + for item in soup.findAll(style=True): + del item['style'] + for alink in soup.findAll('a'): + if alink.string is not None: + tstr = alink.string + alink.replaceWith(tstr) + return soup \ No newline at end of file From 6e4be52e9497517f3017f1e0c58cb90330993e4c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Dec 2010 15:56:40 -0700 Subject: [PATCH 43/43] ... --- resources/recipes/bwmagazine.recipe | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/resources/recipes/bwmagazine.recipe b/resources/recipes/bwmagazine.recipe index e3a4e3337a..9a1f10a680 100644 --- a/resources/recipes/bwmagazine.recipe +++ b/resources/recipes/bwmagazine.recipe @@ -4,9 +4,7 @@ __copyright__ = '2008 Kovid Goyal kovid@kovidgoyal.net, 2010 Darko Miletic