Sync to trunk.

This commit is contained in:
John Schember 2011-08-14 03:16:01 -04:00
commit dae4b7cfa6
128 changed files with 65809 additions and 133057 deletions

View File

@ -19,6 +19,53 @@
# new recipes:
# - title:
- version: 0.8.14
date: 2011-08-12
new features:
- title: "Make the keyboard shortcuts used by the main calibre interface user customizable, via Preferences->Advanced->Keyboard"
type: major
- title: "When switching libraries, if the library no longer exists, give the user a chance to specify a new location for the library, in case it was moved, before forgetting it."
tickets: [822018]
- title: "Template language: Add strcat and strlen builtin functions."
tickets: [821935]
bug fixes:
- title: "The various options to control how automerging works when adding books now also apply when copying a book from one library to another."
tickets: [822033]
- title: "Ebook viewer: Respond to key presses even when the book display area does not have keyboard focus"
- title: "Allow integer and float column values to go to -999999. -1000000 is the value of 'undefined'."
tickets: [821941]
- title: "Fix in calibre browser not working for the Open books store in Get Books."
tickets: [822359]
- title: "Fix regression in 0.8.13 that caused incorrect title/author for downloaded news if you turned off reading metadata from file contents in Preferences->Adding books"
- title: "Save to disk: When saving to a single directory, handle the case of the save to disk template containing path separators inside template expression correctly."
tickets: [821912]
- title: "Get Books: Always read metadata from the file contents, ignoring the setting in Preferences->Adding books"
- title: "Fix merge_metadata to not overwrite non-text fields ('bool', 'int', 'float', 'rating', 'datetime') that have a value of zero/false instead of None."
tickets: [821665]
improved recipes:
- The Independent
new recipes:
- title: "Novinite"
author: Martin Tsanchev
- title: "Blog Escrevinhador"
author: Diniz Bortolotto
- version: 0.8.13
date: 2011-08-05
@ -251,8 +298,8 @@
- title: Techcrunch and Pecat
author: Darko Miletic
- title: Vio Mundo, IDG Now and Tojolaco
author: Diniz Bortoletto
- title: "Vio Mundo, IDG Now! and Tojolaco"
author: Diniz Bortolotto
- title: Geek and Poke, Automatiseringgids IT
author: DrMerry

912
imgsrc/keyboard-prefs.svg Normal file
View File

@ -0,0 +1,912 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
id="Livello_1"
width="128"
height="128"
viewBox="0 0 144 94"
overflow="visible"
enable-background="new 0 0 144 94"
xml:space="preserve"
sodipodi:version="0.32"
inkscape:version="0.45+devel"
sodipodi:docname="preferences-desktop-keyboard.svgz"
inkscape:output_extension="org.inkscape.output.svgz.inkscape"
style="overflow:visible"><metadata
id="metadata224"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs222"><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_35_"
id="linearGradient2719"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
x1="72.000504"
y1="83.799797"
x2="72.000504"
y2="5.8003001" /><linearGradient
inkscape:collect="always"
xlink:href="#rect3941_1_"
id="linearGradient2721"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.474754,0,0,-0.465075,-255.92554,-542.49842)"
x1="780.77576"
y1="-1248.1824"
x2="780.81049"
y2="-1195.5962" /><linearGradient
inkscape:collect="always"
xlink:href="#rect3941_1_"
id="linearGradient2723"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.474754,0,0,-0.465075,-263.1733,-542.49842)"
x1="708.36438"
y1="-1248.1824"
x2="708.39648"
y2="-1195.5962" /><linearGradient
inkscape:collect="always"
xlink:href="#rect3941_1_"
id="linearGradient2725"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.474754,0,0,-0.465075,-270.42218,-542.49842)"
x1="635.95538"
y1="-1248.1824"
x2="635.9834"
y2="-1195.5962" /><linearGradient
inkscape:collect="always"
xlink:href="#rect3941_1_"
id="linearGradient2727"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.474754,0,0,-0.465075,-253.92268,-535.12325)"
x1="790.77502"
y1="-1324.245"
x2="790.81049"
y2="-1271.6509" /><linearGradient
inkscape:collect="always"
xlink:href="#rect3941_1_"
id="linearGradient2729"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.474754,0,0,-0.465075,-261.17157,-535.12325)"
x1="718.36609"
y1="-1324.245"
x2="718.39838"
y2="-1271.6509" /><linearGradient
inkscape:collect="always"
xlink:href="#rect3941_1_"
id="linearGradient2731"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.474754,0,0,-0.465075,-268.41933,-535.12325)"
x1="645.95471"
y1="-1324.245"
x2="645.9834"
y2="-1271.6509" /><filter
inkscape:collect="always"
x="-0.36659993"
width="1.7331999"
y="-0.17839379"
height="1.3567876"
id="filter3416"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.51430916"
id="feGaussianBlur3418" /></filter><filter
inkscape:collect="always"
x="-0.36972603"
width="1.7394521"
y="-0.17766281"
height="1.3553256"
id="filter3424"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.51984026"
id="feGaussianBlur3426" /></filter><filter
inkscape:collect="always"
x="-0.22179123"
width="1.4435825"
y="-0.10660794"
height="1.2132159"
id="filter3444"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.31193415"
id="feGaussianBlur3446" /></filter><filter
inkscape:collect="always"
x="-0.21995996"
width="1.4399199"
y="-0.10703628"
height="1.2140726"
id="filter3448"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.30858549"
id="feGaussianBlur3450" /></filter><filter
inkscape:collect="always"
x="-0.22183562"
width="1.4436712"
y="-0.10659768"
height="1.2131954"
id="filter3452"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.31190415"
id="feGaussianBlur3454" /></filter><filter
inkscape:collect="always"
x="-0.21995996"
width="1.4399199"
y="-0.10703628"
height="1.2140726"
id="filter3456"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.30858549"
id="feGaussianBlur3458" /></filter><filter
inkscape:collect="always"
x="-0.22179123"
width="1.4435825"
y="-0.10660794"
height="1.2132159"
id="filter3460"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.31193415"
id="feGaussianBlur3462" /></filter><filter
inkscape:collect="always"
x="-0.21991603"
width="1.4398321"
y="-0.10704668"
height="1.2140934"
id="filter3464"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.30861549"
id="feGaussianBlur3466" /></filter><filter
inkscape:collect="always"
x="-0.22179123"
width="1.4435825"
y="-0.10660794"
height="1.2132159"
id="filter3468"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.31193415"
id="feGaussianBlur3470" /></filter><filter
inkscape:collect="always"
x="-0.21995996"
width="1.4399199"
y="-0.10703628"
height="1.2140726"
id="filter3472"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.30858549"
id="feGaussianBlur3474" /></filter><filter
inkscape:collect="always"
x="-0.22183562"
width="1.4436712"
y="-0.10659768"
height="1.2131954"
id="filter3476"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.31190415"
id="feGaussianBlur3478" /></filter><filter
inkscape:collect="always"
x="-0.21995996"
width="1.4399199"
y="-0.10703628"
height="1.2140726"
id="filter3484"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.30858549"
id="feGaussianBlur3486" /></filter><filter
inkscape:collect="always"
x="-0.02891983"
width="1.0578397"
y="-0.14107949"
height="1.282159"
id="filter3492"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.33591005"
id="feGaussianBlur3494" /></filter><filter
inkscape:collect="always"
x="-0.02891983"
width="1.0578397"
y="-0.14107949"
height="1.282159"
id="filter3496"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.33591005"
id="feGaussianBlur3498" /></filter><filter
inkscape:collect="always"
x="-0.028919654"
width="1.0578393"
y="-0.14108369"
height="1.2821674"
id="filter3500"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.33592005"
id="feGaussianBlur3502" /></filter><filter
inkscape:collect="always"
x="-0.02891983"
width="1.0578397"
y="-0.14107949"
height="1.282159"
id="filter3504"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.33591005"
id="feGaussianBlur3506" /></filter><filter
inkscape:collect="always"
x="-0.02891983"
width="1.0578397"
y="-0.14107949"
height="1.282159"
id="filter3508"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.33591005"
id="feGaussianBlur3510" /></filter><filter
inkscape:collect="always"
x="-0.02891983"
width="1.0578397"
y="-0.14107949"
height="1.282159"
id="filter3512"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.33591005"
id="feGaussianBlur3514" /></filter><linearGradient
id="XMLID_31_"
gradientUnits="userSpaceOnUse"
x1="69.333504"
y1="17.6504"
x2="69.333504"
y2="9.7958002"
xlink:href="#XMLID_32_">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop169" />
<stop
offset="1"
style="stop-color:#DDDDDD"
id="stop171" />
</linearGradient><linearGradient
id="XMLID_32_"
gradientUnits="userSpaceOnUse"
x1="106.334"
y1="17.6504"
x2="106.334"
y2="9.7958002">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop186" />
<stop
offset="1"
style="stop-color:#DDDDDD"
id="stop188" />
</linearGradient><linearGradient
id="XMLID_30_"
gradientUnits="userSpaceOnUse"
x1="31.742201"
y1="17.6504"
x2="31.742201"
y2="9.7958002">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop152" /><stop
id="stop3366"
style="stop-color:#eaeaea;stop-opacity:1;"
offset="0.68235296" />
<stop
offset="1"
style="stop-color:#c8c8c8;stop-opacity:1;"
id="stop154" />
</linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient2945"
gradientUnits="userSpaceOnUse"
x1="69.333504"
y1="17.6504"
x2="69.333504"
y2="9.7958002" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient2947"
gradientUnits="userSpaceOnUse"
x1="106.334"
y1="17.6504"
x2="106.334"
y2="9.7958002" /><linearGradient
xlink:href="#XMLID_30_"
id="XMLID_28_"
gradientUnits="userSpaceOnUse"
x1="38.033699"
y1="55.649399"
x2="38.033699"
y2="47.795502"
spreadMethod="pad">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop118" />
<stop
offset="1"
style="stop-color:#DDDDDD"
id="stop120" />
</linearGradient><linearGradient
id="XMLID_29_"
gradientUnits="userSpaceOnUse"
x1="75.333"
y1="55.649399"
x2="75.333"
y2="47.795502">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop135" />
<stop
offset="1"
style="stop-color:#DDDDDD"
id="stop137" />
</linearGradient><linearGradient
id="XMLID_33_"
gradientUnits="userSpaceOnUse"
x1="112.334"
y1="55.649399"
x2="112.334"
y2="47.795502">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop203" />
<stop
offset="1"
style="stop-color:#DDDDDD"
id="stop205" />
</linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient2979"
gradientUnits="userSpaceOnUse"
spreadMethod="pad"
x1="38.033699"
y1="55.649399"
x2="38.033699"
y2="47.795502" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient2981"
gradientUnits="userSpaceOnUse"
x1="75.333"
y1="55.649399"
x2="75.333"
y2="47.795502" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient2983"
gradientUnits="userSpaceOnUse"
x1="112.334"
y1="55.649399"
x2="112.334"
y2="47.795502" /><filter
inkscape:collect="always"
id="filter3372"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.3179705"
id="feGaussianBlur3374" /></filter><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient3378"
gradientUnits="userSpaceOnUse"
x1="106.334"
y1="17.6504"
x2="106.334"
y2="9.7958002" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient3380"
gradientUnits="userSpaceOnUse"
x1="31.742201"
y1="17.6504"
x2="31.742201"
y2="9.7958002" /></defs><sodipodi:namedview
inkscape:window-height="696"
inkscape:window-width="998"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
guidetolerance="10.0"
gridtolerance="10.0"
objecttolerance="10.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base"
height="128px"
width="128px"
inkscape:zoom="2.8284271"
inkscape:cx="65.761733"
inkscape:cy="68.182683"
inkscape:window-x="26"
inkscape:window-y="0"
inkscape:current-layer="g2620" />
<filter
id="AI_Sfocatura_2">
<feGaussianBlur
stdDeviation="2"
id="feGaussianBlur4" />
</filter>
<path
display="none"
d="M 89.758,1.8 C 88.983,1.8 88.229,1.945 87.501,2.17 C 86.774,1.945 86.022,1.8 85.248,1.8 L 52.752,1.8 C 51.978,1.8 51.226,1.945 50.499,2.17 C 49.772,1.944 49.02,1.8 48.245,1.8 L 15.755,1.8 C 11.479,1.8 8,5.275 8,9.546 L 8,42.054 C 8,45.72 10.57,48.783 14,49.582 C 14,53.16 14,80.057 14,80.057 C 14,84.327 17.478,87.8 21.752,87.8 L 54.248,87.8 C 55.022,87.8 55.774,87.655 56.5,87.431 C 57.227,87.656 57.979,87.8 58.754,87.8 L 91.241,87.8 C 92.017,87.8 92.77,87.655 93.498,87.431 C 94.225,87.655 94.977,87.8 95.751,87.8 L 128.241,87.8 C 132.518,87.8 135.999,84.326 135.999,80.057 L 135.999,47.546 C 135.999,43.881 133.43,40.818 129.999,40.019 C 129.999,36.442 129.999,9.546 129.999,9.546 C 129.999,5.275 126.521,1.8 122.247,1.8 L 89.758,1.8 L 89.758,1.8 z"
id="path6"
style="fill:#ff00bf;display:none" />
<linearGradient
id="XMLID_35_"
gradientUnits="userSpaceOnUse"
x1="72.000504"
y1="83.799797"
x2="72.000504"
y2="5.8003001">
<stop
offset="0.0059"
style="stop-color:#888888"
id="stop13" />
<stop
offset="0.5"
style="stop-color:#555555"
id="stop15" />
<stop
offset="0.54"
style="stop-color:#888888"
id="stop17" />
<stop
offset="1"
style="stop-color:#555555"
id="stop19" />
</linearGradient>
<linearGradient
id="rect3785_1_"
gradientUnits="userSpaceOnUse"
x1="780.81049"
y1="-1240.9404"
x2="780.81049"
y2="-1195.5962"
gradientTransform="matrix(0.422,0,0,-0.4134,-223.4874,-472.1986)">
<stop
offset="0"
style="stop-color:#BEBEBE"
id="stop24" />
<stop
offset="1"
style="stop-color:#EDEDED"
id="stop26" />
</linearGradient>
<linearGradient
id="rect3791_1_"
gradientUnits="userSpaceOnUse"
x1="708.39648"
y1="-1240.9404"
x2="708.39648"
y2="-1195.5962"
gradientTransform="matrix(0.422,0,0,-0.4134,-229.9298,-472.1986)">
<stop
offset="0"
style="stop-color:#BEBEBE"
id="stop38" />
<stop
offset="1"
style="stop-color:#EDEDED"
id="stop40" />
</linearGradient>
<linearGradient
id="rect3797_1_"
gradientUnits="userSpaceOnUse"
x1="635.9834"
y1="-1240.9404"
x2="635.9834"
y2="-1195.5962"
gradientTransform="matrix(0.422,0,0,-0.4134,-236.3732,-472.1986)">
<stop
offset="0"
style="stop-color:#BEBEBE"
id="stop52" />
<stop
offset="1"
style="stop-color:#EDEDED"
id="stop54" />
</linearGradient>
<linearGradient
id="rect3929_1_"
gradientUnits="userSpaceOnUse"
x1="790.81049"
y1="-1316.9951"
x2="790.81049"
y2="-1271.6509"
gradientTransform="matrix(0.422,0,0,-0.4134,-221.7071,-465.6429)">
<stop
offset="0"
style="stop-color:#BEBEBE"
id="stop66" />
<stop
offset="1"
style="stop-color:#EDEDED"
id="stop68" />
</linearGradient>
<linearGradient
id="rect3935_1_"
gradientUnits="userSpaceOnUse"
x1="718.39838"
y1="-1316.9951"
x2="718.39838"
y2="-1271.6509"
gradientTransform="matrix(0.422,0,0,-0.4134,-228.1505,-465.6429)">
<stop
offset="0"
style="stop-color:#BEBEBE"
id="stop80" />
<stop
offset="1"
style="stop-color:#EDEDED"
id="stop82" />
</linearGradient>
<linearGradient
id="rect3941_1_"
gradientUnits="userSpaceOnUse"
x1="645.9834"
y1="-1316.9951"
x2="645.9834"
y2="-1271.6509"
gradientTransform="matrix(0.422,0,0,-0.4134,-234.5929,-465.6429)">
<stop
offset="0"
style="stop-color:#e4e4e4;stop-opacity:1;"
id="stop94" /><stop
id="stop3516"
style="stop-color:#bebebe;stop-opacity:1;"
offset="0.18012393" /><stop
offset="0.61417598"
style="stop-color:#cdcdcd;stop-opacity:1;"
id="stop3376" />
<stop
offset="1"
style="stop-color:#b3b3b3;stop-opacity:1;"
id="stop96" />
</linearGradient>
<g
id="g8"
style="opacity:0.8;filter:url(#AI_Sfocatura_2)"
transform="matrix(1.1250094,0,0,1.125,-9.0001128,-12.4)">
<path
d="M 128.242,45.8 L 125.547,45.8 C 125.826,45.276 126,44.688 126,44.054 L 126,11.546 C 126,9.481 124.316,7.8 122.248,7.8 L 89.758,7.8 C 88.907,7.8 88.131,8.093 87.501,8.57 C 86.872,8.093 86.097,7.8 85.248,7.8 L 52.752,7.8 C 51.903,7.8 51.128,8.093 50.499,8.57 C 49.869,8.094 49.094,7.8 48.245,7.8 L 15.755,7.8 C 13.685,7.8 12,9.48 12,11.546 L 12,44.054 C 12,46.119 13.685,47.8 15.755,47.8 L 18.453,47.8 C 18.173,48.323 18,48.912 18,49.546 L 18,82.057 C 18,84.121 19.683,85.8 21.752,85.8 L 54.248,85.8 C 55.097,85.8 55.872,85.507 56.5,85.031 C 57.13,85.507 57.905,85.8 58.754,85.8 L 91.241,85.8 C 92.092,85.8 92.868,85.507 93.498,85.03 C 94.127,85.507 94.902,85.8 95.751,85.8 L 128.241,85.8 C 130.313,85.8 131.999,84.121 131.999,82.057 L 131.999,49.546 C 132,47.48 130.314,45.8 128.242,45.8 z"
id="path10"
style="opacity:0.8" />
</g><g
id="g2620"
transform="translate(-4.5,0)"><path
style="fill:url(#linearGradient2721);fill-opacity:1"
d="M 96.478481,-2.5 L 133.03004,-2.5 C 134.11905,-2.5 135.00105,-1.621375 135.00105,-0.53575 L 135.00105,36.03575 C 135.00105,37.121374 134.11905,38 133.03004,38 L 96.478481,38 C 95.382722,38 94.500715,37.121374 94.500715,36.03575 L 94.500715,-0.53575 C 94.500715,-1.621375 95.382722,-2.5 96.478481,-2.5 z"
id="rect3785" /><g
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
nodetypes="cccsssssscccc"
id="path3787">
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.6404,-8.5e-3,-7.7e-3,-0.6279,-240.4975,-567.9111)"
r="40.036301"
cy="-971.75677"
cx="527.62299"
id="XMLID_17_">
<stop
id="stop31"
style="stop-color:#E8E8E8"
offset="0" />
<stop
id="stop33"
style="stop-color:#FFFFFF"
offset="1" />
</radialGradient>
<path
style="fill:url(#XMLID_17_)"
id="path35"
d="M 91.145,9.856 L 89.924,35.848 L 89.852,37.041 C 91.107,37.752 92.344,38.38 93.661,38.908 C 93.683,38.914 93.717,38.9 93.738,38.908 C 97.663,40.465 101.819,41.179 105.966,41.145 C 106.662,41.14 107.359,41.12 108.055,41.072 C 109.436,40.973 110.852,40.816 112.223,40.55 C 113.593,40.285 114.914,39.932 116.252,39.504 C 118.259,38.861 120.26,38.043 122.15,37.042 L 122.077,35.849 L 120.928,9.857 L 91.145,9.857 L 91.145,9.856 z" />
</g><path
style="fill:url(#linearGradient2723);fill-opacity:1"
d="M 54.846383,-2.5 L 91.404689,-2.5 C 92.492573,-2.5 93.375705,-1.621375 93.375705,-0.53575 L 93.375705,36.03575 C 93.375705,37.121374 92.492573,38 91.404689,38 L 54.846383,38 C 53.758499,38 52.875367,37.121374 52.875367,36.03575 L 52.875367,-0.53575 C 52.875367,-1.621375 53.758499,-2.5 54.846383,-2.5 z"
id="rect3791" /><g
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
nodetypes="cccsssssscccc"
id="path3793">
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.6404,-8.5e-3,-7.7e-3,-0.6279,-247.2058,-567.9111)"
r="40.036098"
cy="-971.12653"
cx="480.29791"
id="XMLID_19_">
<stop
id="stop45"
style="stop-color:#E8E8E8"
offset="0" />
<stop
id="stop47"
style="stop-color:#FFFFFF"
offset="1" />
</radialGradient>
<path
style="fill:url(#XMLID_19_)"
id="path49"
d="M 54.145,9.856 L 52.921,35.848 L 52.849,37.041 C 54.109,37.752 55.348,38.38 56.663,38.908 C 56.682,38.914 56.715,38.9 56.734,38.908 C 60.662,40.465 64.821,41.179 68.962,41.145 C 69.658,41.14 70.355,41.12 71.048,41.072 C 72.433,40.973 73.848,40.816 75.218,40.55 C 76.589,40.285 77.909,39.932 79.253,39.504 C 81.254,38.861 83.255,38.043 85.147,37.042 L 85.08,35.849 L 83.927,9.856 L 54.145,9.856 z" />
</g><path
style="fill:url(#linearGradient2725);fill-opacity:1"
d="M 13.224411,-2.5 L 49.775966,-2.5 C 50.867225,-2.5 51.750357,-1.621375 51.750357,-0.53575 L 51.750357,36.03575 C 51.750357,37.121374 50.867225,38 49.775966,38 L 13.224411,38 C 12.133151,38 11.250019,37.121374 11.250019,36.03575 L 11.250019,-0.53575 C 11.250019,-1.621375 12.133151,-2.5 13.224411,-2.5 z"
id="rect3797" /><g
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
nodetypes="cccsssssscccc"
id="path3799">
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.6403,-8.5e-3,-7.7e-3,-0.6279,-253.8748,-567.9111)"
r="40.033199"
cy="-970.48578"
cx="432.98141"
id="XMLID_24_">
<stop
id="stop59"
style="stop-color:#E8E8E8"
offset="0" />
<stop
id="stop61"
style="stop-color:#FFFFFF"
offset="1" />
</radialGradient>
<path
style="fill:url(#XMLID_24_)"
id="path63"
d="M 17.146,9.856 L 15.924,35.848 L 15.852,37.041 C 17.109,37.752 18.348,38.38 19.663,38.908 C 19.682,38.914 19.716,38.9 19.735,38.908 C 23.662,40.465 27.821,41.179 31.964,41.145 C 32.657,41.14 33.357,41.12 34.05,41.072 C 35.435,40.973 36.85,40.816 38.22,40.55 C 39.591,40.285 40.909,39.932 42.249,39.504 C 44.258,38.861 46.254,38.043 48.146,37.042 L 48.075,35.849 L 46.925,9.857 L 17.146,9.857 L 17.146,9.856 z" />
</g><path
style="fill:url(#linearGradient2727);fill-opacity:1"
d="M 103.22179,40.25 L 139.77334,40.25 C 140.8691,40.25 141.75111,41.128625 141.75111,42.21425 L 141.75111,78.789125 C 141.75111,79.87475 140.8691,80.75 139.77334,80.75 L 103.22179,80.75 C 102.13278,80.75 101.25077,79.873625 101.25077,78.789125 L 101.25077,42.21425 C 101.25077,41.128625 102.13278,40.25 103.22179,40.25 z"
id="rect3929" /><g
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
nodetypes="cccsssssscccc"
id="path3931">
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.6404,-8.5e-3,-7.7e-3,-0.6279,-238.6489,-561.7972)"
r="40.036499"
cy="-1022.5366"
cx="533.49512"
id="XMLID_25_">
<stop
id="stop73"
style="stop-color:#E8E8E8"
offset="0" />
<stop
id="stop75"
style="stop-color:#FFFFFF"
offset="1" />
</radialGradient>
<path
style="fill:url(#XMLID_25_)"
id="path77"
d="M 97.145,47.856 L 95.918,73.85 L 95.851,75.04 C 97.113,75.751 98.35,76.383 99.665,76.908 C 99.682,76.914 99.716,76.902 99.731,76.908 C 103.661,78.47 107.824,79.181 111.963,79.148 C 112.654,79.142 113.357,79.125 114.047,79.075 C 115.434,78.979 116.849,78.821 118.221,78.55 C 119.591,78.29 120.912,77.936 122.25,77.506 C 124.262,76.863 126.252,76.045 128.147,75.04 L 128.075,73.85 L 126.926,47.857 L 97.145,47.857 L 97.145,47.856 z" />
</g><path
style="fill:url(#linearGradient2729);fill-opacity:1"
d="M 61.599815,40.25 L 98.148,40.25 C 99.24263,40.25 100.12576,41.128625 100.12576,42.21425 L 100.12576,78.789125 C 100.12576,79.87475 99.24263,80.75 98.148,80.75 L 61.599815,80.75 C 60.511931,80.75 59.625423,79.873625 59.625423,78.789125 L 59.625423,42.21425 C 59.625423,41.128625 60.511931,40.25 61.599815,40.25 z"
id="rect3935" /><g
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
nodetypes="cccsssssscccc"
id="path3937">
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.6403,-8.5e-3,-7.7e-3,-0.6279,-245.3333,-561.7972)"
r="40.038898"
cy="-1021.9087"
cx="486.17969"
id="XMLID_26_">
<stop
id="stop87"
style="stop-color:#E8E8E8"
offset="0" />
<stop
id="stop89"
style="stop-color:#FFFFFF"
offset="1" />
</radialGradient>
<path
style="fill:url(#XMLID_26_)"
id="path91"
d="M 60.145,47.856 L 58.923,73.85 L 58.852,75.04 C 60.112,75.751 61.347,76.383 62.663,76.908 C 62.684,76.914 62.715,76.902 62.735,76.908 C 66.662,78.47 70.824,79.181 74.964,79.148 C 75.654,79.142 76.357,79.125 77.048,79.075 C 78.441,78.979 79.856,78.821 81.227,78.55 C 82.597,78.29 83.913,77.936 85.25,77.506 C 87.263,76.863 89.258,76.045 91.153,75.04 L 91.076,73.85 L 89.926,47.857 L 60.145,47.857 L 60.145,47.856 z" />
</g><path
style="fill:url(#linearGradient2731)"
d="M 19.971092,40.25 L 56.529397,40.25 C 57.617282,40.25 58.500414,41.128625 58.500414,42.21425 L 58.500414,78.789125 C 58.500414,79.87475 57.617282,80.75 56.529397,80.75 L 19.971092,80.75 C 18.883208,80.75 18.000075,79.873625 18.000075,78.789125 L 18.000075,42.21425 C 18.000075,41.128625 18.883208,40.25 19.971092,40.25 z"
id="rect3941" /><g
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
nodetypes="cccsssssscccc"
id="path3943"
style="filter:url(#filter3372)">
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.6404,-8.5e-3,-7.7e-3,-0.6279,-252.0475,-561.7972)"
r="40.039001"
cy="-1021.27"
cx="438.85059"
id="XMLID_27_">
<stop
id="stop101"
style="stop-color:#E8E8E8"
offset="0" />
<stop
id="stop103"
style="stop-color:#FFFFFF"
offset="0.5858" />
</radialGradient>
<path
style="fill:url(#XMLID_27_)"
id="path105"
d="M 23.146,47.856 L 21.921,73.85 L 21.849,75.04 C 23.11,75.751 24.348,76.383 25.663,76.908 C 25.682,76.914 25.715,76.902 25.734,76.908 C 29.662,78.47 33.821,79.181 37.965,79.148 C 38.658,79.142 39.358,79.125 40.049,79.075 C 41.436,78.979 42.851,78.821 44.222,78.55 C 45.593,78.29 46.911,77.936 48.252,77.506 C 50.262,76.863 52.257,76.045 54.15,75.04 L 54.078,73.85 L 52.926,47.857 L 23.146,47.857 L 23.146,47.856 z" />
</g><g
style="filter:url(#filter3460)"
id="g109"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 22.213,81.018 L 23.666,76.231 C 23.666,76.231 23.743,74.036 21.849,75.041 L 21.921,76.231 L 20.291,81.018 C 20.605,81.49 21.142,81.8 21.752,81.8 L 23.515,81.8 C 22.971,81.8 22.493,81.489 22.213,81.018 z"
id="path111"
style="fill:#ffffff" />
</g><g
style="filter:url(#filter3464)"
id="g113"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 54.079,76.23 L 54.151,75.04 C 52.89,74.329 52.333,76.23 52.333,76.23 L 53.779,81.033 C 53.498,81.496 53.023,81.8 52.485,81.8 L 54.249,81.8 C 54.853,81.8 55.386,81.496 55.701,81.033 L 54.079,76.23 z"
id="path115"
style="fill:#ffffff" />
</g><path
d="M 24.1,48.856 C 23.693,57.45 52.262,55.434 51.971,48.856 C 42.68,48.856 33.39,48.856 24.1,48.856"
id="path122"
style="fill:url(#linearGradient2979);fill-opacity:1;filter:url(#filter3500)"
transform="matrix(1.1250094,0,0,1.7718407,-4.5001124,-41.815049)" /><g
style="filter:url(#filter3468)"
id="g126"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 59.513,81.018 L 60.966,76.231 C 60.966,76.231 61.043,74.036 59.149,75.041 L 59.221,76.231 L 57.591,81.018 C 57.905,81.49 58.442,81.8 59.052,81.8 L 60.815,81.8 C 60.271,81.8 59.792,81.489 59.513,81.018 z"
id="path128"
style="fill:#ffffff" />
</g><g
style="filter:url(#filter3472)"
id="g130"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 91.379,76.23 L 91.451,75.04 C 90.189,74.329 89.633,76.23 89.633,76.23 L 91.078,81.033 C 90.797,81.496 90.322,81.8 89.784,81.8 L 91.548,81.8 C 92.152,81.8 92.685,81.496 93,81.033 L 91.379,76.23 z"
id="path132"
style="fill:#ffffff" />
</g><path
d="M 61.399,48.856 C 60.992,57.45 89.56,55.434 89.269,48.856 C 79.98,48.856 70.689,48.856 61.399,48.856"
id="path139"
style="fill:url(#linearGradient2981);filter:url(#filter3496)"
transform="matrix(1.1250094,0,0,1.7718407,-4.5001124,-41.815049)" /><g
id="g143"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 15.921,43.018 L 17.374,38.231 C 17.374,38.231 17.451,36.036 15.557,37.041 L 15.629,38.231 L 14,43.018 C 14.314,43.49 14.851,43.8 15.461,43.8 L 17.224,43.8 C 16.68,43.8 16.201,43.489 15.921,43.018 z"
id="path145"
style="fill:#ffffff;filter:url(#filter3424)" />
</g><path
id="path149"
d="M 47.788,38.23 L 47.86,37.04 C 46.598,36.329 46.042,38.23 46.042,38.23 L 47.487,43.033 C 47.206,43.496 46.731,43.8 46.193,43.8 L 47.957,43.8 C 48.561,43.8 49.094,43.496 49.409,43.033 L 47.788,38.23 z"
style="fill:#ffffff;filter:url(#filter3416)"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)" /><path
d="M 17.808,10.856 C 17.401,19.45 45.969,17.434 45.678,10.856 C 36.389,10.856 27.098,10.856 17.808,10.856"
id="path156"
style="fill:url(#linearGradient3380);fill-opacity:1;filter:url(#filter3504)"
transform="matrix(1.1250094,0,0,1.7718407,-4.5001124,-17.235103)" /><g
style="filter:url(#filter3444)"
id="g160"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 53.513,43.018 L 54.966,38.231 C 54.966,38.231 55.043,36.036 53.149,37.041 L 53.221,38.231 L 51.591,43.018 C 51.905,43.49 52.442,43.8 53.052,43.8 L 54.815,43.8 C 54.271,43.8 53.792,43.489 53.513,43.018 z"
id="path162"
style="fill:#ffffff" />
</g><g
style="filter:url(#filter3448)"
id="g164"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 85.379,38.23 L 85.451,37.04 C 84.189,36.329 83.633,38.23 83.633,38.23 L 85.078,43.033 C 84.797,43.496 84.322,43.8 83.784,43.8 L 85.548,43.8 C 86.152,43.8 86.685,43.496 87,43.033 L 85.379,38.23 z"
id="path166"
style="fill:#ffffff" />
</g><path
d="M 55.399,10.856 C 54.992,19.45 83.56,17.434 83.269,10.856 C 73.98,10.856 64.689,10.856 55.399,10.856"
id="path173"
style="fill:url(#linearGradient2945);fill-opacity:1;filter:url(#filter3508)"
transform="matrix(1.1250094,0,0,1.7718407,-4.5001124,-17.235103)" /><g
style="filter:url(#filter3452)"
id="g177"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 90.513,43.018 L 91.966,38.231 C 91.966,38.231 92.043,36.036 90.149,37.041 L 90.221,38.231 L 88.592,43.018 C 88.905,43.49 89.442,43.8 90.053,43.8 L 91.815,43.8 C 91.271,43.8 90.793,43.489 90.513,43.018 z"
id="path179"
style="fill:#ffffff" />
</g><g
style="filter:url(#filter3456)"
id="g181"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 122.379,38.23 L 122.451,37.04 C 121.189,36.329 120.633,38.23 120.633,38.23 L 122.078,43.033 C 121.797,43.496 121.322,43.8 120.784,43.8 L 122.548,43.8 C 123.152,43.8 123.685,43.496 124,43.033 L 122.379,38.23 z"
id="path183"
style="fill:#ffffff" />
</g><path
d="M 92.399,10.856 C 91.992,19.45 120.56,17.434 120.269,10.856 C 110.98,10.856 101.689,10.856 92.399,10.856"
id="path190"
style="fill:url(#linearGradient3378);fill-opacity:1;filter:url(#filter3512)"
transform="matrix(1.1250094,0,0,1.7718407,-4.5001124,-17.235103)" /><g
style="filter:url(#filter3476)"
id="g194"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 96.513,81.018 L 97.966,76.231 C 97.966,76.231 98.043,74.036 96.149,75.041 L 96.221,76.231 L 94.592,81.018 C 94.905,81.49 95.442,81.8 96.053,81.8 L 97.815,81.8 C 97.271,81.8 96.793,81.489 96.513,81.018 z"
id="path196"
style="fill:#ffffff" />
</g><g
style="filter:url(#filter3484)"
id="g198"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 128.379,76.23 L 128.451,75.04 C 127.189,74.329 126.633,76.23 126.633,76.23 L 128.078,81.033 C 127.797,81.496 127.322,81.8 126.784,81.8 L 128.548,81.8 C 129.152,81.8 129.685,81.496 130,81.033 L 128.379,76.23 z"
id="path200"
style="fill:#ffffff" />
</g><path
d="M 98.399,48.856 C 97.992,57.45 126.56,55.434 126.269,48.856 C 116.98,48.856 107.689,48.856 98.399,48.856"
id="path207"
style="fill:url(#linearGradient2983);filter:url(#filter3492)"
transform="matrix(1.1250094,0,0,1.7718407,-4.5001124,-41.815049)" /><path
style="fill:#323232"
id="path209"
d="M 21.99503,12.125 L 19.778139,23.842759 C 19.61869,24.635599 19.540614,25.268992 19.540614,25.848502 C 19.540614,27.088895 20.172912,27.749779 21.150501,27.749779 C 22.470079,27.749779 23.261827,26.799689 23.788558,23.974716 L 26.058233,12.125 L 28.723782,12.125 L 26.533281,23.632728 C 25.715142,27.960908 24.079966,30.125 20.832702,30.125 C 18.27382,30.125 16.875066,28.645985 16.875066,26.112415 C 16.875066,25.399849 16.981732,24.502542 17.166473,23.47328 L 19.330582,12.125 L 21.99503,12.125 L 21.99503,12.125 z" /><path
style="fill:#323232"
id="path211"
d="M 64.643632,12.125 L 61.198712,30.125 L 58.500414,30.125 L 61.918629,12.125 L 64.643632,12.125 L 64.643632,12.125 z" /><path
style="fill:#323232"
id="path213"
d="M 111.34148,17.759442 C 111.34148,21.280968 110.11546,26.238278 107.82102,28.532705 C 106.7515,29.602206 105.57766,30.125 104.19402,30.125 C 100.56813,30.125 100.12576,26.368704 100.12576,24.464472 C 100.12576,20.995117 101.35178,16.037807 103.77666,13.664038 C 104.79508,12.672793 105.99394,12.125 107.35038,12.125 C 110.95129,12.125 111.34148,15.62044 111.34148,17.759442 z M 105.60374,15.124819 C 103.82883,16.899712 102.83867,21.985274 102.83867,24.593812 C 102.83867,25.793739 102.94301,27.802314 104.58532,27.802314 C 105.05486,27.802314 105.4983,27.567546 105.91676,27.15018 C 107.76775,25.270946 108.60358,19.585419 108.60358,17.629015 C 108.60358,16.089978 108.47315,14.446599 106.98627,14.446599 C 106.48956,14.447686 106.0211,14.681367 105.60374,15.124819 z" /><path
style="fill:#323232"
id="path215"
d="M 30.011882,54.875 L 32.678531,54.875 L 30.223015,67.727587 C 29.431267,71.819347 27.794991,72.875 25.182225,72.875 C 24.522435,72.875 23.91543,72.768335 23.625122,72.663868 L 24.073779,70.288648 C 24.443261,70.394213 24.785251,70.420605 25.287791,70.420605 C 26.450121,70.420605 27.161593,69.681647 27.531075,67.728687 L 30.011882,54.875 z" /><path
style="fill:#323232"
id="path217"
d="M 68.668474,54.876125 L 71.338789,54.876125 L 69.683194,63.422176 L 69.763303,63.422176 C 70.350773,62.407464 70.804726,61.659781 71.285383,60.885397 L 75.156227,54.877238 L 78.146981,54.877238 L 72.512615,62.72901 L 74.782383,72.876125 L 71.978553,72.876125 L 70.428657,64.732844 L 69.148018,66.387312 L 67.895195,72.875012 L 65.25047,72.875012 L 68.668474,54.876125 z" /><path
style="fill:#323232"
id="path219"
d="M 110.29425,54.873875 L 113.0183,54.873875 L 110.02717,70.523727 L 114.67522,70.523727 L 114.22121,72.873875 L 106.87582,72.873875 L 110.29425,54.873875 z" /></g>
</svg>

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -30,8 +30,14 @@ class CnetNews(BasicNewsRecipe):
remove_tags = [
dict(name='div', attrs={'id':'tweetmemeAndFacebook'})
,dict(name='ul', attrs={'class':'contentTools'})
,dict(name='aside', attrs={'id':'filed'})
,dict(name='div', attrs={'class':'postLinks'})
,dict(name='span', attrs={'class':'shareButton'})
,dict(name='span', attrs={'class':'printButton'})
,dict(name='span', attrs={'class':'emailButton'})
,dict(name='div', attrs={'class':'editorBio'})
]
keep_only_tags = dict(name='div', attrs={'class':'txtWrap'})
keep_only_tags = dict(name='div', attrs={'class':'post'})
feeds = [(u'News', u'http://news.cnet.com/2547-1_3-0-20.xml')]

View File

@ -9,7 +9,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag, NavigableString
from collections import OrderedDict
import time, re
import re
class Economist(BasicNewsRecipe):
@ -31,42 +31,33 @@ class Economist(BasicNewsRecipe):
{'class': lambda x: x and 'share-links-header' in x},
]
keep_only_tags = [dict(id='ec-article-body')]
needs_subscription = False
no_stylesheets = True
preprocess_regexps = [(re.compile('</html>.*', re.DOTALL),
lambda x:'</html>')]
# economist.com has started throttling after about 60% of the total has
# downloaded with connection reset by peer (104) errors.
delay = 1
needs_subscription = False
'''
def get_browser(self):
br = BasicNewsRecipe.get_browser()
br.open('http://www.economist.com')
req = mechanize.Request(
'http://www.economist.com/members/members.cfm?act=exec_login',
headers = {
'Referer':'http://www.economist.com/',
},
data=urllib.urlencode({
'logging_in' : 'Y',
'returnURL' : '/',
'email_address': self.username,
'fakepword' : 'Password',
'pword' : self.password,
'x' : '0',
'y' : '0',
}))
br.open(req).read()
if self.username and self.password:
br.open('http://www.economist.com/user/login')
br.select_form(nr=1)
br['name'] = self.username
br['pass'] = self.password
res = br.submit()
raw = res.read()
if '>Log out<' not in raw:
raise ValueError('Failed to login to economist.com. '
'Check your username and password.')
return br
'''
def parse_index(self):
try:
return self.economist_parse_index()
except:
raise
self.log.warn(
'Initial attempt to parse index failed, retrying in 30 seconds')
time.sleep(30)
return self.economist_parse_index()
return self.economist_parse_index()
def economist_parse_index(self):
soup = self.index_to_soup(self.INDEX)

View File

@ -36,27 +36,10 @@ class Economist(BasicNewsRecipe):
preprocess_regexps = [(re.compile('</html>.*', re.DOTALL),
lambda x:'</html>')]
'''
def get_browser(self):
br = BasicNewsRecipe.get_browser()
br.open('http://www.economist.com')
req = mechanize.Request(
'http://www.economist.com/members/members.cfm?act=exec_login',
headers = {
'Referer':'http://www.economist.com/',
},
data=urllib.urlencode({
'logging_in' : 'Y',
'returnURL' : '/',
'email_address': self.username,
'fakepword' : 'Password',
'pword' : self.password,
'x' : '0',
'y' : '0',
}))
br.open(req).read()
return br
'''
# economist.com has started throttling after about 60% of the total has
# downloaded with connection reset by peer (104) errors.
delay = 1
def parse_index(self):
try:

View File

@ -18,7 +18,7 @@ class ElMundo(BasicNewsRecipe):
no_stylesheets = True
use_embedded_content = False
encoding = 'iso8859_15'
language = 'es_ES'
language = 'es'
masthead_url = 'http://estaticos03.elmundo.es/elmundo/iconos/v4.x/v4.01/bg_h1.png'
publication_type = 'newspaper'
extra_css = """

View File

@ -0,0 +1,28 @@
from calibre.web.feeds.news import BasicNewsRecipe
class Escrevinhador(BasicNewsRecipe):
title = 'Blog Escrevinhador'
__author__ = 'Diniz Bortolotto'
description = 'Posts do Blog Escrevinhador'
publisher = 'Rodrigo Viana'
oldest_article = 5
max_articles_per_feed = 20
category = 'news, politics, Brazil'
language = 'pt_BR'
publication_type = 'news and politics portal'
use_embedded_content = False
no_stylesheets = True
remove_javascript = True
feeds = [(u'Blog Escrevinhador', u'http://www.rodrigovianna.com.br/feed')]
reverse_article_order = True
remove_tags_after = [dict(name='div', attrs={'class':'text'})]
remove_tags = [
dict(id='header'),
dict(name='p', attrs={'class':'tags'}),
dict(name='div', attrs={'class':'sociable'})
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

View File

@ -1,70 +1,86 @@
__license__ = 'GPL v3'
__copyright__ = '2011, Darko Miletic <darko.miletic at gmail.com>'
'''
www.independent.co.uk
'''
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup
class TheIndependent(BasicNewsRecipe):
title = u'The Independent'
language = 'en_GB'
__author__ = 'Krittika Goyal'
oldest_article = 1 #days
max_articles_per_feed = 30
encoding = 'latin1'
title = 'The Independent'
__author__ = 'Darko Miletic'
description = 'Independent News - Breaking news, comment and features from The Independent newspaper'
publisher = 'The Independent'
category = 'news, politics, UK'
oldest_article = 2
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'cp1252'
use_embedded_content = False
language = 'en_GB'
remove_empty_feeds = True
publication_type = 'newspaper'
masthead_url = 'http://www.independent.co.uk/independent.co.uk/images/logo-london.png'
extra_css = """
h1{font-family: Georgia,serif }
body{font-family: Verdana,Arial,Helvetica,sans-serif}
img{margin-bottom: 0.4em; display:block}
.info,.caption,.credits{font-size: x-small}
"""
no_stylesheets = True
#remove_tags_before = dict(name='h1', attrs={'class':'heading'})
#remove_tags_after = dict(name='td', attrs={'class':'newptool1'})
remove_tags = [
dict(name='iframe'),
dict(name='div', attrs={'class':'related-articles'}),
dict(name='div', attrs={'id':['qrformdiv', 'inSection', 'alpha-inner']}),
dict(name='ul', attrs={'class':'article-tools'}),
dict(name='ul', attrs={'class':'articleTools'}),
]
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
feeds = [
('UK',
'http://www.independent.co.uk/news/uk/rss'),
('World',
'http://www.independent.co.uk/news/world/rss'),
('Business',
'http://www.independent.co.uk/news/business/rss'),
('People',
'http://www.independent.co.uk/news/people/rss'),
('Science',
'http://www.independent.co.uk/news/science/rss'),
('Media',
'http://www.independent.co.uk/news/media/rss'),
('Education',
'http://www.independent.co.uk/news/education/rss'),
('Obituaries',
'http://www.independent.co.uk/news/obituaries/rss'),
remove_tags =[
dict(name=['meta','link','object','embed','iframe','base','style'])
,dict(attrs={'class':['related-articles','share','googleCols','article-tools','paging','googleArt']})
,dict(attrs={'id':['newsVideoPlayer','yahoobook','google-intext']})
]
keep_only_tags =[dict(attrs={'id':'article'})]
remove_attributes=['lang','onclick','width','xmlns:fb']
('Opinion',
'http://www.independent.co.uk/opinion/rss'),
('Environment',
'http://www.independent.co.uk/environment/rss'),
feeds = [
(u'UK' , u'http://www.independent.co.uk/news/uk/rss' )
,(u'World' , u'http://www.independent.co.uk/news/world/rss' )
,(u'Business' , u'http://www.independent.co.uk/news/business/rss' )
,(u'People' , u'http://www.independent.co.uk/news/people/rss' )
,(u'Science' , u'http://www.independent.co.uk/news/science/rss' )
,(u'Media' , u'http://www.independent.co.uk/news/media/rss' )
,(u'Education' , u'http://www.independent.co.uk/news/education/rss' )
,(u'Leading Articles' , u'http://www.independent.co.uk/opinion/leading-articles/rss')
,(u'Comentators' , u'http://www.independent.co.uk/opinion/commentators/rss' )
,(u'Columnists' , u'http://www.independent.co.uk/opinion/columnists/rss' )
,(u'Letters' , u'http://www.independent.co.uk/opinion/letters/rss' )
,(u'Big Question' , u'http://www.independent.co.uk/extras/big-question/rss' )
,(u'Sport' , u'http://www.independent.co.uk/sport/rss' )
,(u'Life&Style' , u'http://www.independent.co.uk/life-style/rss' )
,(u'Arts&Entertainment' , u'http://www.independent.co.uk/arts-entertainment/rss' )
,(u'Travel' , u'http://www.independent.co.uk/travel/rss' )
,(u'Money' , u'http://www.independent.co.uk/money/rss' )
]
('Sport',
'http://www.independent.co.uk/sport/rss'),
('Life and Style',
'http://www.independent.co.uk/life-style/rss'),
('Arts and Entertainment',
'http://www.independent.co.uk/arts-entertainment/rss'),
('Travel',
'http://www.independent.co.uk/travel/rss'),
('Money',
'http://www.independent.co.uk/money/rss'),
]
def get_article_url(self, article):
return article.get('guid', None)
def preprocess_html(self, soup):
story = soup.find(name='div', attrs={'id':'mainColumn'})
#td = heading.findParent(name='td')
#td.extract()
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
body = soup.find(name='body')
body.insert(0, story)
return soup
for item in soup.body.findAll(style=True):
del item['style']
for item in soup.body.findAll(['author','preform']):
item.name='span'
for item in soup.body.findAll('img'):
if not item.has_key('alt'):
item['alt'] = 'image'
for item in soup.body.findAll('div', attrs={'class':['clear-o','body','photoCaption']}):
item.name = 'p'
for item in soup.body.findAll('div'):
if not item.attrs and not item.contents:
item.extract()
soup2 = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
soup2.body.replaceWith(soup.body)
return soup2

View File

@ -1,55 +1,46 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>'
__copyright__ = '2011, Oscar Megia Lopez'
'''
juventudrebelde.cu
'''
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class JuventudRebelde(BasicNewsRecipe):
title = u'Juventud Rebelde'
__author__ = 'Oscar Megia Lopez'
description = 'Periodico cubano'
oldest_article = 30
max_articles_per_feed = 100
no_stylesheets = True
#delay = 1
use_embedded_content = False
encoding = 'utf8'
publisher = 'Juventud Rebelde'
category = 'Noticias'
language = 'es'
publication_type = 'Periodico'
extra_css = ' body{ font-family: Verdana,Helvetica,Arial,sans-serif } .title{font-weight: bold} .read{display: block; padding: 0; border: 1px solid; width: 40%; font-size: small} .story-feature h2{text-align: center; text-transform: uppercase} '
preprocess_regexps = [(re.compile(r'<!--.*?-->', re.DOTALL), lambda m: '')]
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
,'linearize_tables': True
}
class Juventudrebelde(BasicNewsRecipe):
title = 'Juventud Rebelde'
__author__ = 'Darko Miletic'
description = 'Diario de la Juventud Cubana'
publisher = 'Juventud rebelde'
category = 'news, politics, Cuba'
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'cp1252'
language = 'es_CU'
cover_url = strftime('http://www.juventudrebelde.cu/UserFiles/File/impreso/iportada-%Y-%m-%d.jpg')
remove_javascript = True
html2lrf_options = [
'--comment' , description
, '--category' , category
, '--publisher', publisher
, '--ignore-tables'
keep_only_tags = [
dict(name='div', attrs={'class':['title']})
,dict(attrs={'class':['read']})
,dict(attrs={'class':['author']})
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True'
remove_tags = [
dict(name='div', attrs={'class':['share']}),
]
keep_only_tags = [dict(name='div', attrs={'id':'noticia'})]
remove_attributes = ['width','height']
feeds = [
(u'Generales', u'http://www.juventudrebelde.cu/rss/generales.php' )
,(u'Cuba', u'http://www.juventudrebelde.cu/rss/generales.php?seccion=cuba' )
,(u'Internacionales', u'http://www.juventudrebelde.cu/rss/generales.php?seccion=internacionales' )
,(u'Opinion', u'http://www.juventudrebelde.cu/rss/generales.php?seccion=opinion' )
,(u'Cultura', u'http://www.juventudrebelde.cu/rss/generales.php?seccion=cultura' )
,(u'Deportes', u'http://www.juventudrebelde.cu/rss/generales.php?seccion=deportes' )
,(u'Lectura', u'http://www.juventudrebelde.cu/rss/generales.php?seccion=lectura' )
]
def preprocess_html(self, soup):
mtag = '<meta http-equiv="Content-Language" content="es-CU"/>'
soup.head.insert(0,mtag)
for item in soup.findAll(style=True):
del item['style']
return soup
feeds = [(u'Generales', u'http://www.juventudrebelde.cu/get/rss/grupo/generales/'), (u'Internacionales', u'http://www.psychologytoday.com/blog/romance-redux/feed'), (u'Ciencia y Tecnica', u'http://www.juventudrebelde.cu/get/rss/noticias/ciencia-tecnica/'), (u'Opini\xf3n', u'http://www.juventudrebelde.cu/get/rss/noticias/opinion/'), (u'Cuba', u'http://www.juventudrebelde.cu/get/rss/noticias/cuba/'), (u'Cultura', u'http://www.juventudrebelde.cu/get/rss/noticias/cultura/'), (u'Deportes', u'http://www.juventudrebelde.cu/get/rss/noticias/deportes')]

View File

@ -26,7 +26,7 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
use_embedded_content = False
encoding = 'utf-8'
language = 'es_ES'
language = 'es'
timefmt = '[%a, %d %b, %Y]'
keep_only_tags = [

View File

@ -17,18 +17,15 @@ class Lanacion(BasicNewsRecipe):
use_embedded_content = False
no_stylesheets = True
language = 'es_AR'
delay = 14
publication_type = 'newspaper'
remove_empty_feeds = True
masthead_url = 'http://www.lanacion.com.ar/_ui/desktop/imgs/layout/logos/ln341x47.gif'
masthead_url = 'http://www.lanacion.com.ar/_ui/desktop/imgs/layout/logos/ln-home.gif'
extra_css = """
h1{font-family: Georgia,serif}
h2{color: #626262; font-weight: normal; font-size: 1.1em}
h1{font-family: TheSans,Arial,sans-serif}
body{font-family: Arial,sans-serif}
img{margin-top: 0.5em; margin-bottom: 0.2em; display: block}
.notaFecha{color: #808080; font-size: small}
.notaEpigrafe{font-size: x-small}
.topNota h1{font-family: Arial,sans-serif}
img{display: block}
.firma,.fecha{font-size: small}
.epigrafe-columna{font-size: x-small}
"""
@ -39,21 +36,13 @@ class Lanacion(BasicNewsRecipe):
, 'language' : language
}
keep_only_tags = [
dict(name='div', attrs={'class':['topNota','itemHeader','nota','itemBody']})
,dict(name='div', attrs={'id':'content'})
]
remove_tags = [
dict(name='div' , attrs={'class':'notaComentario floatFix noprint' })
,dict(name='ul' , attrs={'class':['cajaHerramientas cajaTop noprint','herramientas noprint']})
,dict(name='div' , attrs={'class':['titulosMultimedia','herramientas noprint','cajaHerramientas noprint','cajaHerramientas floatFix'] })
,dict(attrs={'class':['izquierda','espacio17','espacio10','espacio20','floatFix ultimasNoticias','relacionadas','titulosMultimedia','derecha','techo color','encuesta','izquierda compartir','floatFix','videoCentro']})
,dict(name=['iframe','embed','object','form','base','hr','meta','link','input'])
dict(name=['iframe','embed','object','meta','link'])
,dict(attrs={'id':['herramientas','relacionadas','ampliar']})
]
remove_tags_after = dict(attrs={'class':['tags','nota-destacado']})
remove_attributes = ['height','width','visible','onclick','data-count','name']
remove_tags_before = dict(attrs={'id':'encabezado'})
remove_tags_after = dict(attrs={'id':'relacionadas'})
feeds = [
(u'Politica' , u'http://servicios.lanacion.com.ar/herramientas/rss/categoria_id=30' )
@ -91,6 +80,15 @@ class Lanacion(BasicNewsRecipe):
if link.rfind('galeria=') > 0:
return None
return link
def get_cover_url(self):
soup = self.index_to_soup('http://www.lanacion.com.ar/edicion-impresa')
atap = soup.find(attrs={'class':'tapa'})
if atap:
li = atap.find('img')
if li:
return li['src']
return None
def preprocess_html(self, soup):
for item in soup.findAll(style=True):

View File

@ -23,7 +23,7 @@ class LaTribuna(BasicNewsRecipe):
encoding = 'utf-8'
language = 'es_HN'
lang = 'es-HN'
lang = 'es_HN'
direction = 'ltr'
html2lrf_options = [

View File

@ -19,7 +19,7 @@ class Marca(BasicNewsRecipe):
use_embedded_content = False
delay = 1
encoding = 'iso-8859-15'
language = 'es_ES'
language = 'es'
publication_type = 'newsportal'
masthead_url = 'http://estaticos.marca.com/deporte/img/v3.0/img_marca-com.png'
extra_css = """

View File

@ -1,91 +1,135 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2010, matek09, matek09@gmail.com'
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ptempfile import PersistentTemporaryFile
import datetime
class Newsweek(BasicNewsRecipe):
FIND_LAST_FULL_ISSUE = True
EDITION = '0'
EXCLUDE_LOCKED = True
LOCKED_ICO = 'http://www.newsweek.pl/bins/media/static/newsweek/img/ico_locked.gif'
DATE = None
YEAR = datetime.datetime.now().year
title = u'Newsweek Polska'
__author__ = 'matek09'
description = 'Weekly magazine'
encoding = 'utf-8'
no_stylesheets = True
language = 'pl'
remove_javascript = True
keep_only_tags =[]
keep_only_tags.append(dict(name = 'div', attrs = {'class' : 'article'}))
temp_files = []
articles_are_obfuscated = True
remove_tags =[]
remove_tags.append(dict(name = 'div', attrs = {'class' : 'copy'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'url'}))
extra_css = '''
.body {font-size: small}
.author {font-size: x-small}
.lead {font-size: x-small}
.title{font-size: x-large; font-weight: bold}
'''
def print_version(self, url):
return url.replace("http://www.newsweek.pl/artykuly/wydanie/" + str(self.EDITION), "http://www.newsweek.pl/artykuly") + '/print'
def is_locked(self, a):
if a.findNext('img')['src'] == 'http://www.newsweek.pl/bins/media/static/newsweek/img/ico_locked.gif':
return True
else:
return False
def get_obfuscated_article(self, url):
br = self.get_browser()
br.open(url)
source = br.response().read()
page = self.index_to_soup(source)
main_section = page.find(id='mainSection')
title = main_section.find('h1')
info = main_section.find('ul', attrs={'class' : 'articleInfo'})
authors = info.find('li').find('h4')
article = main_section.find('div', attrs={'id' : 'article'})
html = unicode(title) + unicode(authors) + unicode(article)
next = main_section.find('li', attrs={'class' : 'next'})
while next:
url = next.find('a')['href']
br.open(url)
source = br.response().read()
page = self.index_to_soup(source)
main_section = page.find(id='mainSection')
article = main_section.find('div', attrs={'id' : 'article'})
aside = article.find(id='articleAside')
if aside is not None:
aside.extract()
html = html + unicode(article)
next = main_section.find('li', attrs={'class' : 'next'})
self.temp_files.append(PersistentTemporaryFile('_temparse.html'))
self.temp_files[-1].write(html)
self.temp_files[-1].close()
return self.temp_files[-1].name
def is_full(self, issue_soup):
if len(issue_soup.findAll('img', attrs={'src' : 'http://www.newsweek.pl/bins/media/static/newsweek/img/ico_locked.gif'})) > 1:
return False
else:
return True
def find_last_full_issue(self):
frame_url = 'http://www.newsweek.pl/Frames/IssueCover.aspx'
while True:
frame_soup = self.index_to_soup(frame_url)
self.EDITION = frame_soup.find('a', attrs={'target' : '_parent'})['href'].replace('/wydania/','')
main_section = issue_soup.find(id='mainSection')
next = main_section.find('li', attrs={'class' : 'next'})
if len(main_section.findAll(attrs={'class' : 'locked'})) > 1:
return False
elif next is None:
return True
else:
issue_soup = self.index_to_soup(next.find('a')['href'])
def find_last_full_issue(self, archive_url):
archive_soup = self.index_to_soup(archive_url)
select = archive_soup.find('select', attrs={'id' : 'paper_issue_select'})
for option in select.findAll(lambda tag: tag.name == 'option' and tag.has_key('value')):
self.EDITION = option['value'].replace('http://www.newsweek.pl/wydania/','')
issue_soup = self.index_to_soup('http://www.newsweek.pl/wydania/' + self.EDITION)
if self.is_full(issue_soup):
break
frame_url = 'http://www.newsweek.pl/Frames/' + frame_soup.find(lambda tag: tag.name == 'span' and not tag.attrs).a['href']
return
self.YEAR = self.YEAR - 1
self.find_last_full_issue(archive_url + ',' + str(self.YEAR))
def parse_index(self):
if self.FIND_LAST_FULL_ISSUE:
self.find_last_full_issue()
archive_url = 'http://www.newsweek.pl/wydania/archiwum'
self.find_last_full_issue(archive_url)
soup = self.index_to_soup('http://www.newsweek.pl/wydania/' + self.EDITION)
img = soup.find('img', id="ctl00_C1_PaperIsssueView_IssueImage", src=True)
self.DATE = self.tag_to_string(soup.find('span', attrs={'class' : 'data'}))
main_section = soup.find(id='mainSection')
img = main_section.find(lambda tag: tag.name == 'img' and tag.has_key('alt') and tag.has_key('title'))
self.cover_url = img['src']
feeds = []
parent = soup.find(id='content-left-big')
for txt in parent.findAll(attrs={'class':'txt_normal_red strong'}):
articles = list(self.find_articles(txt))
if len(articles) > 0:
section = self.tag_to_string(txt).capitalize()
feeds.append((section, articles))
articles = {}
sections = []
while True:
news_list = main_section.find('ul', attrs={'class' : 'newsList'})
for h2 in news_list.findAll('h2'):
article = self.create_article(h2)
category_div = h2.findNext('div', attrs={'class' : 'kategorie'})
section = self.tag_to_string(category_div)
if articles.has_key(section):
articles[section].append(article)
else:
articles[section] = [article]
sections.append(section)
next = main_section.find('li', attrs={'class' : 'next'})
if next is None:
break
soup = self.index_to_soup(next.find('a')['href'])
main_section = soup.find(id='mainSection')
for section in sections:
feeds.append((section, articles[section]))
return feeds
def find_articles(self, txt):
for a in txt.findAllNext( attrs={'class':['strong','hr']}):
if a.name in "div":
break
if (not self.FIND_LAST_FULL_ISSUE) & self.EXCLUDE_LOCKED & self.is_locked(a):
continue
yield {
'title' : self.tag_to_string(a),
'url' : 'http://www.newsweek.pl' + a['href'],
'date' : '',
'description' : ''
}
def create_article(self, h2):
article = {}
a = h2.find('a')
article['title'] = self.tag_to_string(a)
article['url'] = a['href']
article['date'] = self.DATE
desc = h2.findNext('p')
if desc is not None:
article['description'] = self.tag_to_string(desc)
else:
article['description'] = ''
return article

35
recipes/novinite.recipe Normal file
View File

@ -0,0 +1,35 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1308572538(BasicNewsRecipe):
title = u'Novinite.com'
__author__ = 'Martin Tsanchev'
description = 'Real time provider of the latest Bulgarian news in English'
category = 'Business, Politics, Society, Sports, Crime, Lifestyle, World, People'
language = 'en_BG'
encoding = 'utf-8'
oldest_article = 7
max_articles_per_feed = 10
keep_only_tags = [dict(name='div', attrs={'id':'content'})]
remove_tags = [dict(name='a', attrs={'class':'twitter-share-button'})]
remove_tags_after = dict(id='textsize')
no_stylesheets = True
feeds = [(u'Business', u'http://www.novinite.com/services/news_rdf.php?category_id=1'),
(u'Finance', u'http://www.novinite.com/services/news_rdf.php?category_id=15'),
(u'Energy', u'http://www.novinite.com/services/news_rdf.php?category_id=16'),
(u'Industry', u'http://www.novinite.com/services/news_rdf.php?category_id=17'),
(u'Properties', u'http://www.novinite.com/services/news_rdf.php?category_id=18'),
(u'Politics', u'http://www.novinite.com/services/news_rdf.php?category_id=2'),
(u'Diplomacy', u'http://www.novinite.com/services/news_rdf.php?category_id=20'),
(u'Defense', u'http://www.novinite.com/services/news_rdf.php?category_id=21'),
(u'Bulgaria in EU', u'http://www.novinite.com/services/news_rdf.php?category_id=22'),
(u'Domestic', u'http://www.novinite.com/services/news_rdf.php?category_id=23'),
(u'Society', u'http://www.novinite.com/services/news_rdf.php?category_id=3'),
(u'Environment', u'http://www.novinite.com/services/news_rdf.php?category_id=24'),
(u'Education', u'http://www.novinite.com/services/news_rdf.php?category_id=25'),
(u'Culture', u'http://www.novinite.com/services/news_rdf.php?category_id=26'),
(u'Archaeology', u'http://www.novinite.com/services/news_rdf.php?category_id=34'),
(u'Health', u'http://www.novinite.com/services/news_rdf.php?category_id=62'),
(u'Sports', u'http://www.novinite.com/services/news_rdf.php?category_id=4'),
(u'Crime', u'http://www.novinite.com/services/news_rdf.php?category_id=5'),
(u'Lifestyle', u'http://www.novinite.com/services/news_rdf.php?category_id=6'),
(u'World', u'http://www.novinite.com/services/news_rdf.php?category_id=30')]

View File

@ -0,0 +1,43 @@
__license__ = 'GPL v3'
__copyright__ = '2011, Oscar Megia Lopez'
'''
perezreverte.com
'''
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
class PerezReverte(BasicNewsRecipe):
title = u'Patente de Corso'
__author__ = 'Oscar Megia Lopez'
description = 'Arturo Perez Reverte'
oldest_article = 90
max_articles_per_feed = 100
no_stylesheets = True
#delay = 1
use_embedded_content = False
encoding = 'utf8'
publisher = 'Arturo Perez Reverte'
category = 'Articulo'
language = 'es'
publication_type = 'Magazine'
extra_css = ' body{ font-family: Verdana,Helvetica,Arial,sans-serif } .contentheading{font-weight: bold} .txt_articulo{display: block; padding: 0; border: 1px solid; width: 40%; font-size: small} .story-feature h2{text-align: center; text-transform: uppercase} '
preprocess_regexps = [(re.compile(r'<!--.*?-->', re.DOTALL), lambda m: '')]
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
,'linearize_tables': True
}
keep_only_tags = [
dict(name='h2', attrs={'class':['titular']}),
dict(name='p', attrs={'class':['fecha']}),
dict(name='div', attrs={'class':['bloqueTexto']})
]
remove_attributes = ['width','height']
feeds = [
('Patente de corso - Web oficial de Arturo Perez Reverte', 'http://www.perezreverte.com/rss/patentes-corso/')
]

View File

@ -1,12 +1,12 @@
from calibre.web.feeds.news import BasicNewsRecipe
class RzeczpospolitaRecipe(BasicNewsRecipe):
__license__ = 'GPL v3'
__license__ = 'GPL v3'
__author__ = u'kwetal and Tomasz Dlugosz'
language = 'pl'
version = 1
title = u'Rzeczpospolita OnLine'
title = u'Rzeczpospolita OnLine'
publisher = u'Presspublica Sp.'
category = u'News'
description = u'Newspaper'
@ -31,15 +31,19 @@ class RzeczpospolitaRecipe(BasicNewsRecipe):
feeds.append(u'http://www.rp.pl/rss/8.html')
keep_only_tags =[]
keep_only_tags.append(dict(name = 'div', attrs = {'id' : 'storyp'}))
keep_only_tags.append(dict(name = 'div', attrs = {'id' : 'story'}))
remove_tags =[]
remove_tags.append(dict(name = 'div', attrs = {'id' : 'adk_0'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'socialTools'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'articleToolBoxTop'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'clr'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'share_bottom'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'copyright_law'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'recommendations'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'editorPicks'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'articleCopyrightText'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'articleCopyrightButton'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'articleToolBoxBottom'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'more'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'editorPicks'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'addRecommendation'}))
extra_css = '''
body {font-family: verdana, arial, helvetica, geneva, sans-serif ;}
@ -62,3 +66,4 @@ class RzeczpospolitaRecipe(BasicNewsRecipe):
forget, sep, index = rest.rpartition(',')
return start + '/' + index + '?print=tak'

View File

@ -11,7 +11,7 @@
<link rel="stylesheet" type="text/css" href="{prefix}/static/browse/browse.css" />
<link type="text/css" href="{prefix}/static/jquery_ui/css/humanity-custom/jquery-ui-1.8.5.custom.css" rel="stylesheet" />
<link rel="stylesheet" type="text/css" href="{prefix}/static/jquery.multiselect.css" />
<link rel="apple-touch-icon" href="/static/calibre.png" />
<link rel="apple-touch-icon" href="{prefix}/static/calibre.png" />
<script type="text/javascript" src="{prefix}/static/jquery.js"></script>
<script type="text/javascript" src="{prefix}/static/jquery.corner.js"></script>

View File

@ -179,6 +179,9 @@ save_template_title_series_sorting = 'library_order'
# changed. Changes to this tweak won't have an effect until the book is modified
# in some way. If you enter an invalid pattern, it is silently ignored.
# To disable use the expression: '^$'
# This expression is designed for articles that are followed by spaces. If you
# also need to match articles that are followed by other characters, for example L'
# in French, use: r"^(A\s+|The\s+|An\s+|L')" instead.
# Default: '^(A|The|An)\s+'
title_sort_articles=r'^(A|The|An)\s+'

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -21,3 +21,5 @@ vipy.session.add_content_browser('.r', ',r', 'Recipe',
vipy.session.glob_based_iterator(os.path.join(project_dir, 'recipes', '*.recipe')),
vipy.session.regexp_based_matcher(r'title\s*=\s*(?P<title>.+)', 'title', recipe_title_callback))
EOFPY
nmap \log :enew<CR>:read ! bzr log -l 500 ../.. <CR>:e ../../Changelog.yaml<CR>:e constants.py<CR>

2169
setup/iso639.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -273,10 +273,9 @@ class GetTranslations(Translations):
class ISO639(Command):
description = 'Compile translations for ISO 639 codes'
XML = '/usr/lib/python2.7/site-packages/pycountry/databases/iso639.xml'
def run(self, opts):
src = self.XML
src = self.j(self.d(self.SRC), 'setup', 'iso639.xml')
if not os.path.exists(src):
raise Exception(src + ' does not exist')
dest = self.j(self.d(self.SRC), 'resources', 'localization',
@ -290,20 +289,27 @@ class ISO639(Command):
by_2 = {}
by_3b = {}
by_3t = {}
m2to3 = {}
m3to2 = {}
codes2, codes3t, codes3b = set([]), set([]), set([])
for x in root.xpath('//iso_639_entry'):
name = x.get('name')
two = x.get('iso_639_1_code', None)
threeb = x.get('iso_639_2B_code')
threet = x.get('iso_639_2T_code')
if two is not None:
by_2[two] = name
codes2.add(two)
by_3b[x.get('iso_639_2B_code')] = name
by_3t[x.get('iso_639_2T_code')] = name
m2to3[two] = threet
m3to2[threeb] = m3to2[threet] = two
by_3b[threeb] = name
by_3t[threet] = name
codes3b.add(x.get('iso_639_2B_code'))
codes3t.add(x.get('iso_639_2T_code'))
from cPickle import dump
x = {'by_2':by_2, 'by_3b':by_3b, 'by_3t':by_3t, 'codes2':codes2,
'codes3b':codes3b, 'codes3t':codes3t}
'codes3b':codes3b, 'codes3t':codes3t, '2to3':m2to3,
'3to2':m3to2}
dump(x, open(dest, 'wb'), -1)

View File

@ -4,7 +4,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = u'calibre'
numeric_version = (0, 8, 13)
numeric_version = (0, 8, 14)
__version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"

View File

@ -1029,7 +1029,7 @@ class TemplateFunctions(PreferencesPlugin):
category = 'Advanced'
gui_category = _('Advanced')
category_order = 5
name_order = 4
name_order = 5
config_widget = 'calibre.gui2.preferences.template_functions'
description = _('Create your own template functions')
@ -1092,6 +1092,17 @@ class Tweaks(PreferencesPlugin):
config_widget = 'calibre.gui2.preferences.tweaks'
description = _('Fine tune how calibre behaves in various contexts')
class Keyboard(PreferencesPlugin):
name = 'Keyboard'
icon = I('keyboard-prefs.png')
gui_name = _('Keyboard')
category = 'Advanced'
gui_category = _('Advanced')
category_order = 5
name_order = 4
config_widget = 'calibre.gui2.preferences.keyboard'
description = _('Customize the keyboard shortcuts used by calibre')
class Misc(PreferencesPlugin):
name = 'Misc'
icon = I('exec.png')
@ -1106,7 +1117,7 @@ class Misc(PreferencesPlugin):
plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions,
CommonOptions, OutputOptions, Adding, Saving, Sending, Plugboard,
Email, Server, Plugins, Tweaks, Misc, TemplateFunctions,
MetadataSources]
MetadataSources, Keyboard]
#}}}

View File

@ -48,7 +48,7 @@ class ANDROID(USBMS):
# Google
0x18d1 : {
0x0001 : [0x0223],
0x0001 : [0x0223, 0x9999],
0x4e11 : [0x0100, 0x226, 0x227],
0x4e12 : [0x0100, 0x226, 0x227],
0x4e21 : [0x0100, 0x226, 0x227],
@ -76,8 +76,11 @@ class ANDROID(USBMS):
0x413c : { 0xb007 : [0x0100, 0x0224, 0x0226]},
# LG
0x1004 : { 0x61cc : [0x100], 0x61ce : [0x100], 0x618e : [0x226,
0x9999] },
0x1004 : {
0x61cc : [0x100],
0x61ce : [0x100],
0x618e : [0x226, 0x9999, 0x100]
},
# Archos
0x0e79 : {

View File

@ -6,6 +6,7 @@ Created on 15 May 2010
import os
from calibre.devices.usbms.driver import USBMS, BookList
from calibre.ebooks import BOOK_EXTENSIONS
# This class is added to the standard device plugin chain, so that it can
# be configured. It has invalid vendor_id etc, so it will never match a
@ -16,8 +17,8 @@ class FOLDER_DEVICE_FOR_CONFIG(USBMS):
description = _('Use an arbitrary folder as a device.')
author = 'John Schember/Charles Haley'
supported_platforms = ['windows', 'osx', 'linux']
FORMATS = ['epub', 'fb2', 'mobi', 'azw', 'lrf', 'tcr', 'pmlz', 'lit',
'rtf', 'rb', 'pdf', 'oeb', 'txt', 'pdb', 'prc']
FORMATS = list(BOOK_EXTENSIONS)
VENDOR_ID = [0xffff]
PRODUCT_ID = [0xffff]
BCD = [0xffff]

View File

@ -35,7 +35,7 @@ class ISBNDB(Source):
options = (
Option('isbndb_key', 'string', None, _('IsbnDB key:'),
_('To use isbndb.com you have to sign up for a free account'
_('To use isbndb.com you have to sign up for a free account '
'at isbndb.com and get an access key.')),
)

View File

@ -97,6 +97,7 @@ gprefs.defaults['book_display_fields'] = [
('last_modified', False), ('size', False),
]
gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}'
gprefs.defaults['preserve_date_on_ctl'] = True
# }}}
@ -169,7 +170,9 @@ def _config(): # {{{
c.add_opt('scheduler_search_history', default=[],
help='Search history for the recipe scheduler')
c.add_opt('plugin_search_history', default=[],
help='Search history for the recipe scheduler')
help='Search history for the plugin preferences')
c.add_opt('shortcuts_search_history', default=[],
help='Search history for the keyboard preferences')
c.add_opt('worker_limit', default=6,
help=_(
'Maximum number of simultaneous conversion/news download jobs. '
@ -425,6 +428,8 @@ class FileIconProvider(QFileIconProvider):
'snb' : 'snb',
'djv' : 'djvu',
'djvu' : 'djvu',
'xps' : 'xps',
'oxps' : 'xps',
}
def __init__(self):

View File

@ -8,9 +8,13 @@ __docformat__ = 'restructuredtext en'
from functools import partial
from zipfile import ZipFile
from PyQt4.Qt import QToolButton, QAction, QIcon, QObject, QMenu
from PyQt4.Qt import (QToolButton, QAction, QIcon, QObject, QMenu,
QKeySequence)
from calibre import prints
from calibre.gui2 import Dispatcher
from calibre.gui2.keyboard import NameConflict
class InterfaceAction(QObject):
@ -108,7 +112,10 @@ class InterfaceAction(QObject):
@property
def unique_name(self):
return u'%s(%s)'%(self.__class__.__name__, self.name)
bn = self.__class__.__name__
if getattr(self.interface_action_base_plugin, 'name'):
bn = self.interface_action_base_plugin.name
return u'Interface Action: %s (%s)'%(bn, self.name)
def create_action(self, spec=None, attr='qaction'):
if spec is None:
@ -129,7 +136,6 @@ class InterfaceAction(QObject):
a.setToolTip(text)
a.setStatusTip(text)
a.setWhatsThis(text)
keys = ()
shortcut_action = action
desc = tooltip if tooltip else None
if attr == 'qaction':
@ -138,9 +144,22 @@ class InterfaceAction(QObject):
keys = ((shortcut,) if isinstance(shortcut, basestring) else
tuple(shortcut))
self.gui.keyboard.register_shortcut(self.unique_name + ' - ' + attr,
unicode(shortcut_action.text()), default_keys=keys,
action=shortcut_action, description=desc)
if spec[0] and not (attr=='qaction' and self.popup_type ==
QToolButton.InstantPopup):
try:
self.gui.keyboard.register_shortcut(self.unique_name + ' - ' + attr,
unicode(spec[0]), default_keys=keys,
action=shortcut_action, description=desc,
group=self.action_spec[0])
except NameConflict as e:
try:
prints(unicode(e))
except:
pass
shortcut_action.setShortcuts([QKeySequence(key,
QKeySequence.PortableText) for key in keys])
if attr is not None:
setattr(self, attr, action)
if attr == 'qaction' and self.action_add_menu:
@ -166,10 +185,11 @@ class InterfaceAction(QObject):
ac.setToolTip(description)
ac.setStatusTip(description)
ac.setWhatsThis(description)
if shortcut is not False:
self.gui.keyboard.register_shortcut(unique_name,
unicode(text), default_keys=keys,
action=ac, description=description)
action=ac, description=description, group=self.action_spec[0])
if triggered is not None:
ac.triggered.connect(triggered)
return ac

View File

@ -8,13 +8,14 @@ __docformat__ = 'restructuredtext en'
import os
from functools import partial
from PyQt4.Qt import QMenu, Qt, QInputDialog, QToolButton
from PyQt4.Qt import (QMenu, Qt, QInputDialog, QToolButton, QDialog,
QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QIcon, QSize)
from calibre import isbytestring
from calibre.constants import filesystem_encoding, iswindows
from calibre.utils.config import prefs
from calibre.gui2 import (gprefs, warning_dialog, Dispatcher, error_dialog,
question_dialog, info_dialog, open_local_file)
question_dialog, info_dialog, open_local_file, choose_dir)
from calibre.library.database2 import LibraryDatabase2
from calibre.gui2.actions import InterfaceAction
@ -76,16 +77,73 @@ class LibraryUsageStats(object): # {{{
self.write_stats()
# }}}
class MovedDialog(QDialog): # {{{
def __init__(self, stats, location, parent=None):
QDialog.__init__(self, parent)
self.setWindowTitle(_('No library found'))
self._l = l = QGridLayout(self)
self.setLayout(l)
self.stats, self.location = stats, location
loc = self.oldloc = location.replace('/', os.sep)
self.header = QLabel(_('No existing calibre library was found at %s. '
'If the library was moved, select its new location below. '
'Otherwise calibre will forget this library.')%loc)
self.header.setWordWrap(True)
ncols = 2
l.addWidget(self.header, 0, 0, 1, ncols)
self.cl = QLabel('<br><b>'+_('New location of this library:'))
l.addWidget(self.cl, 1, 0, 1, ncols)
self.loc = QLineEdit(loc, self)
l.addWidget(self.loc, 2, 0, 1, 1)
self.cd = QToolButton(self)
self.cd.setIcon(QIcon(I('document_open.png')))
self.cd.clicked.connect(self.choose_dir)
l.addWidget(self.cd, 2, 1, 1, 1)
self.bb = QDialogButtonBox(self)
b = self.bb.addButton(_('Library moved'), self.bb.AcceptRole)
b.setIcon(QIcon(I('ok.png')))
b = self.bb.addButton(_('Forget library'), self.bb.RejectRole)
b.setIcon(QIcon(I('edit-clear.png')))
self.bb.accepted.connect(self.accept)
self.bb.rejected.connect(self.reject)
l.addWidget(self.bb, 3, 0, 1, ncols)
self.resize(self.sizeHint() + QSize(100, 50))
def choose_dir(self):
d = choose_dir(self, 'library moved choose new loc',
_('New library location'), default_dir=self.oldloc)
if d is not None:
self.loc.setText(d)
def reject(self):
self.stats.remove(self.location)
QDialog.reject(self)
def accept(self):
newloc = unicode(self.loc.text())
if not LibraryDatabase2.exists_at(newloc):
error_dialog(self, _('No library found'),
_('No existing calibre library found at %s')%newloc,
show=True)
return
self.stats.rename(self.location, newloc)
self.newloc = newloc
QDialog.accept(self)
# }}}
class ChooseLibraryAction(InterfaceAction):
name = 'Choose Library'
action_spec = (_('%d books'), 'lt.png',
action_spec = (_('Choose Library'), 'lt.png',
_('Choose calibre library to work with'), None)
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
action_add_menu = True
action_menu_clone_qaction = _('Switch/create library...')
def genesis(self):
self.base_text = _('%d books')
self.count_changed(0)
self.qaction.triggered.connect(self.choose_library,
type=Qt.QueuedConnection)
@ -338,14 +396,14 @@ class ChooseLibraryAction(InterfaceAction):
loc = location.replace('/', os.sep)
exists = self.gui.library_view.model().db.exists_at(loc)
if not exists:
warning_dialog(self.gui, _('No library found'),
_('No existing calibre library was found at %s.'
' It will be removed from the list of known'
' libraries.')%loc, show=True)
self.stats.remove(location)
d = MovedDialog(self.stats, location, self.gui)
ret = d.exec_()
self.build_menus()
self.gui.iactions['Copy To Library'].build_menus()
return
if ret == d.Accepted:
loc = d.newloc.replace('/', os.sep)
else:
return
prefs['library_path'] = loc
#from calibre.utils.mem import memory
@ -376,7 +434,7 @@ class ChooseLibraryAction(InterfaceAction):
self.switch_requested(self.qs_locations[idx])
def count_changed(self, new_count):
text = self.action_spec[0]%new_count
text = self.base_text%new_count
a = self.qaction
a.setText(text)
tooltip = self.action_spec[2] + '\n\n' + text

View File

@ -12,9 +12,10 @@ from threading import Thread
from PyQt4.Qt import QToolButton
from calibre.gui2.actions import InterfaceAction
from calibre.gui2 import error_dialog, Dispatcher, warning_dialog
from calibre.gui2 import error_dialog, Dispatcher, warning_dialog, gprefs
from calibre.gui2.dialogs.progress import ProgressDialog
from calibre.utils.config import prefs, tweaks
from calibre.utils.date import now
class Worker(Thread): # {{{
@ -55,6 +56,8 @@ class Worker(Thread): # {{{
for i, x in enumerate(self.ids):
mi = self.db.get_metadata(x, index_is_id=True, get_cover=True,
cover_as_data=True)
if not gprefs['preserve_date_on_ctl']:
mi.timestamp = now()
self.progress(i, mi.title)
fmts = self.db.formats(x, index_is_id=True)
if not fmts: fmts = []
@ -65,14 +68,37 @@ class Worker(Thread): # {{{
as_path=True)
if p:
paths.append(p)
added = False
automerged = False
if prefs['add_formats_to_existing']:
identical_book_list = newdb.find_identical_books(mi)
if identical_book_list: # books with same author and nearly same title exist in newdb
added = True
automerged = True
seen_fmts = set()
for identical_book in identical_book_list:
self.add_formats(identical_book, paths, newdb, replace=False)
if not added:
ib_fmts = newdb.formats(identical_book, index_is_id=True)
if ib_fmts:
seen_fmts |= set(ib_fmts.split(','))
replace = gprefs['automerge'] == 'overwrite'
self.add_formats(identical_book, paths, newdb,
replace=replace)
if gprefs['automerge'] == 'new record':
incoming_fmts = \
set([os.path.splitext(path)[-1].replace('.',
'').upper() for path in paths])
if incoming_fmts.intersection(seen_fmts):
# There was at least one duplicate format
# so create a new record and put the
# incoming formats into it
# We should arguably put only the duplicate
# formats, but no real harm is done by having
# all formats
newdb.import_book(mi, paths, notify=False, import_hooks=False,
apply_import_tags=tweaks['add_new_book_tags_when_importing_books'],
preserve_uuid=False)
if not automerged:
newdb.import_book(mi, paths, notify=False, import_hooks=False,
apply_import_tags=tweaks['add_new_book_tags_when_importing_books'],
preserve_uuid=self.delete_after)

View File

@ -63,11 +63,15 @@ class ShareConnMenu(QMenu): # {{{
if hasattr(parent, 'keyboard'):
r = parent.keyboard.register_shortcut
prefix = 'Share/Connect Menu '
gr = ConnectShareAction.action_spec[0]
for attr in ('folder', 'bambook', 'itunes'):
if not (iswindows or isosx) and attr == 'itunes':
continue
ac = getattr(self, 'connect_to_%s_action'%attr)
r(prefix + attr, unicode(ac.text()), action=ac)
r(prefix + attr, unicode(ac.text()), action=ac,
group=gr)
r(prefix+' content server', _('Start/stop content server'),
action=self.toggle_server_action, group=gr)
def server_state_changed(self, running):
text = _('Start Content Server')

View File

@ -11,7 +11,7 @@ from calibre.gui2.actions import InterfaceAction
class RestartAction(InterfaceAction):
name = 'Restart'
action_spec = (_('&Restart'), None, None, _('Ctrl+R'))
action_spec = (_('Restart'), None, None, _('Ctrl+R'))
def genesis(self):
self.qaction.triggered.connect(self.restart)

View File

@ -239,7 +239,7 @@ class DBAdder(QObject): # {{{
class Adder(QObject): # {{{
ADD_TIMEOUT = 600 # seconds
ADD_TIMEOUT = 900 # seconds (15 minutes)
def __init__(self, parent, db, callback, spare_server=None):
QObject.__init__(self, parent)

View File

@ -87,7 +87,7 @@ class Int(Base):
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
QSpinBox(parent)]
w = self.widgets[1]
w.setRange(-100, 100000000)
w.setRange(-1000000, 100000000)
w.setSpecialValueText(_('Undefined'))
w.setSingleStep(1)
@ -110,7 +110,7 @@ class Float(Int):
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
QDoubleSpinBox(parent)]
w = self.widgets[1]
w.setRange(-100., float(100000000))
w.setRange(-1000000., float(100000000))
w.setDecimals(2)
w.setSpecialValueText(_('Undefined'))
w.setSingleStep(1)
@ -300,7 +300,6 @@ class Series(Base):
w = QDoubleSpinBox(parent)
w.setRange(-100., float(100000000))
w.setDecimals(2)
w.setSpecialValueText(_('Undefined'))
w.setSingleStep(1)
self.idx_widget=w
self.widgets.append(w)
@ -605,7 +604,7 @@ class BulkInt(BulkBase):
def setup_ui(self, parent):
self.make_widgets(parent, QSpinBox)
self.main_widget.setRange(-100, 100000000)
self.main_widget.setRange(-1000000, 100000000)
self.main_widget.setSpecialValueText(_('Undefined'))
self.main_widget.setSingleStep(1)
@ -627,7 +626,7 @@ class BulkFloat(BulkInt):
def setup_ui(self, parent):
self.make_widgets(parent, QDoubleSpinBox)
self.main_widget.setRange(-100., float(100000000))
self.main_widget.setRange(-1000000., float(100000000))
self.main_widget.setDecimals(2)
self.main_widget.setSpecialValueText(_('Undefined'))
self.main_widget.setSingleStep(1)

View File

@ -193,6 +193,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.recipe_model = recipe_model
self.recipe_model.do_refresh()
self.count_label.setText(
# NOTE: Number of news sources
_('%s news sources') %
self.recipe_model.showing_count)

View File

@ -8,32 +8,97 @@ __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from collections import OrderedDict
from functools import partial
from PyQt4.Qt import (QObject, QKeySequence)
from PyQt4.Qt import (QObject, QKeySequence, QAbstractItemModel, QModelIndex,
Qt, QStyledItemDelegate, QTextDocument, QStyle, pyqtSignal, QFrame,
QApplication, QSize, QRectF, QWidget, QTreeView,
QGridLayout, QLabel, QRadioButton, QPushButton, QToolButton, QIcon)
from calibre.utils.config import JSONConfig
from calibre.constants import DEBUG
from calibre import prints
from calibre.utils.icu import sort_key, lower
from calibre.gui2 import NONE, error_dialog, info_dialog
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.gui2.search_box import SearchBox2
ROOT = QModelIndex()
class NameConflict(ValueError):
pass
class Manager(QObject):
def finalize(shortcuts, custom_keys_map={}): # {{{
'''
Resolve conflicts and assign keys to every action in shorcuts, which must
be a OrderedDict. User specified mappings of unique names to keys (as a
list of strings) should be passed in in custom_keys_map. Return a mapping
of unique names to resolved keys. Also sets the set_to_defaul member
correctly for each shortcut.
'''
seen, keys_map = {}, {}
for unique_name, shortcut in shortcuts.iteritems():
custom_keys = custom_keys_map.get(unique_name, None)
if custom_keys is None:
candidates = shortcut['default_keys']
shortcut['set_to_default'] = True
else:
candidates = custom_keys
shortcut['set_to_default'] = False
keys = []
for x in candidates:
ks = QKeySequence(x, QKeySequence.PortableText)
x = unicode(ks.toString(QKeySequence.PortableText))
if x in seen:
if DEBUG:
prints('Key %r for shortcut %s is already used by'
' %s, ignoring'%(x, shortcut['name'], seen[x]['name']))
continue
seen[x] = shortcut
keys.append(ks)
keys = tuple(keys)
#print (111111, unique_name, candidates, keys)
keys_map[unique_name] = keys
ac = shortcut['action']
if ac is not None:
ac.setShortcuts(list(keys))
return keys_map
# }}}
class Manager(QObject): # {{{
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.config = JSONConfig('shortcuts/main')
self.custom_keys_map = {}
self.shortcuts = OrderedDict()
self.keys_map = {}
for unique_name, keys in self.config.get(
'map', {}).iteritems():
self.custom_keys_map[unique_name] = tuple(keys)
self.groups = {}
def register_shortcut(self, unique_name, name, default_keys=(),
description=None, action=None):
description=None, action=None, group=None):
'''
Register a shortcut with calibre. calibre will manage the shortcut,
automatically resolving conflicts and allowing the user to customize
it.
:param unique_name: A string that uniquely identifies this shortcut
:param name: A user visible name describing the action performed by
this shortcut
:param default_keys: A tuple of keys that trigger this shortcut. Each
key must be a string. For example: ('Ctrl+A', 'Alt+B', 'C',
'Shift+Meta+D'). These keys will be assigned to the
shortcut unless there is a conflict.
:param action: A QAction object. The shortcut will cause this QAction
to be triggered. Connect to its triggered signal in your code to
respond to the shortcut.
:param group: A string describing what "group" this shortcut belongs
to. This is used to organize the list of shortcuts when the user is
customizing them.
'''
if unique_name in self.shortcuts:
name = self.shortcuts[unique_name]['name']
raise NameConflict('Shortcut for %r already registered by %s'%(
@ -41,31 +106,519 @@ class Manager(QObject):
shortcut = {'name':name, 'desc':description, 'action': action,
'default_keys':tuple(default_keys)}
self.shortcuts[unique_name] = shortcut
group = group if group else _('Miscellaneous')
self.groups[group] = self.groups.get(group, []) + [unique_name]
def finalize(self):
seen = {}
for unique_name, shortcut in self.shortcuts.iteritems():
custom_keys = self.custom_keys_map.get(unique_name, None)
if custom_keys is None:
candidates = shortcut['default_keys']
custom_keys_map = {un:tuple(keys) for un, keys in self.config.get(
'map', {}).iteritems()}
self.keys_map = finalize(self.shortcuts, custom_keys_map=custom_keys_map)
# }}}
# Model {{{
class Node(object):
def __init__(self, group_map, shortcut_map, name=None, shortcut=None):
self.data = name if name is not None else shortcut
self.is_shortcut = shortcut is not None
self.children = []
if name is not None:
self.children = [Node(None, None, shortcut=shortcut_map[uname])
for uname in group_map[name]]
def __len__(self):
return len(self.children)
def __getitem__(self, row):
return self.children[row]
def __iter__(self):
for child in self.children:
yield child
class ConfigModel(QAbstractItemModel, SearchQueryParser):
def __init__(self, keyboard, parent=None):
QAbstractItemModel.__init__(self, parent)
SearchQueryParser.__init__(self, ['all'])
self.keyboard = keyboard
groups = sorted(keyboard.groups, key=sort_key)
shortcut_map = {k:v.copy() for k, v in
self.keyboard.shortcuts.iteritems()}
for un, s in shortcut_map.iteritems():
s['keys'] = tuple(self.keyboard.keys_map[un])
s['unique_name'] = un
s['group'] = [g for g, names in self.keyboard.groups.iteritems() if un in
names][0]
group_map = {group:sorted(names, key=lambda x:
sort_key(shortcut_map[x]['name'])) for group, names in
self.keyboard.groups.iteritems()}
self.data = [Node(group_map, shortcut_map, group) for group in groups]
@property
def all_shortcuts(self):
for group in self.data:
for sc in group:
yield sc
def rowCount(self, parent=ROOT):
ip = parent.internalPointer()
if ip is None:
return len(self.data)
return len(ip)
def columnCount(self, parent=ROOT):
return 1
def index(self, row, column, parent=ROOT):
ip = parent.internalPointer()
if ip is None:
ip = self.data
try:
return self.createIndex(row, column, ip[row])
except:
pass
return ROOT
def parent(self, index):
ip = index.internalPointer()
if ip is None or not ip.is_shortcut:
return ROOT
group = ip.data['group']
for i, g in enumerate(self.data):
if g.data == group:
return self.index(i, 0)
return ROOT
def data(self, index, role=Qt.DisplayRole):
ip = index.internalPointer()
if ip is not None and role == Qt.UserRole:
return ip
return NONE
def flags(self, index):
ans = QAbstractItemModel.flags(self, index)
ip = index.internalPointer()
if getattr(ip, 'is_shortcut', False):
ans |= Qt.ItemIsEditable
return ans
def restore_defaults(self):
shortcut_map = {}
for node in self.all_shortcuts:
sc = node.data
shortcut_map[sc['unique_name']] = sc
shortcuts = OrderedDict([(un, shortcut_map[un]) for un in
self.keyboard.shortcuts])
keys_map = finalize(shortcuts)
for node in self.all_shortcuts:
s = node.data
s['keys'] = tuple(keys_map[s['unique_name']])
for r in xrange(self.rowCount()):
group = self.index(r, 0)
num = self.rowCount(group)
if num > 0:
self.dataChanged.emit(self.index(0, 0, group),
self.index(num-1, 0, group))
def commit(self):
kmap = {}
for node in self.all_shortcuts:
sc = node.data
if sc['set_to_default']: continue
keys = [unicode(k.toString(k.PortableText)) for k in sc['keys']]
kmap[sc['unique_name']] = keys
self.keyboard.config['map'] = kmap
def universal_set(self):
ans = set()
for i, group in enumerate(self.data):
ans.add((i, -1))
for j, sc in enumerate(group.children):
ans.add((i, j))
return ans
def get_matches(self, location, query, candidates=None):
if candidates is None:
candidates = self.universal_set()
ans = set([])
if not query:
return ans
query = lower(query)
for c, p in candidates:
if p < 0:
if query in lower(self.data[c].data):
ans.add((c, p))
else:
candidates = custom_keys
keys = []
for x in candidates:
ks = QKeySequence(x, QKeySequence.PortableText)
x = unicode(ks.toString(QKeySequence.PortableText))
if x in seen:
if DEBUG:
prints('Key %r for shortcut %s is already used by'
' %s, ignoring'%(x, shortcut['name'], seen[x]['name']))
try:
sc = self.data[c].children[p].data
except:
continue
seen[x] = shortcut
keys.append(ks)
keys = tuple(keys)
#print (111111, unique_name, candidates, keys)
if query in lower(sc['name']):
ans.add((c, p))
return ans
self.keys_map[unique_name] = keys
ac = shortcut['action']
if ac is not None:
ac.setShortcuts(list(keys))
def find(self, query):
query = query.strip()
if not query:
return ROOT
matches = self.parse(query)
if not matches:
return ROOT
matches = list(sorted(matches))
c, p = matches[0]
cat_idx = self.index(c, 0)
if p == -1:
return cat_idx
return self.index(p, 0, cat_idx)
def find_next(self, idx, query, backwards=False):
query = query.strip()
if not query:
return idx
matches = self.parse(query)
if not matches:
return idx
if idx.parent().isValid():
loc = (idx.parent().row(), idx.row())
else:
loc = (idx.row(), -1)
if loc not in matches:
return self.find(query)
if len(matches) == 1:
return ROOT
matches = list(sorted(matches))
i = matches.index(loc)
if backwards:
ans = i - 1 if i - 1 >= 0 else len(matches)-1
else:
ans = i + 1 if i + 1 < len(matches) else 0
ans = matches[ans]
return (self.index(ans[0], 0) if ans[1] < 0 else
self.index(ans[1], 0, self.index(ans[0], 0)))
# }}}
class Editor(QFrame): # {{{
editing_done = pyqtSignal(object)
def __init__(self, parent=None):
QFrame.__init__(self, parent)
self.setFocusPolicy(Qt.StrongFocus)
self.setAutoFillBackground(True)
self.capture = 0
self.setFrameShape(self.StyledPanel)
self.setFrameShadow(self.Raised)
self._layout = l = QGridLayout(self)
self.setLayout(l)
self.header = QLabel('')
l.addWidget(self.header, 0, 0, 1, 2)
self.use_default = QRadioButton('')
self.use_custom = QRadioButton(_('Custom'))
l.addWidget(self.use_default, 1, 0, 1, 3)
l.addWidget(self.use_custom, 2, 0, 1, 3)
self.use_custom.toggled.connect(self.custom_toggled)
off = 2
for which in (1, 2):
text = _('&Shortcut:') if which == 1 else _('&Alternate shortcut:')
la = QLabel(text)
la.setStyleSheet('QLabel { margin-left: 1.5em }')
l.addWidget(la, off+which, 0, 1, 3)
setattr(self, 'label%d'%which, la)
button = QPushButton(_('None'), self)
button.clicked.connect(partial(self.capture_clicked, which=which))
button.keyPressEvent = partial(self.key_press_event, which=which)
setattr(self, 'button%d'%which, button)
clear = QToolButton(self)
clear.setIcon(QIcon(I('clear_left.png')))
clear.clicked.connect(partial(self.clear_clicked, which=which))
setattr(self, 'clear%d'%which, clear)
l.addWidget(button, off+which, 1, 1, 1)
l.addWidget(clear, off+which, 2, 1, 1)
la.setBuddy(button)
self.done_button = doneb = QPushButton(_('Done'), self)
l.addWidget(doneb, 0, 2, 1, 1)
doneb.clicked.connect(lambda : self.editing_done.emit(self))
l.setColumnStretch(0, 100)
self.custom_toggled(False)
def initialize(self, shortcut, all_shortcuts):
self.header.setText('<b>%s: %s</b>'%(_('Customize'), shortcut['name']))
self.all_shortcuts = all_shortcuts
self.shortcut = shortcut
self.default_keys = [QKeySequence(k, QKeySequence.PortableText) for k
in shortcut['default_keys']]
self.current_keys = list(shortcut['keys'])
default = ', '.join([unicode(k.toString(k.NativeText)) for k in
self.default_keys])
if not default: default = _('None')
current = ', '.join([unicode(k.toString(k.NativeText)) for k in
self.current_keys])
if not current: current = _('None')
self.use_default.setText(_('Default: %s [Currently not conflicting: %s]')%
(default, current))
if shortcut['set_to_default']:
self.use_default.setChecked(True)
else:
self.use_custom.setChecked(True)
for key, which in zip(self.current_keys, [1,2]):
button = getattr(self, 'button%d'%which)
button.setText(key.toString(key.NativeText))
def custom_toggled(self, checked):
for w in ('1', '2'):
for o in ('label', 'button', 'clear'):
getattr(self, o+w).setEnabled(checked)
def capture_clicked(self, which=1):
self.capture = which
button = getattr(self, 'button%d'%which)
button.setText(_('Press a key...'))
button.setFocus(Qt.OtherFocusReason)
button.setStyleSheet('QPushButton { font-weight: bold}')
def clear_clicked(self, which=0):
button = getattr(self, 'button%d'%which)
button.setText(_('None'))
def key_press_event(self, ev, which=0):
code = ev.key()
if self.capture == 0 or code in (0, Qt.Key_unknown,
Qt.Key_Shift, Qt.Key_Control, Qt.Key_Alt, Qt.Key_Meta,
Qt.Key_AltGr, Qt.Key_CapsLock, Qt.Key_NumLock, Qt.Key_ScrollLock):
return QWidget.keyPressEvent(self, ev)
button = getattr(self, 'button%d'%which)
button.setStyleSheet('QPushButton { font-weight: normal}')
sequence = QKeySequence(code|(int(ev.modifiers())&~Qt.KeypadModifier))
button.setText(sequence.toString(QKeySequence.NativeText))
self.capture = 0
dup_desc = self.dup_check(sequence)
if dup_desc is not None:
error_dialog(self, _('Already assigned'),
unicode(sequence.toString(QKeySequence.NativeText)) + ' ' +
_('already assigned to') + ' ' + dup_desc, show=True)
self.clear_clicked(which=which)
def dup_check(self, sequence):
for sc in self.all_shortcuts:
if sc is self.shortcut: continue
for k in sc['keys']:
if k == sequence:
return sc['name']
@property
def custom_keys(self):
if self.use_default.isChecked():
return None
ans = []
for which in (1, 2):
button = getattr(self, 'button%d'%which)
t = unicode(button.text())
if t == _('None'):
continue
ks = QKeySequence(t, QKeySequence.NativeText)
if not ks.isEmpty():
ans.append(ks)
return tuple(ans)
# }}}
class Delegate(QStyledItemDelegate): # {{{
changed_signal = pyqtSignal()
def __init__(self, parent=None):
QStyledItemDelegate.__init__(self, parent)
self.editing_index = None
self.closeEditor.connect(self.editing_done)
def to_doc(self, index):
data = index.data(Qt.UserRole).toPyObject()
if data is None:
html = _('<b>This shortcut no longer exists</b>')
elif data.is_shortcut:
shortcut = data.data
# Shortcut
keys = [unicode(k.toString(k.NativeText)) for k in shortcut['keys']]
if not keys:
keys = _('None')
else:
keys = ', '.join(keys)
html = '<b>%s</b><br>%s: %s'%(shortcut['name'], _('Shortcuts'), keys)
else:
# Group
html = '<h3>%s</h3>'%data.data
doc = QTextDocument()
doc.setHtml(html)
return doc
def sizeHint(self, option, index):
if index == self.editing_index:
return QSize(200, 200)
ans = self.to_doc(index).size().toSize()
return ans
def paint(self, painter, option, index):
painter.save()
painter.setClipRect(QRectF(option.rect))
if hasattr(QStyle, 'CE_ItemViewItem'):
QApplication.style().drawControl(QStyle.CE_ItemViewItem, option, painter)
elif option.state & QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.highlight())
painter.translate(option.rect.topLeft())
self.to_doc(index).drawContents(painter)
painter.restore()
def createEditor(self, parent, option, index):
w = Editor(parent=parent)
w.editing_done.connect(self.editor_done)
self.editing_index = index
self.sizeHintChanged.emit(index)
return w
def editor_done(self, editor):
self.commitData.emit(editor)
def setEditorData(self, editor, index):
all_shortcuts = [x.data for x in index.model().all_shortcuts]
shortcut = index.internalPointer().data
editor.initialize(shortcut, all_shortcuts)
def setModelData(self, editor, model, index):
self.closeEditor.emit(editor, self.NoHint)
custom_keys = editor.custom_keys
sc = index.data(Qt.UserRole).toPyObject().data
if custom_keys is None:
candidates = []
for ckey in sc['default_keys']:
ckey = QKeySequence(ckey, QKeySequence.PortableText)
matched = False
for s in editor.all_shortcuts:
for k in s['keys']:
if k == ckey:
matched = True
break
if not matched:
candidates.append(ckey)
candidates = tuple(candidates)
sc['set_to_default'] = True
else:
sc['set_to_default'] = False
candidates = custom_keys
sc['keys'] = candidates
self.changed_signal.emit()
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def editing_done(self, *args):
idx = self.editing_index
self.editing_index = None
if idx is not None:
self.sizeHintChanged.emit(idx)
# }}}
class ShortcutConfig(QWidget): # {{{
changed_signal = pyqtSignal()
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self._layout = l = QGridLayout()
self.setLayout(self._layout)
self.header = QLabel(_('Double click on any entry to change the'
' keyboard shortcuts associated with it'))
l.addWidget(self.header, 0, 0, 1, 3)
self.view = QTreeView(self)
self.view.setAlternatingRowColors(True)
self.view.setHeaderHidden(True)
self.view.setAnimated(True)
l.addWidget(self.view, 1, 0, 1, 3)
self.delegate = Delegate()
self.view.setItemDelegate(self.delegate)
self.delegate.sizeHintChanged.connect(self.scrollTo)
self.delegate.changed_signal.connect(self.changed_signal)
self.search = SearchBox2(self)
self.search.initialize('shortcuts_search_history',
help_text=_('Search for a shortcut by name'))
self.search.search.connect(self.find)
l.addWidget(self.search, 2, 0, 1, 1)
self.nb = QPushButton(QIcon(I('arrow-down.png')), _('&Next'), self)
self.pb = QPushButton(QIcon(I('arrow-up.png')), _('&Previous'), self)
self.nb.clicked.connect(self.find_next)
self.pb.clicked.connect(self.find_previous)
l.addWidget(self.nb, 2, 1, 1, 1)
l.addWidget(self.pb, 2, 2, 1, 1)
l.setColumnStretch(0, 100)
def restore_defaults(self):
self._model.restore_defaults()
self.changed_signal.emit()
def commit(self):
self._model.commit()
def initialize(self, keyboard):
self._model = ConfigModel(keyboard, parent=self)
self.view.setModel(self._model)
def scrollTo(self, index):
if index is not None:
self.view.scrollTo(index, self.view.PositionAtTop)
@property
def is_editing(self):
return self.view.state() == self.view.EditingState
def find(self, query):
idx = self._model.find(query)
if not idx.isValid():
return info_dialog(self, _('No matches'),
_('Could not find any matching shortcuts'), show=True,
show_copy_button=False)
self.highlight_index(idx)
def highlight_index(self, idx):
self.view.scrollTo(idx)
self.view.selectionModel().select(idx,
self.view.selectionModel().ClearAndSelect)
self.view.setCurrentIndex(idx)
def find_next(self, *args):
idx = self.view.currentIndex()
if not idx.isValid():
idx = self._model.index(0, 0)
idx = self._model.find_next(idx,
unicode(self.search.currentText()))
self.highlight_index(idx)
def find_previous(self, *args):
idx = self.view.currentIndex()
if not idx.isValid():
idx = self._model.index(0, 0)
idx = self._model.find_next(idx,
unicode(self.search.currentText()), backwards=True)
self.highlight_index(idx)
# }}}

View File

@ -300,13 +300,13 @@ class CcNumberDelegate(QStyledItemDelegate): # {{{
col = m.column_map[index.column()]
if m.custom_columns[col]['datatype'] == 'int':
editor = QSpinBox(parent)
editor.setRange(-100, 100000000)
editor.setRange(-1000000, 100000000)
editor.setSpecialValueText(_('Undefined'))
editor.setSingleStep(1)
else:
editor = QDoubleSpinBox(parent)
editor.setSpecialValueText(_('Undefined'))
editor.setRange(-100., 100000000)
editor.setRange(-1000000., 100000000)
editor.setDecimals(2)
return editor

View File

@ -62,7 +62,7 @@ class BooksModel(QAbstractTableModel): # {{{
'rating' : _('Rating'),
'publisher' : _("Publisher"),
'tags' : _("Tags"),
'series' : _("Series"),
'series' : ngettext("Series", 'Series', 1),
'last_modified' : _('Modified'),
}
@ -694,7 +694,7 @@ class BooksModel(QAbstractTableModel): # {{{
# we will get asked to display columns we don't know about. Must test for this.
if col >= len(self.column_to_dc_map):
return NONE
if role in (Qt.DisplayRole, Qt.EditRole):
if role in (Qt.DisplayRole, Qt.EditRole, Qt.ToolTipRole):
return self.column_to_dc_map[col](index.row())
elif role == Qt.BackgroundRole:
if self.id(index) in self.ids_to_highlight_set:

View File

@ -719,7 +719,7 @@ class BooksView(QTableView): # {{{
break
def set_current_row(self, row, select=True):
if row > -1:
if row > -1 and row < self.model().rowCount(QModelIndex()):
h = self.horizontalHeader()
logical_indices = list(range(h.count()))
logical_indices = [x for x in logical_indices if not

View File

@ -24,6 +24,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('read_file_metadata', prefs)
r('swap_author_names', prefs)
r('add_formats_to_existing', prefs)
r('preserve_date_on_ctl', gprefs)
choices = [
(_('Ignore duplicate incoming formats'), 'ignore'),
(_('Overwrite existing duplicate formats'), 'overwrite'),

View File

@ -27,7 +27,7 @@
<item row="1" column="0">
<widget class="QCheckBox" name="opt_read_file_metadata">
<property name="text">
<string>Read &amp;metadata from &amp;file contents rather than file name</string>
<string>Read &amp;metadata from file contents rather than file name</string>
</property>
</widget>
</item>
@ -58,7 +58,7 @@
</item>
</layout>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QCheckBox" name="opt_add_formats_to_existing">
<property name="toolTip">
<string>Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
@ -72,7 +72,7 @@ Title match ignores leading indefinite articles (&quot;the&quot;, &quot;a&quot;,
</property>
</widget>
</item>
<item row="2" column="1">
<item row="3" column="1">
<widget class="QComboBox" name="opt_automerge">
<property name="toolTip">
<string>Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
@ -88,7 +88,7 @@ Author matching is exact.</string>
</property>
</widget>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_230">
<property name="text">
<string>&amp;Tags to apply when adding a book:</string>
@ -98,14 +98,14 @@ Author matching is exact.</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="1">
<widget class="QLineEdit" name="opt_new_book_tags">
<property name="toolTip">
<string>A comma-separated list of tags that will be applied to books added to the library</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<item row="5" column="0" colspan="2">
<widget class="QGroupBox" name="metadata_box">
<property name="title">
<string>&amp;Configure metadata from file name</string>
@ -127,6 +127,13 @@ Author matching is exact.</string>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="opt_preserve_date_on_ctl">
<property name="text">
<string>When &amp;copying books from one library to another, preserve the date</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

View File

@ -0,0 +1,44 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import QVBoxLayout
from calibre.gui2.preferences import (ConfigWidgetBase, test_widget)
from calibre.gui2.keyboard import ShortcutConfig
class ConfigWidget(ConfigWidgetBase):
def genesis(self, gui):
self.gui = gui
self.conf_widget = ShortcutConfig(self)
self.conf_widget.changed_signal.connect(self.changed_signal)
self._layout = l = QVBoxLayout()
self.setLayout(l)
l.addWidget(self.conf_widget)
def initialize(self):
ConfigWidgetBase.initialize(self)
self.conf_widget.initialize(self.gui.keyboard)
def restore_defaults(self):
ConfigWidgetBase.restore_defaults(self)
self.conf_widget.restore_defaults()
def commit(self):
self.conf_widget.commit()
return ConfigWidgetBase.commit(self)
def refresh_gui(self, gui):
gui.keyboard.finalize()
if __name__ == '__main__':
from PyQt4.Qt import QApplication
app = QApplication([])
test_widget('Advanced', 'Keyboard')

View File

@ -12,6 +12,7 @@ from calibre.gui2 import error_dialog, question_dialog
from calibre.gui2.preferences.save_template_ui import Ui_Form
from calibre.library.save_to_disk import FORMAT_ARG_DESCS, preprocess_template
from calibre.utils.formatter import validation_formatter
from calibre.gui2.dialogs.template_dialog import TemplateDialog
class SaveTemplate(QWidget, Ui_Form):
@ -40,6 +41,14 @@ class SaveTemplate(QWidget, Ui_Form):
self.opt_template.editTextChanged.connect(self.changed)
self.opt_template.currentIndexChanged.connect(self.changed)
self.option_name = name
self.open_editor.clicked.connect(self.do_open_editor)
def do_open_editor(self):
t = TemplateDialog(self, self.opt_template.text())
t.setWindowTitle(_('Edit template'))
if t.exec_():
self.opt_template.set_value(t.rule[1])
def changed(self, *args):
self.changed_signal.emit()

View File

@ -20,7 +20,7 @@
<string>Save &amp;template</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>By adjusting the template below, you can control what folders the files are saved in and what filenames they are given. You can use the / character to indicate sub-folders. Available metadata variables are described below. If a particular book does not have some metadata, the variable will be replaced by the empty string.</string>
@ -30,18 +30,38 @@
</property>
</widget>
</item>
<item row="2" column="0">
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Available variables:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<item row="3" column="0" colspan="2">
<widget class="QTextBrowser" name="template_variables"/>
</item>
<item row="1" column="0">
<widget class="HistoryBox" name="opt_template"/>
<widget class="HistoryBox" name="opt_template">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>10</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
<property name="minimumContentsLength">
<number>40</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="open_editor">
<property name="text">
<string>Template Editor</string>
</property>
</widget>
</item>
</layout>
</widget>

View File

@ -83,7 +83,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.builtin_source_dict = {}
self.funcs = formatter_functions.get_functions()
self.builtins = formatter_functions.get_builtins()
self.builtins = formatter_functions.get_builtins_and_aliases()
self.build_function_names_box()
self.function_name.currentIndexChanged[str].connect(self.function_index_changed)
@ -151,7 +151,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
name = unicode(self.function_name.currentText())
if name in self.funcs:
error_dialog(self.gui, _('Template functions'),
_('Name already used'), show=True)
_('Name %s already used')%(name,), show=True)
return
if self.argument_count.value() == 0:
box = warning_dialog(self.gui, _('Template functions'),

View File

@ -44,7 +44,7 @@ class OpenBooksStore(BasicStoreConfig, StorePlugin):
for data in doc.xpath('//ul[@id="object_list"]//li'):
if counter <= 0:
break
id = ''.join(data.xpath('.//div[@class="links"]/a[1]/@href'))
id = id.strip()
if not id:

View File

@ -522,6 +522,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.card_a_view.reset()
self.card_b_view.reset()
self.device_manager.set_current_library_uuid(db.library_id)
self.library_view.set_current_row(0)
# Run a garbage collection now so that it does not freeze the
# interface later
gc.collect()

View File

@ -979,6 +979,11 @@ class DocumentView(QWebView): # {{{
return ret
def keyPressEvent(self, event):
if not self.handle_key_press(event):
return QWebView.keyPressEvent(self, event)
def handle_key_press(self, event):
handled = True
key = self.shortcuts.get_match(event)
func = self.goto_location_actions.get(key, None)
if func is not None:
@ -996,7 +1001,8 @@ class DocumentView(QWebView): # {{{
elif key == 'Right':
self.scroll_by(x=15)
else:
return QWebView.keyPressEvent(self, event)
handled = False
return handled
def resizeEvent(self, event):
ret = QWebView.resizeEvent(self, event)

View File

@ -755,6 +755,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
if self.current_index > 0:
self.load_path(self.iterator.spine[self.current_index-1], pos=1.0)
def keyPressEvent(self, event):
MainWindow.keyPressEvent(self, event)
if not event.isAccepted():
if not self.view.handle_key_press(event):
event.ignore()
def __enter__(self):
return self

View File

@ -10,7 +10,7 @@ from xml.sax.saxutils import escape
from lxml import etree
from types import StringType, UnicodeType
from calibre import prints, prepare_string_for_xml, strftime
from calibre import (prints, prepare_string_for_xml, strftime, force_unicode)
from calibre.constants import preferred_encoding, DEBUG
from calibre.customize import CatalogPlugin
from calibre.customize.conversion import OptionRecommendation, DummyReporter
@ -1083,15 +1083,11 @@ class EPUB_MOBI(CatalogPlugin):
self.__totalSteps += incremental_jobs
# Load section list templates
templates = []
with open(P('catalog/section_list_templates.py'), 'r') as f:
for line in f:
t = re.match("(by_.+_template)",line)
if t:
templates.append(t.group(1))
execfile(P('catalog/section_list_templates.py'), locals())
for t in templates:
setattr(self,t,eval(t))
templates = {}
execfile(P('catalog/section_list_templates.py'), templates)
for name, template in templates.iteritems():
if name.startswith('by_') and name.endswith('_template'):
setattr(self, name, force_unicode(template, 'utf-8'))
# Accessors
if True:

View File

@ -296,8 +296,8 @@ def do_save_book_to_disk(id_, mi, cover, plugboards,
replace_whitespace=opts.replace_whitespace, safe_format=False)
except Exception, e:
raise ValueError(_('Failed to calculate path for '
'save to disk. Template: %s\n'
'Error: %s'%(opts.template, e)))
'save to disk. Template: %(templ)s\n'
'Error: %(err)s')%dict(templ=opts.template, err=e))
if opts.single_dir:
components = components[-1:]
if not components:

View File

@ -284,11 +284,22 @@ The most likely cause of this is your antivirus program. Try temporarily disabli
I cannot send emails using |app|?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Because of the large amount of spam in email, sending email can be tricky as different servers use different strategies to block email spam.
Because of the large amount of spam in email, sending email can be tricky, as different mail servers use different strategies to block email.
The most common problem is if you are sending email directly (without a mail relay) in |app|. Many servers (for example, Amazon) block email
that does not come from a well known relay. The easiest way around this is to setup a free GMail account and then goto Preferences->Email in |app| and
click the "Use Gmail" button. |app| will then use Gmail to send the mail. Remember to update the email preferences in on your Amazon Kindle page to
allow email sent from your Gmail email address.
that does not come from a well known relay. The most robust way to setup email sending in |app| is to do the following:
* Create a free GMail account at `Google <http://www.gmail.com>`_.
* Goto Preferences->Email in |app| and click the "Use Gmail" button and fill in the information asked for.
* |app| will then use GMail to send the mail.
* If you are sending to your Kindle, remember to update the email preferences on your Amazon Kindle page to allow email sent from your GMail email address.
Even after doing this, you may have problems. One common source of problems is that some poorly designed antivirus
programs block |app| from opening a connection to send email. Try adding an exclusion for |app| in your
antivirus program.
.. note:: Google can disable your account if you use it to send large amounts of email. So, when using GMail to send mail |app| automatically restricts
itself to sending one book every five minutes. If you don't mind risking your account being blocked you can reduce this wait interval by
going to Preferences->Tweaks in |app|.
Why is my device not detected in linux?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -307,7 +318,7 @@ must return ``CONFIG_SCSI_MULTI_LUN=y``. If you don't see either, you have to re
My device is getting mounted read-only in linux, so |app| cannot connect to it?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
linux kernels mount devices read-only when their filesystems have errors. You can repair the filesystem with::
Linux kernels mount devices read-only when their filesystems have errors. You can repair the filesystem with::
sudo fsck.vfat -y /dev/sdc

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More