mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
KG revisions
This commit is contained in:
commit
a5287acd47
@ -1,24 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://web.resource.org/cc/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
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://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="128"
|
||||
height="128"
|
||||
id="svg1307"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.43"
|
||||
inkscape:version="0.47 r22583"
|
||||
version="1.0"
|
||||
sodipodi:docbase="/home/pinheiro/Documents/pics/new oxygen/svg"
|
||||
sodipodi:docname="love.svg">
|
||||
sodipodi:docname="donate.svg">
|
||||
<defs
|
||||
id="defs1309">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 64 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="128 : 64 : 1"
|
||||
inkscape:persp3d-origin="64 : 42.666667 : 1"
|
||||
id="perspective44" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient2231">
|
||||
@ -180,8 +187,8 @@
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="7.851329"
|
||||
inkscape:cx="92.691163"
|
||||
inkscape:cy="92.473338"
|
||||
inkscape:cx="60.937831"
|
||||
inkscape:cy="61.488995"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:document-units="px"
|
||||
@ -189,10 +196,11 @@
|
||||
guidetolerance="0.1px"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:window-width="1106"
|
||||
inkscape:window-height="958"
|
||||
inkscape:window-x="597"
|
||||
inkscape:window-y="25">
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="997"
|
||||
inkscape:window-x="-4"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-maximized="1">
|
||||
<sodipodi:guide
|
||||
orientation="horizontal"
|
||||
position="32.487481"
|
||||
@ -245,26 +253,19 @@
|
||||
id="path2276"
|
||||
d="M 50.892799,3.2812959 L 50.892799,0.48658747 L 50.892799,3.2812959 z "
|
||||
style="fill:#ffffff;fill-opacity:0.75688076;fill-rule:nonzero;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:0.38139535;fill:url(#radialGradient3297);fill-opacity:1.0;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
|
||||
id="path3289"
|
||||
sodipodi:cx="63.912209"
|
||||
sodipodi:cy="115.70919"
|
||||
sodipodi:rx="63.912209"
|
||||
sodipodi:ry="12.641975"
|
||||
d="M 127.82442 115.70919 A 63.912209 12.641975 0 1 1 0,115.70919 A 63.912209 12.641975 0 1 1 127.82442 115.70919 z"
|
||||
transform="matrix(1,0,0,0.416667,0,74.87151)" />
|
||||
<path
|
||||
style="fill:url(#radialGradient2335);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 35.325021,6.2016208 C 32.278871,6.2210338 29.045555,6.6687791 25.645673,7.6089386 C 5.9380713,13.058619 0.404709,29.342113 5.3805953,48.506873 C 12.126047,74.487157 36.855395,101.02725 64.150803,115.92895 L 64.150803,116.02417 C 64.162016,116.00826 64.173539,115.99248 64.184766,115.97656 C 64.195995,115.99248 64.207516,116.00826 64.218732,116.02417 L 64.218732,115.92895 C 90.473794,101.59521 116.24349,74.487157 122.98895,48.506873 C 127.96481,29.342113 122.43148,13.058619 102.72386,7.6089386 C 83.422254,2.2715258 69.549778,12.840101 64.184766,27.183808 C 59.764775,15.366673 49.572303,6.1108179 35.325021,6.2016208 z "
|
||||
id="path2245"
|
||||
sodipodi:nodetypes="cssccsccsscc" />
|
||||
<path
|
||||
id="path2369"
|
||||
d="M 35.325021,6.2016208 C 32.278871,6.2210338 29.045555,6.6687791 25.645673,7.6089386 C 5.9380713,13.058619 0.404709,29.342113 5.3805953,48.506873 C 12.126047,74.487157 37.113186,101.16799 64.150803,115.92895 L 64.150803,116.02417 C 64.162016,116.00826 64.173539,115.99248 64.184766,115.97656 C 64.195995,115.99248 64.207516,116.00826 64.218732,116.02417 L 64.218732,115.92895 C 90.398445,101.63635 116.24349,74.487157 122.98895,48.506873 C 127.96481,29.342113 122.43148,13.058619 102.72386,7.6089386 C 83.422254,2.2715258 69.549778,12.840101 64.184766,27.183808 C 59.764775,15.366673 49.572303,6.1108179 35.325021,6.2016208 z "
|
||||
style="opacity:0.4713115;fill:url(#linearGradient2379);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
sodipodi:nodetypes="cssccsccsscc" />
|
||||
<g
|
||||
id="g2850">
|
||||
<path
|
||||
sodipodi:nodetypes="cssccsccsscc"
|
||||
style="opacity:0.4713115;fill:url(#linearGradient2379);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 35.325021,6.2016208 C 32.278871,6.2210338 29.045555,6.6687791 25.645673,7.6089386 C 5.9380713,13.058619 0.404709,29.342113 5.3805953,48.506873 C 12.126047,74.487157 37.113186,101.16799 64.150803,115.92895 L 64.150803,116.02417 C 64.162016,116.00826 64.173539,115.99248 64.184766,115.97656 C 64.195995,115.99248 64.207516,116.00826 64.218732,116.02417 L 64.218732,115.92895 C 90.398445,101.63635 116.24349,74.487157 122.98895,48.506873 C 127.96481,29.342113 122.43148,13.058619 102.72386,7.6089386 C 83.422254,2.2715258 69.549778,12.840101 64.184766,27.183808 C 59.764775,15.366673 49.572303,6.1108179 35.325021,6.2016208 z "
|
||||
id="path2369" />
|
||||
</g>
|
||||
<path
|
||||
style="opacity:0.1762295;fill:url(#linearGradient2331);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.29999995;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 34.451605,6.2067207 C 31.659392,6.2976073 28.7301,6.7682297 25.648957,7.6202497 C 7.7889432,12.559022 1.5815371,26.389172 4.2759909,43.204304 C 27.13595,75.72273 65.297627,95.42612 91.41193,91.971053 C 105.43169,77.948778 119.04939,63.70497 122.99185,48.520401 C 127.96773,29.355639 122.42255,13.069929 102.71494,7.6202497 C 83.413331,2.2828362 69.546961,12.850845 64.181949,27.194552 C 59.761957,15.377418 49.555176,6.1159177 35.307894,6.2067207 C 35.022317,6.2085406 34.740456,6.1973187 34.451605,6.2067207 z "
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -1,234 +1,176 @@
|
||||
<?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) -->
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<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 139 139"
|
||||
overflow="visible"
|
||||
enable-background="new 0 0 139 139"
|
||||
id="Livello_1"
|
||||
xml:space="preserve"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.45+devel"
|
||||
sodipodi:docname="system-help.svgz"
|
||||
inkscape:output_extension="org.inkscape.output.svgz.inkscape"
|
||||
style="overflow:visible"><metadata
|
||||
id="metadata3164"><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
|
||||
style="overflow:visible"><defs
|
||||
id="defs3162"><filter
|
||||
inkscape:collect="always"
|
||||
x="-0.132641"
|
||||
width="1.265282"
|
||||
y="-0.34752154"
|
||||
width="1.265282"
|
||||
height="1.6950431"
|
||||
color-interpolation-filters="sRGB"
|
||||
id="filter3547"><feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="2.7512044"
|
||||
id="feGaussianBlur3549" /></filter><filter
|
||||
inkscape:collect="always"
|
||||
id="feGaussianBlur3549"
|
||||
stdDeviation="2.7512044" /></filter><filter
|
||||
color-interpolation-filters="sRGB"
|
||||
id="filter5097"><feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="2.32"
|
||||
id="feGaussianBlur5099" /></filter><filter
|
||||
inkscape:collect="always"
|
||||
id="feGaussianBlur5099"
|
||||
stdDeviation="2.32" /></filter><filter
|
||||
x="-0.143268"
|
||||
width="1.286536"
|
||||
y="-0.072184406"
|
||||
width="1.286536"
|
||||
height="1.1443688"
|
||||
color-interpolation-filters="sRGB"
|
||||
id="filter5125"><feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="1.91024"
|
||||
id="feGaussianBlur5127" /></filter></defs><sodipodi:namedview
|
||||
inkscape:window-height="697"
|
||||
inkscape:window-width="1024"
|
||||
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"
|
||||
inkscape:zoom="2.9352518"
|
||||
inkscape:cx="99.496726"
|
||||
inkscape:cy="69.329657"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:current-layer="Livello_1"
|
||||
height="128px"
|
||||
width="128px" />
|
||||
id="feGaussianBlur5127"
|
||||
stdDeviation="1.91024" /></filter></defs>
|
||||
<filter
|
||||
color-interpolation-filters="sRGB"
|
||||
id="AI_Sfocatura_4">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4"
|
||||
id="feGaussianBlur3096" />
|
||||
id="feGaussianBlur3096"
|
||||
stdDeviation="4" />
|
||||
</filter>
|
||||
<filter
|
||||
color-interpolation-filters="sRGB"
|
||||
id="AI_Sfocatura_2">
|
||||
<feGaussianBlur
|
||||
stdDeviation="2"
|
||||
id="feGaussianBlur3099" />
|
||||
id="feGaussianBlur3099"
|
||||
stdDeviation="2" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
id="XMLID_12_"
|
||||
cx="69.600098"
|
||||
cy="69.576698"
|
||||
r="58"
|
||||
gradientTransform="matrix(1,0,0,-0.1823,0,134.8566)"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
id="XMLID_12_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,-0.1823,0,134.8566)">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#000000"
|
||||
id="stop3102" />
|
||||
id="stop3102"
|
||||
style="stop-color:#000000;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
id="stop3104" />
|
||||
id="stop3104"
|
||||
style="stop-color:#000000;stop-opacity:0"
|
||||
offset="1" />
|
||||
</radialGradient>
|
||||
<circle
|
||||
sodipodi:ry="58"
|
||||
sodipodi:rx="58"
|
||||
sodipodi:cy="69.599998"
|
||||
sodipodi:cx="69.599998"
|
||||
style="opacity:0.7;fill:#000000;fill-opacity:1;stroke:none;filter:url(#filter5097)"
|
||||
id="circle5091"
|
||||
r="58"
|
||||
cx="69.599998"
|
||||
cy="69.599998"
|
||||
cx="69.599998"
|
||||
transform="matrix(1.0859375,0,0,1.0859375,-3.9093733,-8.2531233)" /><ellipse
|
||||
cx="69.599998"
|
||||
cy="122.173"
|
||||
rx="58"
|
||||
ry="10.573"
|
||||
id="ellipse3106"
|
||||
style="opacity:0.6;fill:url(#XMLID_12_)"
|
||||
sodipodi:cx="69.599998"
|
||||
sodipodi:cy="122.173"
|
||||
sodipodi:rx="58"
|
||||
sodipodi:ry="10.573"
|
||||
transform="translate(-9.9998474e-2,1.9102535)" />
|
||||
r="58"
|
||||
transform="matrix(1.0859375,0,0,1.0859375,-3.9093733,-8.2531233)"
|
||||
id="circle5091"
|
||||
style="opacity:0.7;fill:#000000;fill-opacity:1;stroke:none;filter:url(#filter5097)" />
|
||||
|
||||
<radialGradient
|
||||
id="XMLID_13_"
|
||||
cx="69.600098"
|
||||
cy="69.600098"
|
||||
r="58"
|
||||
id="XMLID_13_"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
offset="0.6154"
|
||||
style="stop-color:#EEEEEE"
|
||||
id="stop3113" />
|
||||
id="stop3113"
|
||||
style="stop-color:#eeeeee;stop-opacity:1"
|
||||
offset="0.61540002" />
|
||||
<stop
|
||||
offset="0.8225"
|
||||
style="stop-color:#DDDDDD"
|
||||
id="stop3115" />
|
||||
id="stop3115"
|
||||
style="stop-color:#dddddd;stop-opacity:1"
|
||||
offset="0.82249999" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop3117" />
|
||||
id="stop3117"
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="1" />
|
||||
</radialGradient>
|
||||
<circle
|
||||
cx="69.599998"
|
||||
cy="69.599998"
|
||||
r="58"
|
||||
transform="matrix(1.0859375,0,0,1.0859375,-3.9093733,-8.2531233)"
|
||||
id="circle3119"
|
||||
style="fill:url(#XMLID_13_)"
|
||||
sodipodi:cx="69.599998"
|
||||
sodipodi:cy="69.599998"
|
||||
sodipodi:rx="58"
|
||||
sodipodi:ry="58"
|
||||
transform="matrix(1.0859375,0,0,1.0859375,-3.9093733,-8.2531233)" />
|
||||
style="fill:url(#XMLID_13_)" />
|
||||
<linearGradient
|
||||
id="XMLID_14_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="27.6001"
|
||||
y1="69.600098"
|
||||
x2="111.6001"
|
||||
y2="69.600098"
|
||||
id="XMLID_14_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0859375,0,0,1.0859375,-3.9093733,-8.2531233)">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#2A94EC"
|
||||
id="stop3122" />
|
||||
id="stop3122"
|
||||
style="stop-color:#2a94ec;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#0057AE"
|
||||
id="stop3124" />
|
||||
id="stop3124"
|
||||
style="stop-color:#0057ae;stop-opacity:1"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<path
|
||||
d="M 26.062502,67.328127 C 26.062502,92.477355 46.522651,112.9375 71.671877,112.9375 C 96.821104,112.9375 117.28125,92.477355 117.28125,67.328127 C 117.28125,42.178901 96.821104,21.718753 71.671877,21.718753 C 46.522651,21.718753 26.062502,42.178901 26.062502,67.328127 z"
|
||||
d="m 26.062502,67.328127 c 0,25.149228 20.460149,45.609373 45.609375,45.609373 25.149227,0 45.609373,-20.460145 45.609373,-45.609373 0,-25.149226 -20.460146,-45.609374 -45.609373,-45.609374 -25.149226,0 -45.609375,20.460148 -45.609375,45.609374 z"
|
||||
id="path3126"
|
||||
style="fill:url(#XMLID_14_)" />
|
||||
<g
|
||||
transform="matrix(1.0859375,0,0,1.0859375,-3.9093733,-8.2531233)"
|
||||
id="circle22111"
|
||||
cy="92"
|
||||
rx="36"
|
||||
ry="36"
|
||||
cx="343.99899"
|
||||
enable-background="new "
|
||||
style="opacity:0.3;filter:url(#filter3547)"
|
||||
transform="matrix(1.0859375,0,0,1.0859375,-3.9093733,-8.2531233)">
|
||||
style="opacity:0.3;filter:url(#filter3547)">
|
||||
<path
|
||||
d="M 77.041,104.759 C 63.767,106.115 50.122,103.11 46.565,98.042 C 43.007,92.976 50.885,87.768 64.16,86.41 C 77.434,85.054 91.079,88.058 94.637,93.126 C 98.193,98.194 90.315,103.401 77.041,104.759 z"
|
||||
d="M 77.041,104.759 C 63.767,106.115 50.122,103.11 46.565,98.042 43.007,92.976 50.885,87.768 64.16,86.41 c 13.274,-1.356 26.919,1.648 30.477,6.716 3.556,5.068 -4.322,10.275 -17.596,11.633 z"
|
||||
id="path3129"
|
||||
style="fill:#a8dde0" />
|
||||
</g>
|
||||
<linearGradient
|
||||
id="circle16776_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="135.5601"
|
||||
y1="417.66461"
|
||||
x2="161.87621"
|
||||
y2="417.66461"
|
||||
id="circle16776_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,1.7280523,1.7280523,0,-650.07477,-218.71693)">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop3132" />
|
||||
id="stop3132"
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
id="stop3134" />
|
||||
id="stop3134"
|
||||
style="stop-color:#ffffff;stop-opacity:0"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<path
|
||||
d="m 71.671877,24.06655 c -21.383195,0 -39.252297,14.70468 -43.558039,34.283047 8.584336,7.792687 24.872313,-3.99082 43.558039,-3.99082 18.685727,0 34.974783,11.783507 43.558033,3.99082 C 110.92417,38.77123 93.056158,24.06655 71.671877,24.06655 z"
|
||||
id="circle16776"
|
||||
enable-background="new "
|
||||
d="M 71.671877,24.06655 C 50.288682,24.06655 32.41958,38.77123 28.113838,58.349597 C 36.698174,66.142284 52.986151,54.358777 71.671877,54.358777 C 90.357604,54.358777 106.64666,66.142284 115.22991,58.349597 C 110.92417,38.77123 93.056158,24.06655 71.671877,24.06655 z"
|
||||
style="opacity:0.8;fill:url(#circle16776_1_)" />
|
||||
<g
|
||||
id="g3137"
|
||||
transform="matrix(1.0859375,0,0,1.0859375,-3.9093733,-8.2531233)">
|
||||
transform="matrix(1.0859375,0,0,1.0859375,-3.9093733,-8.2531233)"
|
||||
id="g3137">
|
||||
<defs
|
||||
id="defs3139"><path
|
||||
id="XMLID_10_"
|
||||
d="M 27.6,69.6 C 27.6,92.759 46.441,111.6 69.6,111.6 C 92.759,111.6 111.6,92.759 111.6,69.6 C 111.6,46.441 92.759,27.6 69.6,27.6 C 46.441,27.6 27.6,46.441 27.6,69.6 z" /></defs>
|
||||
d="m 27.6,69.6 c 0,23.159 18.841,42 42,42 23.159,0 42,-18.841 42,-42 0,-23.159 -18.841,-42 -42,-42 -23.159,0 -42,18.841 -42,42 z"
|
||||
id="XMLID_10_" /></defs>
|
||||
<clipPath
|
||||
id="XMLID_6_">
|
||||
<use
|
||||
xlink:href="#XMLID_10_"
|
||||
id="use3143"
|
||||
x="0"
|
||||
y="0"
|
||||
width="139"
|
||||
height="139" />
|
||||
height="139"
|
||||
xlink:href="#XMLID_10_" />
|
||||
</clipPath>
|
||||
<g
|
||||
clip-path="url(#XMLID_6_)"
|
||||
id="g3145"
|
||||
style="filter:url(#AI_Sfocatura_2)">
|
||||
<path
|
||||
d="M 27.6,69.6 C 27.6,92.759 46.441,111.6 69.6,111.6 C 92.759,111.6 111.6,92.759 111.6,69.6 C 111.6,46.441 92.759,27.6 69.6,27.6 C 46.441,27.6 27.6,46.441 27.6,69.6 z"
|
||||
d="m 27.6,69.6 c 0,23.159 18.841,42 42,42 23.159,0 42,-18.841 42,-42 0,-23.159 -18.841,-42 -42,-42 -23.159,0 -42,18.841 -42,42 z"
|
||||
id="path3147"
|
||||
style="fill:none;stroke:#00316e;stroke-width:2" />
|
||||
</g>
|
||||
@ -240,30 +182,22 @@
|
||||
transform="matrix(1.0859375,0,0,1.1113796,-3.201342,-9.3177223)"
|
||||
id="g5119"
|
||||
style="fill:#00316e;filter:url(#filter5125)"><path
|
||||
style="fill:#00316e"
|
||||
d="M 63.37,80.089 L 63.192,77.746 C 63.012,73.148 64.44,68.462 68.451,63.684 C 71.304,60.26 73.62,57.286 73.62,54.221 C 73.62,51.157 71.571,48.994 67.202,48.903 C 64.173,48.903 60.696,49.895 58.289,51.517 L 55.348,41.784 C 58.556,39.89 63.815,38.088 70.233,38.088 C 81.91,38.088 87.348,44.668 87.348,52.058 C 87.348,58.997 83.069,63.415 79.681,67.289 C 76.472,70.894 75.046,74.41 75.135,78.466 L 75.135,80.088 L 63.37,80.088 L 63.37,80.089 z"
|
||||
id="path5121" /><circle
|
||||
style="fill:#00316e"
|
||||
sodipodi:ry="8"
|
||||
sodipodi:rx="8"
|
||||
sodipodi:cy="93.599998"
|
||||
sodipodi:cx="69.599998"
|
||||
d="m 63.37,80.089 -0.178,-2.343 c -0.18,-4.598 1.248,-9.284 5.259,-14.062 2.853,-3.424 5.169,-6.398 5.169,-9.463 0,-3.064 -2.049,-5.227 -6.418,-5.318 -3.029,0 -6.506,0.992 -8.913,2.614 l -2.941,-9.733 c 3.208,-1.894 8.467,-3.696 14.885,-3.696 11.677,0 17.115,6.58 17.115,13.97 0,6.939 -4.279,11.357 -7.667,15.231 -3.209,3.605 -4.635,7.121 -4.546,11.177 l 0,1.622 -11.765,0 0,0.001 z"
|
||||
id="path5121"
|
||||
style="fill:#00316e" /><circle
|
||||
cx="69.599998"
|
||||
cy="93.599998"
|
||||
r="8"
|
||||
id="circle5123" /></g><g
|
||||
id="g5101"
|
||||
transform="matrix(1.0859375,0,0,1.0859375,-3.201342,-8.2531233)"><path
|
||||
id="circle5123"
|
||||
style="fill:#00316e" /></g><g
|
||||
transform="matrix(1.0859375,0,0,1.0859375,-3.201342,-8.2531233)"
|
||||
id="g5101"><path
|
||||
d="m 63.37,80.089 -0.178,-2.343 c -0.18,-4.598 1.248,-9.284 5.259,-14.062 2.853,-3.424 5.169,-6.398 5.169,-9.463 0,-3.064 -2.049,-5.227 -6.418,-5.318 -3.029,0 -6.506,0.992 -8.913,2.614 l -2.941,-9.733 c 3.208,-1.894 8.467,-3.696 14.885,-3.696 11.677,0 17.115,6.58 17.115,13.97 0,6.939 -4.279,11.357 -7.667,15.231 -3.209,3.605 -4.635,7.121 -4.546,11.177 l 0,1.622 -11.765,0 0,0.001 z"
|
||||
id="path3157"
|
||||
d="M 63.37,80.089 L 63.192,77.746 C 63.012,73.148 64.44,68.462 68.451,63.684 C 71.304,60.26 73.62,57.286 73.62,54.221 C 73.62,51.157 71.571,48.994 67.202,48.903 C 64.173,48.903 60.696,49.895 58.289,51.517 L 55.348,41.784 C 58.556,39.89 63.815,38.088 70.233,38.088 C 81.91,38.088 87.348,44.668 87.348,52.058 C 87.348,58.997 83.069,63.415 79.681,67.289 C 76.472,70.894 75.046,74.41 75.135,78.466 L 75.135,80.088 L 63.37,80.088 L 63.37,80.089 z"
|
||||
style="fill:#ffffff" /><circle
|
||||
id="circle3159"
|
||||
r="8"
|
||||
cy="93.599998"
|
||||
cx="69.599998"
|
||||
sodipodi:cx="69.599998"
|
||||
sodipodi:cy="93.599998"
|
||||
sodipodi:rx="8"
|
||||
sodipodi:ry="8"
|
||||
cy="93.599998"
|
||||
r="8"
|
||||
id="circle3159"
|
||||
style="fill:#ffffff" /></g>
|
||||
</svg>
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 6.3 KiB |
BIN
resources/images/lt.png
Normal file
BIN
resources/images/lt.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
resources/images/news/elpais_impreso.png
Normal file
BIN
resources/images/news/elpais_impreso.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 717 B |
@ -12,9 +12,9 @@ class AssociatedPress(BasicNewsRecipe):
|
||||
|
||||
max_articles_per_feed = 15
|
||||
html2lrf_options = ['--force-page-break-before-tag="chapter"']
|
||||
|
||||
|
||||
preprocess_regexps = [ (re.compile(i[0], re.IGNORECASE | re.DOTALL), i[1]) for i in
|
||||
|
||||
|
||||
preprocess_regexps = [ (re.compile(i[0], re.IGNORECASE | re.DOTALL), i[1]) for i in
|
||||
[
|
||||
(r'<HEAD>.*?</HEAD>' , lambda match : '<HEAD></HEAD>'),
|
||||
(r'<body class="apple-rss-no-unread-mode" onLoad="setup(null)">.*?<!-- start Entries -->', lambda match : '<body>'),
|
||||
@ -25,10 +25,10 @@ class AssociatedPress(BasicNewsRecipe):
|
||||
(r'<p class="ap-story-p">', lambda match : '<p>'),
|
||||
(r'Learn more about our <a href="http://apdigitalnews.com/privacy.html">Privacy Policy</a>.*?</body>', lambda match : '</body>'),
|
||||
]
|
||||
]
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
feeds = [ ('AP Headlines', 'http://hosted.ap.org/lineups/TOPHEADS-rss_2.0.xml?SITE=ORAST&SECTION=HOME'),
|
||||
('AP US News', 'http://hosted.ap.org/lineups/USHEADS-rss_2.0.xml?SITE=CAVIC&SECTION=HOME'),
|
||||
('AP World News', 'http://hosted.ap.org/lineups/WORLDHEADS-rss_2.0.xml?SITE=SCAND&SECTION=HOME'),
|
||||
@ -38,4 +38,4 @@ class AssociatedPress(BasicNewsRecipe):
|
||||
('AP Health News', 'http://hosted.ap.org/lineups/HEALTHHEADS-rss_2.0.xml?SITE=FLDAY&SECTION=HOME'),
|
||||
('AP Science News', 'http://hosted.ap.org/lineups/SCIENCEHEADS-rss_2.0.xml?SITE=OHCIN&SECTION=HOME'),
|
||||
('AP Strange News', 'http://hosted.ap.org/lineups/STRANGEHEADS-rss_2.0.xml?SITE=WCNC&SECTION=HOME'),
|
||||
]
|
||||
]
|
||||
|
86
resources/recipes/elpais_impreso.recipe
Normal file
86
resources/recipes/elpais_impreso.recipe
Normal file
@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.elpais.com/diario/
|
||||
'''
|
||||
|
||||
from calibre import strftime
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class ElPaisImpresa(BasicNewsRecipe):
|
||||
title = 'El País - edicion impresa'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'el periodico global en Español'
|
||||
publisher = 'EDICIONES EL PAIS, S.L.'
|
||||
category = 'news, politics,Spain,actualidad,noticias,informacion,videos,fotografias,audios,graficos,nacional,internacional,deportes,economia,tecnologia,cultura,gente,television,sociedad,opinion,blogs,foros,chats,encuestas,entrevistas,participacion'
|
||||
no_stylesheets = True
|
||||
encoding = 'latin1'
|
||||
use_embedded_content = False
|
||||
language = 'es'
|
||||
publication_type = 'newspaper'
|
||||
masthead_url = 'http://www.elpais.com/im/tit_logo_global.gif'
|
||||
index = 'http://www.elpais.com/diario/'
|
||||
extra_css = ' p{text-align: justify} body{ text-align: left; font-family: Georgia,"Times New Roman",Times,serif } h2{font-family: Arial,Helvetica,sans-serif} img{margin-bottom: 0.4em} '
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
feeds = [
|
||||
(u'Internacional' , index + u'internacional/' )
|
||||
,(u'España' , index + u'espana/' )
|
||||
,(u'Economia' , index + u'economia/' )
|
||||
,(u'Opinion' , index + u'opinion/' )
|
||||
,(u'Viñetas' , index + u'vineta/' )
|
||||
,(u'Sociedad' , index + u'sociedad/' )
|
||||
,(u'Cultura' , index + u'cultura/' )
|
||||
,(u'Tendencias' , index + u'tendencias/' )
|
||||
,(u'Gente' , index + u'gente/' )
|
||||
,(u'Obituarios' , index + u'obituarios/' )
|
||||
,(u'Deportes' , index + u'deportes/' )
|
||||
,(u'Pantallas' , index + u'radioytv/' )
|
||||
,(u'Ultima' , index + u'ultima/' )
|
||||
,(u'Educacion' , index + u'educacion/' )
|
||||
,(u'Saludo' , index + u'salud/' )
|
||||
,(u'Ciberpais' , index + u'ciberpais/' )
|
||||
,(u'EP3' , index + u'ep3/' )
|
||||
,(u'Cine' , index + u'cine/' )
|
||||
,(u'Babelia' , index + u'babelia/' )
|
||||
,(u'El viajero' , index + u'viajero/' )
|
||||
,(u'Negocios' , index + u'negocios/' )
|
||||
,(u'Domingo' , index + u'domingo/' )
|
||||
,(u'El Pais semanal' , index + u'eps/' )
|
||||
,(u'Quadern Catalunya' , index + u'quadern-catalunya/' )
|
||||
]
|
||||
|
||||
keep_only_tags=[dict(attrs={'class':['cabecera_noticia','contenido_noticia']})]
|
||||
remove_attributes=['width','height']
|
||||
remove_tags=[dict(name='link')]
|
||||
|
||||
def parse_index(self):
|
||||
totalfeeds = []
|
||||
lfeeds = self.get_feeds()
|
||||
for feedobj in lfeeds:
|
||||
feedtitle, feedurl = feedobj
|
||||
self.report_progress(0, _('Fetching feed')+' %s...'%(feedtitle if feedtitle else feedurl))
|
||||
articles = []
|
||||
soup = self.index_to_soup(feedurl)
|
||||
for item in soup.findAll('a',attrs={'class':['g19r003','g19i003','g17r003','g17i003']}):
|
||||
url = 'http://www.elpais.com' + item['href'].rpartition('/')[0]
|
||||
title = self.tag_to_string(item)
|
||||
date = strftime(self.timefmt)
|
||||
articles.append({
|
||||
'title' :title
|
||||
,'date' :date
|
||||
,'url' :url
|
||||
,'description':''
|
||||
})
|
||||
totalfeeds.append((feedtitle, articles))
|
||||
return totalfeeds
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?print=1'
|
@ -4,29 +4,27 @@ from calibre import __appname__
|
||||
|
||||
class GoogleReader(BasicNewsRecipe):
|
||||
title = 'Google Reader'
|
||||
description = 'This recipe downloads feeds you have tagged from your Google Reader account.'
|
||||
description = 'This recipe fetches from your Google Reader account unread Starred items and unread Feeds you have placed in a folder via the manage subscriptions feature.'
|
||||
needs_subscription = True
|
||||
__author__ = 'davec'
|
||||
__author__ = 'davec, rollercoaster, Starson17'
|
||||
base_url = 'http://www.google.com/reader/atom/'
|
||||
max_articles_per_feed = 50
|
||||
oldest_article = 365
|
||||
max_articles_per_feed = 250
|
||||
get_options = '?n=%d&xt=user/-/state/com.google/read' % max_articles_per_feed
|
||||
use_embedded_content = True
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
|
||||
br = BasicNewsRecipe.get_browser(self)
|
||||
if self.username is not None and self.password is not None:
|
||||
request = urllib.urlencode([('Email', self.username), ('Passwd', self.password),
|
||||
('service', 'reader'), ('source', __appname__)])
|
||||
('service', 'reader'), ('accountType', 'HOSTED_OR_GOOGLE'), ('source', __appname__)])
|
||||
response = br.open('https://www.google.com/accounts/ClientLogin', request)
|
||||
sid = re.search('SID=(\S*)', response.read()).group(1)
|
||||
|
||||
auth = re.search('Auth=(\S*)', response.read()).group(1)
|
||||
cookies = mechanize.CookieJar()
|
||||
br = mechanize.build_opener(mechanize.HTTPCookieProcessor(cookies))
|
||||
cookies.set_cookie(mechanize.Cookie(None, 'SID', sid, None, False, '.google.com', True, True, '/', True, False, None, True, '', '', None))
|
||||
br.addheaders = [('Authorization', 'GoogleLogin auth='+auth)]
|
||||
return br
|
||||
|
||||
|
||||
def get_feeds(self):
|
||||
feeds = []
|
||||
soup = self.index_to_soup('http://www.google.com/reader/api/0/tag/list')
|
||||
|
@ -3,10 +3,10 @@ from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
from calibre import __appname__
|
||||
|
||||
class GoogleReaderUber(BasicNewsRecipe):
|
||||
title = 'Google Reader Uber'
|
||||
description = 'This recipe downloads all unread feedsfrom your Google Reader account.'
|
||||
title = 'Google Reader uber'
|
||||
description = 'Fetches all feeds from your Google Reader account including the uncategorized items.'
|
||||
needs_subscription = True
|
||||
__author__ = 'rollercoaster, davec'
|
||||
__author__ = 'davec, rollercoaster, Starson17'
|
||||
base_url = 'http://www.google.com/reader/atom/'
|
||||
oldest_article = 365
|
||||
max_articles_per_feed = 250
|
||||
@ -14,20 +14,17 @@ class GoogleReaderUber(BasicNewsRecipe):
|
||||
use_embedded_content = True
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
|
||||
br = BasicNewsRecipe.get_browser(self)
|
||||
if self.username is not None and self.password is not None:
|
||||
request = urllib.urlencode([('Email', self.username), ('Passwd', self.password),
|
||||
('service', 'reader'), ('source', __appname__)])
|
||||
('service', 'reader'), ('accountType', 'HOSTED_OR_GOOGLE'), ('source', __appname__)])
|
||||
response = br.open('https://www.google.com/accounts/ClientLogin', request)
|
||||
sid = re.search('SID=(\S*)', response.read()).group(1)
|
||||
|
||||
auth = re.search('Auth=(\S*)', response.read()).group(1)
|
||||
cookies = mechanize.CookieJar()
|
||||
br = mechanize.build_opener(mechanize.HTTPCookieProcessor(cookies))
|
||||
cookies.set_cookie(mechanize.Cookie(None, 'SID', sid, None, False, '.google.com', True, True, '/', True, False, None, True, '', '', None))
|
||||
br.addheaders = [('Authorization', 'GoogleLogin auth='+auth)]
|
||||
return br
|
||||
|
||||
|
||||
def get_feeds(self):
|
||||
feeds = []
|
||||
soup = self.index_to_soup('http://www.google.com/reader/api/0/tag/list')
|
||||
|
38
resources/recipes/orlando_sentinel.recipe
Normal file
38
resources/recipes/orlando_sentinel.recipe
Normal file
@ -0,0 +1,38 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1279258912(BasicNewsRecipe):
|
||||
title = u'Orlando Sentinel'
|
||||
oldest_article = 3
|
||||
max_articles_per_feed = 100
|
||||
|
||||
feeds = [
|
||||
(u'News', u'http://feeds.feedburner.com/orlandosentinel/news'),
|
||||
(u'Opinion', u'http://feeds.feedburner.com/orlandosentinel/news/opinion'),
|
||||
(u'Business', u'http://feeds.feedburner.com/orlandosentinel/business'),
|
||||
(u'Technology', u'http://feeds.feedburner.com/orlandosentinel/technology'),
|
||||
(u'Space and Science', u'http://feeds.feedburner.com/orlandosentinel/news/space'),
|
||||
(u'Entertainment', u'http://feeds.feedburner.com/orlandosentinel/entertainment'),
|
||||
(u'Life and Family', u'http://feeds.feedburner.com/orlandosentinel/features/lifestyle'),
|
||||
]
|
||||
__author__ = 'rty'
|
||||
pubisher = 'OrlandoSentinel.com'
|
||||
description = 'Orlando, Florida, Newspaper'
|
||||
category = 'News, Orlando, Florida'
|
||||
|
||||
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
no_stylesheets = True
|
||||
language = 'en'
|
||||
encoding = 'utf-8'
|
||||
conversion_options = {'linearize_tables':True}
|
||||
masthead_url = 'http://www.orlandosentinel.com/media/graphic/2009-07/46844851.gif'
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':'story'})
|
||||
]
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':['articlerail','tools','comment-group','clearfix']}),
|
||||
]
|
||||
remove_tags_after = [
|
||||
dict(name='p', attrs={'class':'copyright'}),
|
||||
]
|
34
resources/recipes/waco_tribune.recipe
Normal file
34
resources/recipes/waco_tribune.recipe
Normal file
@ -0,0 +1,34 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1278773519(BasicNewsRecipe):
|
||||
title = u'Waco Tribune Herald'
|
||||
__author__ = 'rty'
|
||||
pubisher = 'A Robinson Media Company'
|
||||
description = 'Waco, Texas, Newspaper'
|
||||
category = 'News, Texas, Waco'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
|
||||
feeds = [
|
||||
(u'News', u'http://www.wacotrib.com/news/index.rss2'),
|
||||
(u'Sports', u'http://www.wacotrib.com/sports/index.rss2'),
|
||||
(u'AccessWaco', u'http://www.wacotrib.com/accesswaco/index.rss2'),
|
||||
(u'Opinions', u'http://www.wacotrib.com/opinion/index.rss2')
|
||||
]
|
||||
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
no_stylesheets = True
|
||||
language = 'en'
|
||||
encoding = 'utf-8'
|
||||
conversion_options = {'linearize_tables':True}
|
||||
masthead_url = 'http://media.wacotrib.com/designimages/wacotrib_logo.jpg'
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':'twoColumn left'}),
|
||||
]
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':'right blueLinks'}),
|
||||
]
|
||||
remove_tags_after = [
|
||||
dict(name='div', attrs={'class':'dottedRule'}),
|
||||
]
|
@ -111,7 +111,6 @@
|
||||
or (@shadow = 'true')
|
||||
or (@hidden = 'true')
|
||||
or (@outline = 'true')
|
||||
|
||||
">
|
||||
<emph rend = "paragraph-emph">
|
||||
<xsl:apply-templates/>
|
||||
@ -277,6 +276,26 @@
|
||||
<xsl:value-of select="count(preceding::rtf:footnote) + 1"/>
|
||||
<xsl:text>]</xsl:text>
|
||||
</xsl:when>
|
||||
<xsl:when test="(@superscript = 'true')">
|
||||
<xsl:element name="sup">
|
||||
<xsl:element name="span">
|
||||
<xsl:attribute name="class">
|
||||
<c:inline-class/>
|
||||
</xsl:attribute>
|
||||
<xsl:apply-templates/>
|
||||
</xsl:element>
|
||||
</xsl:element>
|
||||
</xsl:when>
|
||||
<xsl:when test="(@underscript = 'true')">
|
||||
<xsl:element name="sub">
|
||||
<xsl:element name="span">
|
||||
<xsl:attribute name="class">
|
||||
<c:inline-class/>
|
||||
</xsl:attribute>
|
||||
<xsl:apply-templates/>
|
||||
</xsl:element>
|
||||
</xsl:element>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:element name="span">
|
||||
<xsl:attribute name="class">
|
||||
|
@ -40,6 +40,7 @@ class LinuxFreeze(Command):
|
||||
'/usr/bin/pdftohtml',
|
||||
'/usr/lib/libwmflite-0.2.so.7',
|
||||
'/usr/lib/liblcms.so.1',
|
||||
'/usr/lib/liblcms2.so.2',
|
||||
'/usr/lib/libstlport.so.5.1',
|
||||
'/tmp/calibre-mount-helper',
|
||||
'/usr/lib/libunrar.so',
|
||||
@ -50,10 +51,9 @@ class LinuxFreeze(Command):
|
||||
'/usr/lib/libpodofo.so.0.8.1',
|
||||
'/lib/libz.so.1',
|
||||
'/lib/libuuid.so.1',
|
||||
'/usr/lib/libtiff.so.3',
|
||||
'/usr/lib/libtiff.so.5',
|
||||
'/lib/libbz2.so.1',
|
||||
'/usr/lib/libpoppler.so.5',
|
||||
'/usr/lib/libpoppler-qt4.so.3',
|
||||
'/usr/lib/libpoppler.so.6',
|
||||
'/usr/lib/libxml2.so.2',
|
||||
'/usr/lib/libopenjpeg.so.2',
|
||||
'/usr/lib/libxslt.so.1',
|
||||
@ -62,10 +62,10 @@ class LinuxFreeze(Command):
|
||||
'/usr/lib/libgthread-2.0.so.0',
|
||||
stdcpp,
|
||||
ffi,
|
||||
'/usr/lib/libpng12.so.0',
|
||||
'/usr/lib/libpng14.so.14',
|
||||
'/usr/lib/libexslt.so.0',
|
||||
'/usr/lib/libMagickWand.so.2',
|
||||
'/usr/lib/libMagickCore.so.2',
|
||||
'/usr/lib/libMagickWand.so.3',
|
||||
'/usr/lib/libMagickCore.so.3',
|
||||
'/usr/lib/libgcrypt.so.11',
|
||||
'/usr/lib/libgpg-error.so.0',
|
||||
'/usr/lib/libphonon.so.4',
|
||||
|
@ -13,7 +13,7 @@ from setup import Command, modules, functions, basenames, __version__, \
|
||||
from setup.build_environment import msvc, MT, RC
|
||||
from setup.installer.windows.wix import WixMixIn
|
||||
|
||||
QT_DIR = 'C:\\Qt\\4.6.0'
|
||||
QT_DIR = 'C:\\Qt\\4.6.3'
|
||||
QT_DLLS = ['Core', 'Gui', 'Network', 'Svg', 'WebKit', 'Xml', 'XmlPatterns']
|
||||
LIBUSB_DIR = 'C:\\libusb'
|
||||
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
|
||||
|
@ -361,6 +361,8 @@ def strftime(fmt, t=None):
|
||||
before 1900 '''
|
||||
if t is None:
|
||||
t = time.localtime()
|
||||
if hasattr(t, 'timetuple'):
|
||||
t = t.timetuple()
|
||||
early_year = t[0] < 1900
|
||||
if early_year:
|
||||
replacement = 1900 if t[0]%4 == 0 else 1901
|
||||
|
@ -446,7 +446,7 @@ from calibre.devices.eb600.driver import EB600, COOL_ER, SHINEBOOK, \
|
||||
BOOQ, ELONEX, POCKETBOOK301, MENTOR
|
||||
from calibre.devices.iliad.driver import ILIAD
|
||||
from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
|
||||
from calibre.devices.jetbook.driver import JETBOOK
|
||||
from calibre.devices.jetbook.driver import JETBOOK, MIBUK
|
||||
from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX
|
||||
from calibre.devices.nook.driver import NOOK
|
||||
from calibre.devices.prs505.driver import PRS505
|
||||
@ -467,12 +467,12 @@ from calibre.devices.kobo.driver import KOBO
|
||||
from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB, Amazon, \
|
||||
LibraryThing
|
||||
from calibre.ebooks.metadata.douban import DoubanBooks
|
||||
from calibre.library.catalog import CSV_XML, EPUB_MOBI
|
||||
from calibre.library.catalog import CSV_XML, EPUB_MOBI, BIBTEX
|
||||
from calibre.ebooks.epub.fix.unmanifested import Unmanifested
|
||||
from calibre.ebooks.epub.fix.epubcheck import Epubcheck
|
||||
|
||||
plugins = [HTML2ZIP, PML2PMLZ, ArchiveExtract, GoogleBooks, ISBNDB, Amazon,
|
||||
LibraryThing, DoubanBooks, CSV_XML, EPUB_MOBI, Unmanifested, Epubcheck]
|
||||
LibraryThing, DoubanBooks, CSV_XML, EPUB_MOBI, BIBTEX, Unmanifested, Epubcheck]
|
||||
plugins += [
|
||||
ComicInput,
|
||||
EPUBInput,
|
||||
@ -517,6 +517,7 @@ plugins += [
|
||||
IREXDR1000,
|
||||
IREXDR800,
|
||||
JETBOOK,
|
||||
MIBUK,
|
||||
SHINEBOOK,
|
||||
POCKETBOOK360,
|
||||
POCKETBOOK301,
|
||||
|
@ -30,7 +30,8 @@ class ANDROID(USBMS):
|
||||
0x18d1 : { 0x4e11 : [0x0100, 0x226], 0x4e12: [0x0100, 0x226]},
|
||||
|
||||
# Samsung
|
||||
0x04e8 : { 0x681d : [0x0222, 0x0400], 0x681c : [0x0222, 0x0224]},
|
||||
0x04e8 : { 0x681d : [0x0222, 0x0400],
|
||||
0x681c : [0x0222, 0x0224, 0x0400]},
|
||||
|
||||
# Acer
|
||||
0x502 : { 0x3203 : [0x0100]},
|
||||
@ -70,6 +71,16 @@ class ANDROID(USBMS):
|
||||
dirs = [x.strip() for x in dirs.split(',')]
|
||||
self.EBOOK_DIR_MAIN = dirs
|
||||
|
||||
def get_main_ebook_dir(self, for_upload=False):
|
||||
dirs = self.EBOOK_DIR_MAIN
|
||||
if not for_upload:
|
||||
def aldiko_tweak(x):
|
||||
return 'eBooks' if x == 'eBooks/import' else x
|
||||
if isinstance(dirs, basestring):
|
||||
dirs = [dirs]
|
||||
dirs = list(map(aldiko_tweak, dirs))
|
||||
return dirs
|
||||
|
||||
class S60(USBMS):
|
||||
|
||||
name = 'S60 driver'
|
||||
|
@ -80,3 +80,21 @@ class JETBOOK(USBMS):
|
||||
|
||||
return mi
|
||||
|
||||
class MIBUK(USBMS):
|
||||
|
||||
name = 'MiBuk Wolder Device Interface'
|
||||
description = _('Communicate with the MiBuk Wolder reader.')
|
||||
author = 'Kovid Goyal'
|
||||
supported_platforms = ['windows', 'osx', 'linux']
|
||||
|
||||
FORMATS = ['epub', 'mobi', 'prc', 'fb2', 'txt', 'rtf', 'pdf']
|
||||
|
||||
VENDOR_ID = [0x0525]
|
||||
PRODUCT_ID = [0xa4a5]
|
||||
BCD = [0x314]
|
||||
SUPPORTS_SUB_DIRS = True
|
||||
|
||||
VENDOR_NAME = 'LINUX'
|
||||
WINDOWS_MAIN_MEM = 'WOLDERMIBUK'
|
||||
|
||||
|
||||
|
@ -10,10 +10,10 @@ from base64 import b64decode
|
||||
from uuid import uuid4
|
||||
from lxml import etree
|
||||
|
||||
from calibre import prints, guess_type
|
||||
from calibre import prints, guess_type, isbytestring
|
||||
from calibre.devices.errors import DeviceError
|
||||
from calibre.devices.usbms.driver import debug_print
|
||||
from calibre.constants import DEBUG
|
||||
from calibre.constants import DEBUG, preferred_encoding
|
||||
from calibre.ebooks.chardet import xml_to_unicode
|
||||
from calibre.ebooks.metadata import authors_to_string, title_sort
|
||||
|
||||
@ -473,6 +473,13 @@ class XMLCache(object):
|
||||
# if the case of a tie, and hope it is right.
|
||||
timestamp = os.path.getmtime(path)
|
||||
rec_date = record.get('date', None)
|
||||
|
||||
def clean(x):
|
||||
if isbytestring(x):
|
||||
x = x.decode(preferred_encoding, 'replace')
|
||||
x.replace(u'\0', '')
|
||||
return x
|
||||
|
||||
if not getattr(book, '_new_book', False): # book is not new
|
||||
if strftime(timestamp, zone=time.gmtime) == rec_date:
|
||||
gtz_count += 1
|
||||
@ -486,19 +493,19 @@ class XMLCache(object):
|
||||
tz = time.gmtime
|
||||
debug_print("Using GMT TZ for new book", book.lpath)
|
||||
date = strftime(timestamp, zone=tz)
|
||||
record.set('date', date)
|
||||
record.set('date', clean(date))
|
||||
|
||||
record.set('size', str(os.stat(path).st_size))
|
||||
record.set('size', clean(str(os.stat(path).st_size)))
|
||||
title = book.title if book.title else _('Unknown')
|
||||
record.set('title', title)
|
||||
record.set('title', clean(title))
|
||||
ts = book.title_sort
|
||||
if not ts:
|
||||
ts = title_sort(title)
|
||||
record.set('titleSorter', ts)
|
||||
record.set('titleSorter', clean(ts))
|
||||
if self.use_author_sort and book.author_sort is not None:
|
||||
record.set('author', book.author_sort)
|
||||
record.set('author', clean(book.author_sort))
|
||||
else:
|
||||
record.set('author', authors_to_string(book.authors))
|
||||
record.set('author', clean(authors_to_string(book.authors)))
|
||||
ext = os.path.splitext(path)[1]
|
||||
if ext:
|
||||
ext = ext[1:].lower()
|
||||
@ -506,7 +513,7 @@ class XMLCache(object):
|
||||
if mime is None:
|
||||
mime = guess_type('a.'+ext)[0]
|
||||
if mime is not None:
|
||||
record.set('mime', mime)
|
||||
record.set('mime', clean(mime))
|
||||
if 'sourceid' not in record.attrib:
|
||||
record.set('sourceid', '1')
|
||||
if 'id' not in record.attrib:
|
||||
|
@ -98,6 +98,9 @@ class LinuxScanner(object):
|
||||
|
||||
def __call__(self):
|
||||
ans = set([])
|
||||
if not self.ok:
|
||||
raise RuntimeError('DeviceScanner requires the /sys filesystem to work.')
|
||||
|
||||
for x in os.listdir(self.base):
|
||||
base = os.path.join(self.base, x)
|
||||
ven = os.path.join(base, 'idVendor')
|
||||
@ -145,8 +148,6 @@ class DeviceScanner(object):
|
||||
def __init__(self, *args):
|
||||
if isosx and osx_scanner is None:
|
||||
raise RuntimeError('The Python extension usbobserver must be available on OS X.')
|
||||
if islinux and not linux_scanner.ok:
|
||||
raise RuntimeError('DeviceScanner requires the /sys filesystem to work.')
|
||||
self.scanner = win_scanner if iswindows else osx_scanner if isosx else linux_scanner
|
||||
self.devices = []
|
||||
|
||||
|
@ -72,13 +72,13 @@ class Book(MetaInformation):
|
||||
def thumbnail(self):
|
||||
return None
|
||||
|
||||
def smart_update(self, other):
|
||||
def smart_update(self, other, replace_metadata=False):
|
||||
'''
|
||||
Merge the information in C{other} into self. In case of conflicts, the information
|
||||
in C{other} takes precedence, unless the information in C{other} is NULL.
|
||||
'''
|
||||
|
||||
MetaInformation.smart_update(self, other, replace_tags=True)
|
||||
MetaInformation.smart_update(self, other, replace_metadata)
|
||||
|
||||
for attr in self.BOOK_ATTRS:
|
||||
if hasattr(other, attr):
|
||||
@ -116,7 +116,7 @@ class BookList(_BookList):
|
||||
self.append(book)
|
||||
return True
|
||||
if replace_metadata:
|
||||
self[b].smart_update(book)
|
||||
self[b].smart_update(book, replace_metadata=True)
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -132,6 +132,8 @@ class CollectionsBookList(BookList):
|
||||
return True
|
||||
|
||||
def get_collections(self, collection_attributes):
|
||||
from calibre.devices.usbms.driver import debug_print
|
||||
debug_print('Starting get_collections:', prefs['manage_device_metadata'])
|
||||
collections = {}
|
||||
series_categories = set([])
|
||||
# This map of sets is used to avoid linear searches when testing for
|
||||
@ -146,14 +148,19 @@ class CollectionsBookList(BookList):
|
||||
# book in all existing collections. Do not add any new ones.
|
||||
attrs = ['device_collections']
|
||||
if getattr(book, '_new_book', False):
|
||||
if prefs['preserve_user_collections']:
|
||||
if prefs['manage_device_metadata'] == 'manual':
|
||||
# Ensure that the book is in all the book's existing
|
||||
# collections plus all metadata collections
|
||||
attrs += collection_attributes
|
||||
else:
|
||||
# The book's existing collections are ignored. Put the book
|
||||
# in collections defined by its metadata.
|
||||
# For new books, both 'on_send' and 'on_connect' do the same
|
||||
# thing. The book's existing collections are ignored. Put
|
||||
# the book in collections defined by its metadata.
|
||||
attrs = collection_attributes
|
||||
elif prefs['manage_device_metadata'] == 'on_connect':
|
||||
# For existing books, modify the collections only if the user
|
||||
# specified 'on_connect'
|
||||
attrs = collection_attributes
|
||||
for attr in attrs:
|
||||
attr = attr.strip()
|
||||
val = getattr(book, attr, None)
|
||||
|
@ -732,7 +732,7 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
traceback.print_exc()
|
||||
self._main_prefix = self._card_a_prefix = self._card_b_prefix = None
|
||||
|
||||
def get_main_ebook_dir(self):
|
||||
def get_main_ebook_dir(self, for_upload=False):
|
||||
return self.EBOOK_DIR_MAIN
|
||||
|
||||
def _sanity_check(self, on_card, files):
|
||||
@ -750,7 +750,7 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
path = os.path.join(self._card_b_prefix,
|
||||
*(self.EBOOK_DIR_CARD_B.split('/')))
|
||||
else:
|
||||
candidates = self.get_main_ebook_dir()
|
||||
candidates = self.get_main_ebook_dir(for_upload=True)
|
||||
if isinstance(candidates, basestring):
|
||||
candidates = [candidates]
|
||||
candidates = [
|
||||
|
@ -58,7 +58,7 @@ class USBMS(CLI, Device):
|
||||
|
||||
debug_print ('USBMS: Fetching list of books from device. oncard=', oncard)
|
||||
|
||||
dummy_bl = BookList(None, None, None)
|
||||
dummy_bl = self.booklist_class(None, None, None)
|
||||
|
||||
if oncard == 'carda' and not self._card_a_prefix:
|
||||
self.report_progress(1.0, _('Getting list of books on device...'))
|
||||
@ -78,6 +78,8 @@ class USBMS(CLI, Device):
|
||||
self.EBOOK_DIR_CARD_B if oncard == 'cardb' else \
|
||||
self.get_main_ebook_dir()
|
||||
|
||||
debug_print ('USBMS: dirs are:', prefix, ebook_dirs)
|
||||
|
||||
# get the metadata cache
|
||||
bl = self.booklist_class(oncard, prefix, self.settings)
|
||||
need_sync = self.parse_metadata_cache(bl, prefix, self.METADATA_CACHE)
|
||||
|
@ -5,7 +5,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, posixpath, urllib, sys
|
||||
import os, posixpath, urllib, sys, re
|
||||
|
||||
from lxml import etree
|
||||
|
||||
@ -160,8 +160,26 @@ class Container(object):
|
||||
mt = mimetype.lower()
|
||||
if mt.endswith('+xml'):
|
||||
parser = etree.XMLParser(no_network=True, huge_tree=not iswindows)
|
||||
return etree.fromstring(xml_to_unicode(raw,
|
||||
strip_encoding_pats=True, assume_utf8=True)[0], parser=parser)
|
||||
raw = xml_to_unicode(raw,
|
||||
strip_encoding_pats=True, assume_utf8=True,
|
||||
resolve_entities=True)[0].strip()
|
||||
idx = raw.find('<html')
|
||||
if idx == -1:
|
||||
idx = raw.find('<HTML')
|
||||
if idx > -1:
|
||||
pre = raw[:idx]
|
||||
raw = raw[idx:]
|
||||
if '<!DOCTYPE' in pre:
|
||||
user_entities = {}
|
||||
for match in re.finditer(r'<!ENTITY\s+(\S+)\s+([^>]+)', pre):
|
||||
val = match.group(2)
|
||||
if val.startswith('"') and val.endswith('"'):
|
||||
val = val[1:-1]
|
||||
user_entities[match.group(1)] = val
|
||||
if user_entities:
|
||||
pat = re.compile(r'&(%s);'%('|'.join(user_entities.keys())))
|
||||
raw = pat.sub(lambda m:user_entities[m.group(1)], raw)
|
||||
return etree.fromstring(raw, parser=parser)
|
||||
return raw
|
||||
|
||||
def write(self, path):
|
||||
|
@ -21,7 +21,7 @@ class Epubcheck(ePubFixer):
|
||||
def long_description(self):
|
||||
return _('Workarounds for bugs in the latest release of epubcheck. '
|
||||
'epubcheck reports many things as errors that are not '
|
||||
'actually errors. %prog will try to detect these and replace '
|
||||
'actually errors. epub-fix will try to detect these and replace '
|
||||
'them with constructs that epubcheck likes. This may cause '
|
||||
'significant changes to your epub, complain to the epubcheck '
|
||||
'project.')
|
||||
|
@ -18,7 +18,7 @@ class Unmanifested(ePubFixer):
|
||||
|
||||
@property
|
||||
def long_description(self):
|
||||
return _('Fix unmanifested files. %prog can either add them to '
|
||||
return _('Fix unmanifested files. epub-fix can either add them to '
|
||||
'the manifest or delete them as specified by the '
|
||||
'delete unmanifested option.')
|
||||
|
||||
|
@ -268,10 +268,12 @@ class MetaInformation(object):
|
||||
):
|
||||
prints(x, getattr(self, x, 'None'))
|
||||
|
||||
def smart_update(self, mi, replace_tags=False):
|
||||
def smart_update(self, mi, replace_metadata=False):
|
||||
'''
|
||||
Merge the information in C{mi} into self. In case of conflicts, the information
|
||||
in C{mi} takes precedence, unless the information in mi is NULL.
|
||||
Merge the information in C{mi} into self. In case of conflicts, the
|
||||
information in C{mi} takes precedence, unless the information in mi is
|
||||
NULL. If replace_metadata is True, then the information in mi always
|
||||
takes precedence.
|
||||
'''
|
||||
if mi.title and mi.title != _('Unknown'):
|
||||
self.title = mi.title
|
||||
@ -285,13 +287,16 @@ class MetaInformation(object):
|
||||
'cover', 'guide', 'book_producer',
|
||||
'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate', 'rights',
|
||||
'publication_type', 'uuid'):
|
||||
if hasattr(mi, attr):
|
||||
if replace_metadata:
|
||||
setattr(self, attr, getattr(mi, attr, 1.0 if \
|
||||
attr == 'series_index' else None))
|
||||
elif hasattr(mi, attr):
|
||||
val = getattr(mi, attr)
|
||||
if val is not None:
|
||||
setattr(self, attr, val)
|
||||
|
||||
if mi.tags:
|
||||
if replace_tags:
|
||||
if replace_metadata:
|
||||
self.tags = mi.tags
|
||||
else:
|
||||
self.tags += mi.tags
|
||||
|
84
src/calibre/gui2/catalog/catalog_bibtex.py
Normal file
84
src/calibre/gui2/catalog/catalog_bibtex.py
Normal file
@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
from calibre.gui2 import gprefs
|
||||
from calibre.gui2.catalog.catalog_bibtex_ui import Ui_Form
|
||||
from PyQt4.Qt import QWidget, QListWidgetItem
|
||||
|
||||
class PluginWidget(QWidget, Ui_Form):
|
||||
|
||||
TITLE = _('BibTeX Options')
|
||||
HELP = _('Options specific to')+' BibTeX '+_('output')
|
||||
OPTION_FIELDS = [('bib_cit','{authors}{id}'),
|
||||
('bib_entry', 0), #mixed
|
||||
('bibfile_enc', 0), #utf-8
|
||||
('bibfile_enctag', 0), #strict
|
||||
('impcit', True) ]
|
||||
|
||||
sync_enabled = False
|
||||
formats = set(['bib'])
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
self.setupUi(self)
|
||||
from calibre.library.catalog import FIELDS
|
||||
self.all_fields = []
|
||||
for x in FIELDS :
|
||||
if x != 'all':
|
||||
self.all_fields.append(x)
|
||||
QListWidgetItem(x, self.db_fields)
|
||||
|
||||
def initialize(self, name): #not working properly to update
|
||||
self.name = name
|
||||
fields = gprefs.get(name+'_db_fields', self.all_fields)
|
||||
# Restore the activated db_fields from last use
|
||||
for x in xrange(self.db_fields.count()):
|
||||
item = self.db_fields.item(x)
|
||||
item.setSelected(unicode(item.text()) in fields)
|
||||
# Update dialog fields from stored options
|
||||
for opt in self.OPTION_FIELDS:
|
||||
opt_value = gprefs.get(self.name + '_' + opt[0], opt[1])
|
||||
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
|
||||
getattr(self, opt[0]).setCurrentIndex(opt_value)
|
||||
elif opt[0] == 'impcit' :
|
||||
getattr(self, opt[0]).setChecked(opt_value)
|
||||
else:
|
||||
getattr(self, opt[0]).setText(opt_value)
|
||||
|
||||
def options(self):
|
||||
|
||||
# Save the currently activated fields
|
||||
fields = []
|
||||
for x in xrange(self.db_fields.count()):
|
||||
item = self.db_fields.item(x)
|
||||
if item.isSelected():
|
||||
fields.append(unicode(item.text()))
|
||||
gprefs.set(self.name+'_db_fields', fields)
|
||||
|
||||
# Dictionary currently activated fields
|
||||
if len(self.db_fields.selectedItems()):
|
||||
opts_dict = {'fields':[unicode(item.text()) for item in self.db_fields.selectedItems()]}
|
||||
else:
|
||||
opts_dict = {'fields':['all']}
|
||||
|
||||
# Save/return the current options
|
||||
# bib_cit stores as text
|
||||
# 'bibfile_enc','bibfile_enctag' stores as int (Indexes)
|
||||
for opt in self.OPTION_FIELDS:
|
||||
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
|
||||
opt_value = getattr(self,opt[0]).currentIndex()
|
||||
elif opt[0] == 'impcit' :
|
||||
opt_value = getattr(self, opt[0]).isChecked()
|
||||
else :
|
||||
opt_value = unicode(getattr(self, opt[0]).text())
|
||||
gprefs.set(self.name + '_' + opt[0], opt_value)
|
||||
|
||||
opts_dict[opt[0]] = opt_value
|
||||
|
||||
return opts_dict
|
173
src/calibre/gui2/catalog/catalog_bibtex.ui
Normal file
173
src/calibre/gui2/catalog/catalog_bibtex.ui
Normal file
@ -0,0 +1,173 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>579</width>
|
||||
<height>411</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Bib file encoding:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Fields to include in output:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QComboBox" name="bibfile_enc">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">utf-8</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">cp1252</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>ascii/LaTeX</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" rowspan="12">
|
||||
<widget class="QListWidget" name="db_fields">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string extracomment="Select all fields to be exported"/>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::MultiSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Encoding configuration (change if you have errors) :</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QComboBox" name="bibfile_enctag">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>strict</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>replace</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>ignore</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>backslashreplace</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>60</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>BibTeX entry type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QComboBox" name="bib_entry">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>mixed</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>misc</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>book</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QCheckBox" name="impcit">
|
||||
<property name="text">
|
||||
<string>Create a citation tag?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Expression to form the BibTeX citation tag:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLineEdit" name="bib_cit"/>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Some explanation about this template:
|
||||
-The fields availables are 'author_sort', 'authors', 'id',
|
||||
'isbn', 'pubdate', 'publisher', 'series_index', 'series',
|
||||
'tags', 'timestamp', 'title', 'uuid'
|
||||
-For list types ie authors and tags, only the first element
|
||||
wil be selected.
|
||||
-For time field, only the date will be used. </string>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -20,6 +20,30 @@
|
||||
<string>Book Cover</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="_2">
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="_3">
|
||||
<item>
|
||||
<widget class="ImageView" name="cover" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="opt_prefer_metadata_cover">
|
||||
<property name="text">
|
||||
<string>Use cover from &source file</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QVBoxLayout" name="_4">
|
||||
<property name="spacing">
|
||||
@ -71,30 +95,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="opt_prefer_metadata_cover">
|
||||
<property name="text">
|
||||
<string>Use cover from &source file</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="_3">
|
||||
<item>
|
||||
<widget class="ImageView" name="cover" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>opt_prefer_metadata_cover</zorder>
|
||||
<zorder></zorder>
|
||||
@ -232,9 +232,6 @@
|
||||
<property name="insertPolicy">
|
||||
<enum>QComboBox::InsertAlphabetically</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
|
@ -28,9 +28,10 @@ class RegexBuilder(QDialog, Ui_RegexBuilder):
|
||||
|
||||
if not db or not book_id:
|
||||
self.button_box.addButton(QDialogButtonBox.Open)
|
||||
else:
|
||||
self.select_format(db, book_id)
|
||||
|
||||
elif not self.select_format(db, book_id):
|
||||
self.cancelled = True
|
||||
return
|
||||
self.cancelled = False
|
||||
self.connect(self.button_box, SIGNAL('clicked(QAbstractButton*)'), self.button_clicked)
|
||||
self.connect(self.regex, SIGNAL('textChanged(QString)'), self.regex_valid)
|
||||
self.connect(self.test, SIGNAL('clicked()'), self.do_test)
|
||||
@ -79,10 +80,12 @@ class RegexBuilder(QDialog, Ui_RegexBuilder):
|
||||
format = d.format()
|
||||
|
||||
if not format:
|
||||
error_dialog(self, _('No formats available'), _('Cannot build regex using the GUI builder without a book.'))
|
||||
QDialog.reject()
|
||||
else:
|
||||
self.open_book(db.format_abspath(book_id, format, index_is_id=True))
|
||||
error_dialog(self, _('No formats available'),
|
||||
_('Cannot build regex using the GUI builder without a book.'),
|
||||
show=True)
|
||||
return False
|
||||
self.open_book(db.format_abspath(book_id, format, index_is_id=True))
|
||||
return True
|
||||
|
||||
def open_book(self, pathtoebook):
|
||||
self.iterator = EbookIterator(pathtoebook)
|
||||
@ -117,6 +120,8 @@ class RegexEdit(QWidget, Ui_Edit):
|
||||
|
||||
def builder(self):
|
||||
bld = RegexBuilder(self.db, self.book_id, self.edit.text(), self)
|
||||
if bld.cancelled:
|
||||
return
|
||||
if bld.exec_() == bld.Accepted:
|
||||
self.edit.setText(bld.regex.text())
|
||||
|
||||
|
@ -28,7 +28,11 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="opt_chapter_mark"/>
|
||||
<widget class="QComboBox" name="opt_chapter_mark">
|
||||
<property name="minimumContentsLength">
|
||||
<number>20</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="opt_remove_first_image">
|
||||
|
@ -43,6 +43,15 @@
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimumContentsLength">
|
||||
<number>30</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -10,9 +10,10 @@ from functools import partial
|
||||
|
||||
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \
|
||||
QDate, QGroupBox, QVBoxLayout, QPlainTextEdit, QSizePolicy, \
|
||||
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL
|
||||
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \
|
||||
QPushButton
|
||||
|
||||
from calibre.utils.date import qt_to_dt
|
||||
from calibre.utils.date import qt_to_dt, now
|
||||
from calibre.gui2.widgets import TagsLineEdit, EnComboBox
|
||||
from calibre.gui2 import UNDEFINED_QDATE
|
||||
from calibre.utils.config import tweaks
|
||||
@ -132,20 +133,30 @@ class DateEdit(QDateEdit):
|
||||
|
||||
def focusInEvent(self, x):
|
||||
self.setSpecialValueText('')
|
||||
QDateEdit.focusInEvent(self, x)
|
||||
|
||||
def focusOutEvent(self, x):
|
||||
self.setSpecialValueText(_('Undefined'))
|
||||
QDateEdit.focusOutEvent(self, x)
|
||||
|
||||
def set_to_today(self):
|
||||
self.setDate(now())
|
||||
|
||||
class DateTime(Base):
|
||||
|
||||
def setup_ui(self, parent):
|
||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
|
||||
DateEdit(parent)]
|
||||
cm = self.col_metadata
|
||||
self.widgets = [QLabel('&'+cm['name']+':', parent), DateEdit(parent),
|
||||
QLabel(''), QPushButton(_('Set \'%s\' to today')%cm['name'], parent)]
|
||||
w = self.widgets[1]
|
||||
w.setDisplayFormat('dd MMM yyyy')
|
||||
format = cm['display'].get('date_format','')
|
||||
if not format:
|
||||
format = 'dd MMM yyyy'
|
||||
w.setDisplayFormat(format)
|
||||
w.setCalendarPopup(True)
|
||||
w.setMinimumDate(UNDEFINED_QDATE)
|
||||
w.setSpecialValueText(_('Undefined'))
|
||||
self.widgets[3].clicked.connect(w.set_to_today)
|
||||
|
||||
def setter(self, val):
|
||||
if val is None:
|
||||
|
@ -33,6 +33,7 @@ from calibre.devices.apple.driver import ITUNES_ASYNC
|
||||
from calibre.devices.folder_device.driver import FOLDER_DEVICE
|
||||
from calibre.ebooks.metadata.meta import set_metadata
|
||||
from calibre.constants import DEBUG
|
||||
from calibre.utils.config import prefs
|
||||
|
||||
# }}}
|
||||
|
||||
@ -764,6 +765,7 @@ class DeviceMixin(object): # {{{
|
||||
self.book_details.reset_info()
|
||||
self.location_view.setCurrentIndex(self.location_view.model().index(0))
|
||||
self.refresh_ondevice_info (device_connected = False)
|
||||
self.tool_bar.device_status_changed(bool(connected))
|
||||
|
||||
def info_read(self, job):
|
||||
'''
|
||||
@ -1424,19 +1426,25 @@ class DeviceMixin(object): # {{{
|
||||
aus = re.sub('(?u)\W|[_]', '', aus)
|
||||
self.db_book_title_cache[title]['author_sort'][aus] = mi
|
||||
self.db_book_title_cache[title]['db_ids'][mi.application_id] = mi
|
||||
self.db_book_uuid_cache[mi.uuid] = mi.application_id
|
||||
self.db_book_uuid_cache[mi.uuid] = mi
|
||||
|
||||
# Now iterate through all the books on the device, setting the
|
||||
# in_library field Fastest and most accurate key is the uuid. Second is
|
||||
# the application_id, which is really the db key, but as this can
|
||||
# accidentally match across libraries we also verify the title. The
|
||||
# db_id exists on Sony devices. Fallback is title and author match
|
||||
|
||||
update_metadata = prefs['manage_device_metadata'] == 'on_connect'
|
||||
for booklist in booklists:
|
||||
for book in booklist:
|
||||
if getattr(book, 'uuid', None) in self.db_book_uuid_cache:
|
||||
if update_metadata:
|
||||
book.smart_update(self.db_book_uuid_cache[book.uuid],
|
||||
replace_metadata=True)
|
||||
book.in_library = True
|
||||
# ensure that the correct application_id is set
|
||||
book.application_id = self.db_book_uuid_cache[book.uuid]
|
||||
book.application_id = \
|
||||
self.db_book_uuid_cache[book.uuid].application_id
|
||||
continue
|
||||
|
||||
book_title = book.title.lower() if book.title else ''
|
||||
@ -1446,11 +1454,15 @@ class DeviceMixin(object): # {{{
|
||||
if d is not None:
|
||||
if getattr(book, 'application_id', None) in d['db_ids']:
|
||||
book.in_library = True
|
||||
book.smart_update(d['db_ids'][book.application_id])
|
||||
if update_metadata:
|
||||
book.smart_update(d['db_ids'][book.application_id],
|
||||
replace_metadata=True)
|
||||
continue
|
||||
if book.db_id in d['db_ids']:
|
||||
book.in_library = True
|
||||
book.smart_update(d['db_ids'][book.db_id])
|
||||
if update_metadata:
|
||||
book.smart_update(d['db_ids'][book.db_id],
|
||||
replace_metadata=True)
|
||||
continue
|
||||
if book.authors:
|
||||
# Compare against both author and author sort, because
|
||||
@ -1459,14 +1471,21 @@ class DeviceMixin(object): # {{{
|
||||
book_authors = re.sub('(?u)\W|[_]', '', book_authors)
|
||||
if book_authors in d['authors']:
|
||||
book.in_library = True
|
||||
book.smart_update(d['authors'][book_authors])
|
||||
if update_metadata:
|
||||
book.smart_update(d['authors'][book_authors],
|
||||
replace_metadata=True)
|
||||
elif book_authors in d['author_sort']:
|
||||
book.in_library = True
|
||||
book.smart_update(d['author_sort'][book_authors])
|
||||
if update_metadata:
|
||||
book.smart_update(d['author_sort'][book_authors],
|
||||
replace_metadata=True)
|
||||
# Set author_sort if it isn't already
|
||||
asort = getattr(book, 'author_sort', None)
|
||||
if not asort and book.authors:
|
||||
book.author_sort = self.library_view.model().db.author_sort_from_authors(book.authors)
|
||||
|
||||
if update_metadata:
|
||||
if self.device_manager.is_device_connected:
|
||||
self.device_manager.sync_booklists(None, booklists)
|
||||
# }}}
|
||||
|
||||
|
@ -334,7 +334,6 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
||||
|
||||
def __init__(self, parent, library_view, server=None):
|
||||
ResizableDialog.__init__(self, parent)
|
||||
self.ICON_SIZES = {0:QSize(48, 48), 1:QSize(32,32), 2:QSize(24,24)}
|
||||
self._category_model = CategoryModel()
|
||||
|
||||
self.category_view.currentChanged = self.category_current_changed
|
||||
@ -389,10 +388,6 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
||||
self.add_custcol_button.clicked.connect(self.add_custcol)
|
||||
self.edit_custcol_button.clicked.connect(self.edit_custcol)
|
||||
|
||||
icons = config['toolbar_icon_size']
|
||||
self.toolbar_button_size.setCurrentIndex(0 if icons == self.ICON_SIZES[0] else 1 if icons == self.ICON_SIZES[1] else 2)
|
||||
self.show_toolbar_text.setChecked(config['show_text_in_toolbar'])
|
||||
|
||||
output_formats = sorted(available_output_formats())
|
||||
output_formats.remove('oeb')
|
||||
for f in output_formats:
|
||||
@ -845,8 +840,6 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
||||
|
||||
must_restart = self.apply_custom_column_changes()
|
||||
|
||||
config['toolbar_icon_size'] = self.ICON_SIZES[self.toolbar_button_size.currentIndex()]
|
||||
config['show_text_in_toolbar'] = bool(self.show_toolbar_text.isChecked())
|
||||
config['separate_cover_flow'] = bool(self.separate_cover_flow.isChecked())
|
||||
config['disable_tray_notification'] = not self.systray_notifications.isChecked()
|
||||
p = {0:'normal', 1:'high', 2:'low'}[self.priority.currentIndex()]
|
||||
|
@ -45,7 +45,12 @@ class AddSave(QTabWidget, Ui_TabWidget):
|
||||
self.metadata_box.layout().insertWidget(0, self.filename_pattern)
|
||||
self.opt_swap_author_names.setChecked(prefs['swap_author_names'])
|
||||
self.opt_add_formats_to_existing.setChecked(prefs['add_formats_to_existing'])
|
||||
self.preserve_user_collections.setChecked(prefs['preserve_user_collections'])
|
||||
if prefs['manage_device_metadata'] == 'manual':
|
||||
self.manage_device_metadata.setCurrentIndex(0)
|
||||
elif prefs['manage_device_metadata'] == 'on_send':
|
||||
self.manage_device_metadata.setCurrentIndex(1)
|
||||
else:
|
||||
self.manage_device_metadata.setCurrentIndex(2)
|
||||
help = '\n'.join(textwrap.wrap(c.get_option('template').help, 75))
|
||||
self.save_template.initialize('save_to_disk', opts.template, help)
|
||||
self.send_template.initialize('send_to_device', opts.send_template, help)
|
||||
@ -72,12 +77,14 @@ class AddSave(QTabWidget, Ui_TabWidget):
|
||||
prefs['filename_pattern'] = pattern
|
||||
prefs['swap_author_names'] = bool(self.opt_swap_author_names.isChecked())
|
||||
prefs['add_formats_to_existing'] = bool(self.opt_add_formats_to_existing.isChecked())
|
||||
prefs['preserve_user_collections'] = bool(self.preserve_user_collections.isChecked())
|
||||
|
||||
if self.manage_device_metadata.currentIndex() == 0:
|
||||
prefs['manage_device_metadata'] = 'manual'
|
||||
elif self.manage_device_metadata.currentIndex() == 1:
|
||||
prefs['manage_device_metadata'] = 'on_send'
|
||||
else:
|
||||
prefs['manage_device_metadata'] = 'on_connect'
|
||||
return True
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt4.Qt import QApplication
|
||||
app=QApplication([])
|
||||
|
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>588</width>
|
||||
<width>671</width>
|
||||
<height>516</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -177,32 +177,81 @@ Title match ignores leading indefinite articles ("the", "a",
|
||||
<attribute name="title">
|
||||
<string>Sending to &device</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="preserve_user_collections">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preserve device collections.</string>
|
||||
<string>Metadata &management:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>manage_device_metadata</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="manage_device_metadata">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string extracomment="foobar">Manual management</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Only on send</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Automatic management</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>313</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label_41">
|
||||
<property name="text">
|
||||
<string>If checked, collections will not be deleted even if a book with changed metadata is resent and the collection is not in the book's metadata. In addition, editing collections in the device view will be enabled. If unchecked, collections will be always reflect only the metadata in the calibre library.</string>
|
||||
<string><li><b>Manual Management</b>: Calibre updates the metadata and adds collections only when a book is sent. With this option, calibre will never remove a collection.</li>
|
||||
<li><b>Only on send</b>: Calibre updates metadata and adds/removes collections for a book only when it is sent to the device. </li>
|
||||
<li><b>Automatic management</b>: Calibre automatically keeps metadata on the device in sync with the calibre library, on every connect</li></ul></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_42">
|
||||
<property name="text">
|
||||
<string> </string>
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="3" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label_43">
|
||||
<property name="text">
|
||||
<string>Here you can control how calibre will save your books when you click the Send to Device button. This setting can be overriden for individual devices by customizing the device interface plugins in Preferences->Plugins</string>
|
||||
@ -212,7 +261,7 @@ Title match ignores leading indefinite articles ("the", "a",
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="4" column="0" colspan="3">
|
||||
<widget class="SaveTemplate" name="send_template" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -422,54 +422,6 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item row="10" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Toolbar</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="toolbar_button_size">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Large</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Medium</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Small</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>&Button size in toolbar</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>toolbar_button_size</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="show_toolbar_text">
|
||||
<property name="text">
|
||||
<string>Show &text in toolbar buttons</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
|
@ -277,12 +277,6 @@
|
||||
</property>
|
||||
<item>
|
||||
<widget class="EnComboBox" name="series">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>List of known series. You can add new series.</string>
|
||||
</property>
|
||||
@ -295,9 +289,6 @@
|
||||
<property name="insertPolicy">
|
||||
<enum>QComboBox::InsertAlphabetically</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -176,12 +176,6 @@ class ToolbarMixin(object): # {{{
|
||||
def show_help(self, *args):
|
||||
open_url(QUrl('http://calibre-ebook.com/user_manual'))
|
||||
|
||||
def read_toolbar_settings(self):
|
||||
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
||||
self.tool_bar.setToolButtonStyle(
|
||||
Qt.ToolButtonTextUnderIcon if \
|
||||
config['show_text_in_toolbar'] else \
|
||||
Qt.ToolButtonIconOnly)
|
||||
|
||||
# }}}
|
||||
|
||||
@ -409,7 +403,8 @@ class StatusBar(QStatusBar): # {{{
|
||||
self.clearMessage()
|
||||
|
||||
def message_changed(self, msg):
|
||||
if not msg or msg.isEmpty() or msg.isNull():
|
||||
if not msg or msg.isEmpty() or msg.isNull() or \
|
||||
not unicode(msg).strip():
|
||||
extra = ''
|
||||
if self.device_string:
|
||||
extra = ' ..::.. ' + self.device_string
|
||||
|
@ -5,48 +5,21 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from operator import attrgetter
|
||||
|
||||
from PyQt4.Qt import QIcon, Qt, QWidget, QAction, QToolBar, QSize, QVariant, \
|
||||
QAbstractListModel, QFont, QApplication, QPalette, pyqtSignal, QToolButton, \
|
||||
QModelIndex, QListView, QAbstractButton, QPainter, QPixmap, QColor, \
|
||||
QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QComboBox
|
||||
QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout
|
||||
|
||||
from calibre.constants import __appname__, filesystem_encoding
|
||||
from calibre.gui2.search_box import SearchBox2, SavedSearchBox
|
||||
from calibre.gui2.throbber import ThrobbingButton
|
||||
from calibre.gui2 import NONE
|
||||
from calibre.gui2 import NONE, config
|
||||
from calibre.gui2.widgets import ComboBoxWithHelp
|
||||
from calibre import human_readable
|
||||
|
||||
class ToolBar(QToolBar): # {{{
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QToolBar.__init__(self, parent)
|
||||
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||
self.setMovable(False)
|
||||
self.setFloatable(False)
|
||||
self.setOrientation(Qt.Horizontal)
|
||||
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
|
||||
self.setIconSize(QSize(48, 48))
|
||||
self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
|
||||
|
||||
def add_actions(self, *args):
|
||||
self.left_space = QWidget(self)
|
||||
self.left_space.setSizePolicy(QSizePolicy.Expanding,
|
||||
QSizePolicy.Minimum)
|
||||
self.addWidget(self.left_space)
|
||||
for action in args:
|
||||
if action is None:
|
||||
self.addSeparator()
|
||||
else:
|
||||
self.addAction(action)
|
||||
self.right_space = QWidget(self)
|
||||
self.right_space.setSizePolicy(QSizePolicy.Expanding,
|
||||
QSizePolicy.Minimum)
|
||||
self.addWidget(self.right_space)
|
||||
|
||||
def contextMenuEvent(self, *args):
|
||||
pass
|
||||
|
||||
# }}}
|
||||
ICON_SIZE = 48
|
||||
|
||||
# Location View {{{
|
||||
|
||||
@ -165,7 +138,7 @@ class LocationModel(QAbstractListModel): # {{{
|
||||
|
||||
class LocationView(QListView):
|
||||
|
||||
unmount_device = pyqtSignal()
|
||||
umount_device = pyqtSignal()
|
||||
location_selected = pyqtSignal(object)
|
||||
|
||||
def __init__(self, parent):
|
||||
@ -190,22 +163,27 @@ class LocationView(QListView):
|
||||
self.setTabKeyNavigation(True)
|
||||
self.setProperty("showDropIndicator", True)
|
||||
self.setSelectionMode(self.SingleSelection)
|
||||
self.setIconSize(QSize(40, 40))
|
||||
self.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
|
||||
self.setMovement(self.Static)
|
||||
self.setFlow(self.LeftToRight)
|
||||
self.setGridSize(QSize(175, 90))
|
||||
self.setGridSize(QSize(175, ICON_SIZE))
|
||||
self.setViewMode(self.ListMode)
|
||||
self.setWordWrap(True)
|
||||
self.setObjectName("location_view")
|
||||
self.setMaximumHeight(74)
|
||||
self.setMaximumSize(QSize(600, ICON_SIZE+16))
|
||||
self.setMinimumWidth(400)
|
||||
|
||||
def eject_clicked(self, *args):
|
||||
self.unmount_device.emit()
|
||||
self.umount_device.emit()
|
||||
|
||||
def count_changed(self, new_count):
|
||||
self.model().count = new_count
|
||||
self.model().reset()
|
||||
|
||||
@property
|
||||
def book_count(self):
|
||||
return self.model().count
|
||||
|
||||
def current_changed(self, current, previous):
|
||||
if current.isValid():
|
||||
i = current.row()
|
||||
@ -247,12 +225,15 @@ class EjectButton(QAbstractButton):
|
||||
def __init__(self, parent):
|
||||
QAbstractButton.__init__(self, parent)
|
||||
self.mouse_over = False
|
||||
self.setMouseTracking(True)
|
||||
|
||||
def enterEvent(self, event):
|
||||
self.mouse_over = True
|
||||
QAbstractButton.enterEvent(self, event)
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.mouse_over = False
|
||||
QAbstractButton.leaveEvent(self, event)
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
@ -280,12 +261,7 @@ class SearchBar(QWidget): # {{{
|
||||
self._layout = l = QHBoxLayout()
|
||||
self.setLayout(self._layout)
|
||||
|
||||
self.restriction_label = QLabel(_("&Restrict to:"))
|
||||
l.addWidget(self.restriction_label)
|
||||
self.restriction_label.setSizePolicy(QSizePolicy.Minimum,
|
||||
QSizePolicy.Minimum)
|
||||
|
||||
x = QComboBox(self)
|
||||
x = ComboBoxWithHelp(self)
|
||||
x.setMaximumSize(QSize(150, 16777215))
|
||||
x.setObjectName("search_restriction")
|
||||
x.setToolTip(_("Books display will be restricted to those matching the selected saved search"))
|
||||
@ -344,38 +320,88 @@ class SearchBar(QWidget): # {{{
|
||||
x.setToolTip(_("Delete current saved search"))
|
||||
|
||||
self.label.setBuddy(parent.search)
|
||||
self.restriction_label.setBuddy(parent.search_restriction)
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
class LocationBar(ToolBar): # {{{
|
||||
class ToolBar(QToolBar): # {{{
|
||||
|
||||
def __init__(self, actions, donate, location_view, parent=None):
|
||||
ToolBar.__init__(self, parent)
|
||||
|
||||
for ac in actions:
|
||||
self.addAction(ac)
|
||||
|
||||
self.addWidget(location_view)
|
||||
self.w = QWidget()
|
||||
self.w.setLayout(QVBoxLayout())
|
||||
self.w.layout().addWidget(donate)
|
||||
donate.setAutoRaise(True)
|
||||
donate.setCursor(Qt.PointingHandCursor)
|
||||
self.addWidget(self.w)
|
||||
self.setIconSize(QSize(50, 50))
|
||||
QToolBar.__init__(self, parent)
|
||||
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||
self.setMovable(False)
|
||||
self.setFloatable(False)
|
||||
self.setOrientation(Qt.Horizontal)
|
||||
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
|
||||
self.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
|
||||
self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
|
||||
|
||||
def button_for_action(self, ac):
|
||||
b = QToolButton(self)
|
||||
b.setDefaultAction(ac)
|
||||
for x in ('ToolTip', 'StatusTip', 'WhatsThis'):
|
||||
getattr(b, 'set'+x)(b.text())
|
||||
self.showing_device = False
|
||||
self.all_actions = actions
|
||||
self.donate = donate
|
||||
self.location_view = location_view
|
||||
self.d_widget = QWidget()
|
||||
self.d_widget.setLayout(QVBoxLayout())
|
||||
self.d_widget.layout().addWidget(donate)
|
||||
donate.setAutoRaise(True)
|
||||
donate.setCursor(Qt.PointingHandCursor)
|
||||
self.build_bar()
|
||||
|
||||
def contextMenuEvent(self, *args):
|
||||
pass
|
||||
|
||||
def device_status_changed(self, connected):
|
||||
self.showing_device = connected
|
||||
self.build_bar()
|
||||
|
||||
def build_bar(self):
|
||||
order_field = 'device' if self.showing_device else 'normal'
|
||||
o = attrgetter(order_field+'_order')
|
||||
sepvals = [2] if self.showing_device else [1]
|
||||
sepvals += [3]
|
||||
actions = [x for x in self.all_actions if o(x) > -1]
|
||||
actions.sort(cmp=lambda x,y : cmp(o(x), o(y)))
|
||||
self.clear()
|
||||
for x in actions:
|
||||
self.addAction(x)
|
||||
ch = self.widgetForAction(x)
|
||||
ch.setCursor(Qt.PointingHandCursor)
|
||||
ch.setAutoRaise(True)
|
||||
|
||||
if x.action_name == 'choose_library':
|
||||
self.location_action = self.addWidget(self.location_view)
|
||||
self.choose_action = x
|
||||
if config['show_donate_button']:
|
||||
self.addWidget(self.d_widget)
|
||||
if x.action_name not in ('choose_library', 'help'):
|
||||
ch.setPopupMode(ch.MenuButtonPopup)
|
||||
|
||||
|
||||
for x in actions:
|
||||
if x.separator_before in sepvals:
|
||||
self.insertSeparator(x)
|
||||
|
||||
|
||||
self.location_action.setVisible(self.showing_device)
|
||||
self.choose_action.setVisible(not self.showing_device)
|
||||
|
||||
def count_changed(self, new_count):
|
||||
text = _('%d books')%new_count
|
||||
a = self.choose_action
|
||||
a.setText(text)
|
||||
|
||||
def resizeEvent(self, ev):
|
||||
style = Qt.ToolButtonTextUnderIcon
|
||||
if self.size().width() < 1260:
|
||||
style = Qt.ToolButtonIconOnly
|
||||
self.setToolButtonStyle(style)
|
||||
QToolBar.resizeEvent(self, ev)
|
||||
|
||||
return b
|
||||
# }}}
|
||||
|
||||
class Action(QAction):
|
||||
pass
|
||||
|
||||
class MainWindowMixin(object):
|
||||
|
||||
def __init__(self):
|
||||
@ -390,12 +416,19 @@ class MainWindowMixin(object):
|
||||
self.centralwidget.setLayout(self._central_widget_layout)
|
||||
self.resize(1012, 740)
|
||||
self.donate_button = ThrobbingButton(self.centralwidget)
|
||||
self.donate_button.set_normal_icon_size(64, 64)
|
||||
self.donate_button.set_normal_icon_size(ICON_SIZE, ICON_SIZE)
|
||||
|
||||
# Actions {{{
|
||||
|
||||
def ac(name, text, icon, shortcut=None, tooltip=None):
|
||||
action = QAction(QIcon(I(icon)), text, self)
|
||||
all_actions = []
|
||||
|
||||
def ac(normal_order, device_order, separator_before,
|
||||
name, text, icon, shortcut=None, tooltip=None):
|
||||
action = Action(QIcon(I(icon)), text, self)
|
||||
action.normal_order = normal_order
|
||||
action.device_order = device_order
|
||||
action.separator_before = separator_before
|
||||
action.action_name = name
|
||||
text = tooltip if tooltip else text
|
||||
action.setToolTip(text)
|
||||
action.setStatusTip(text)
|
||||
@ -405,56 +438,46 @@ class MainWindowMixin(object):
|
||||
if shortcut:
|
||||
action.setShortcut(shortcut)
|
||||
setattr(self, 'action_'+name, action)
|
||||
all_actions.append(action)
|
||||
|
||||
ac('add', _('Add books'), 'add_book.svg', _('A'))
|
||||
ac('del', _('Remove books'), 'trash.svg', _('Del'))
|
||||
ac('edit', _('Edit meta info'), 'edit_input.svg', _('E'))
|
||||
ac('merge', _('Merge book records'), 'merge_books.svg', _('M'))
|
||||
ac('sync', _('Send to device'), 'sync.svg')
|
||||
ac('save', _('Save to disk'), 'save.svg', _('S'))
|
||||
ac('news', _('Fetch news'), 'news.svg', _('F'))
|
||||
ac('convert', _('Convert books'), 'convert.svg', _('C'))
|
||||
ac('view', _('View'), 'view.svg', _('V'))
|
||||
ac('open_containing_folder', _('Open containing folder'),
|
||||
ac(0, 7, 0, 'add', _('Add books'), 'add_book.svg', _('A'))
|
||||
ac(1, 1, 0, 'edit', _('Edit metadata'), 'edit_input.svg', _('E'))
|
||||
ac(2, 2, 3, 'convert', _('Convert books'), 'convert.svg', _('C'))
|
||||
ac(3, 3, 0, 'view', _('View'), 'view.svg', _('V'))
|
||||
ac(4, 4, 3, 'choose_library', _('%d books')%0, 'lt.png',
|
||||
tooltip=_('Choose calibre library to work with'))
|
||||
ac(5, 5, 3, 'news', _('Fetch news'), 'news.svg', _('F'))
|
||||
ac(6, 6, 0, 'save', _('Save to disk'), 'save.svg', _('S'))
|
||||
ac(7, 0, 0, 'sync', _('Send to device'), 'sync.svg')
|
||||
ac(8, 8, 3, 'del', _('Remove books'), 'trash.svg', _('Del'))
|
||||
ac(9, 9, 3, 'help', _('Help'), 'help.svg', _('F1'), _("Browse the calibre User Manual"))
|
||||
ac(10, 10, 0, 'preferences', _('Preferences'), 'config.svg', _('Ctrl+P'))
|
||||
|
||||
ac(-1, -1, 0, 'merge', _('Merge book records'), 'merge_books.svg', _('M'))
|
||||
ac(-1, -1, 0, 'open_containing_folder', _('Open containing folder'),
|
||||
'document_open.svg')
|
||||
ac('show_book_details', _('Show book details'),
|
||||
ac(-1, -1, 0, 'show_book_details', _('Show book details'),
|
||||
'dialog_information.svg')
|
||||
ac('books_by_same_author', _('Books by same author'),
|
||||
ac(-1, -1, 0, 'books_by_same_author', _('Books by same author'),
|
||||
'user_profile.svg')
|
||||
ac('books_in_this_series', _('Books in this series'),
|
||||
ac(-1, -1, 0, 'books_in_this_series', _('Books in this series'),
|
||||
'books_in_series.svg')
|
||||
ac('books_by_this_publisher', _('Books by this publisher'),
|
||||
ac(-1, -1, 0, 'books_by_this_publisher', _('Books by this publisher'),
|
||||
'publisher.png')
|
||||
ac('books_with_the_same_tags', _('Books with the same tags'),
|
||||
ac(-1, -1, 0, 'books_with_the_same_tags', _('Books with the same tags'),
|
||||
'tags.svg')
|
||||
ac('preferences', _('Preferences'), 'config.svg', _('Ctrl+P'))
|
||||
ac('help', _('Help'), 'help.svg', _('F1'), _("Browse the calibre User Manual"))
|
||||
|
||||
# }}}
|
||||
|
||||
self.tool_bar = ToolBar(self)
|
||||
self.addToolBar(Qt.BottomToolBarArea, self.tool_bar)
|
||||
self.tool_bar.add_actions(self.action_convert, self.action_view,
|
||||
None, self.action_edit, None,
|
||||
self.action_save, self.action_del,
|
||||
None,
|
||||
self.action_help, None, self.action_preferences)
|
||||
|
||||
self.location_view = LocationView(self.centralwidget)
|
||||
self.search_bar = SearchBar(self)
|
||||
self.location_bar = LocationBar([self.action_add, self.action_sync,
|
||||
self.action_news], self.donate_button, self.location_view, self)
|
||||
self.addToolBar(Qt.TopToolBarArea, self.location_bar)
|
||||
self.tool_bar = ToolBar(all_actions, self.donate_button, self.location_view, self)
|
||||
self.addToolBar(Qt.TopToolBarArea, self.tool_bar)
|
||||
|
||||
l = self.centralwidget.layout()
|
||||
l.addWidget(self.search_bar)
|
||||
|
||||
for ch in list(self.tool_bar.children()) + list(self.location_bar.children()):
|
||||
if isinstance(ch, QToolButton):
|
||||
ch.setCursor(Qt.PointingHandCursor)
|
||||
ch.setAutoRaise(True)
|
||||
if ch is not self.donate_button:
|
||||
ch.setPopupMode(ch.MenuButtonPopup)
|
||||
|
||||
|
||||
def read_toolbar_settings(self):
|
||||
pass
|
||||
|
||||
|
@ -96,7 +96,7 @@ class DateDelegate(QStyledItemDelegate): # {{{
|
||||
|
||||
def displayText(self, val, locale):
|
||||
d = val.toDate()
|
||||
if d == UNDEFINED_QDATE:
|
||||
if d <= UNDEFINED_QDATE:
|
||||
return ''
|
||||
return format_date(d.toPyDate(), 'dd MMM yyyy')
|
||||
|
||||
@ -116,7 +116,7 @@ class PubDateDelegate(QStyledItemDelegate): # {{{
|
||||
|
||||
def displayText(self, val, locale):
|
||||
d = val.toDate()
|
||||
if d == UNDEFINED_QDATE:
|
||||
if d <= UNDEFINED_QDATE:
|
||||
return ''
|
||||
format = tweaks['gui_pubdate_display_format']
|
||||
if format is None:
|
||||
@ -194,7 +194,7 @@ class CcDateDelegate(QStyledItemDelegate): # {{{
|
||||
|
||||
def displayText(self, val, locale):
|
||||
d = val.toDate()
|
||||
if d == UNDEFINED_QDATE:
|
||||
if d <= UNDEFINED_QDATE:
|
||||
return ''
|
||||
return format_date(d.toPyDate(), self.format)
|
||||
|
||||
@ -217,7 +217,7 @@ class CcDateDelegate(QStyledItemDelegate): # {{{
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
val = editor.date()
|
||||
if val == UNDEFINED_QDATE:
|
||||
if val <= UNDEFINED_QDATE:
|
||||
val = None
|
||||
model.setData(index, QVariant(val), Qt.EditRole)
|
||||
|
||||
|
@ -944,7 +944,7 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
(cname == 'collections' and \
|
||||
callable(getattr(self.db, 'supports_collections', None)) and \
|
||||
self.db.supports_collections() and \
|
||||
prefs['preserve_user_collections']):
|
||||
prefs['manage_device_metadata']=='manual'):
|
||||
flags |= Qt.ItemIsEditable
|
||||
return flags
|
||||
|
||||
@ -1216,7 +1216,9 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
return done
|
||||
|
||||
def set_editable(self, editable):
|
||||
self.editable = editable
|
||||
# Cannot edit if metadata is sent on connect. Reason: changes will
|
||||
# revert to what is in the library on next connect.
|
||||
self.editable = editable and prefs['manage_device_metadata']!='on_connect'
|
||||
|
||||
def set_search_restriction(self, s):
|
||||
pass
|
||||
|
@ -503,7 +503,7 @@ class DeviceBooksView(BooksView): # {{{
|
||||
self.edit_collections_menu.setVisible(
|
||||
callable(getattr(self._model.db, 'supports_collections', None)) and \
|
||||
self._model.db.supports_collections() and \
|
||||
prefs['preserve_user_collections'])
|
||||
prefs['manage_device_metadata'] == 'manual')
|
||||
self.context_menu.popup(event.globalPos())
|
||||
event.accept()
|
||||
|
||||
|
@ -7,11 +7,13 @@ Created on 10 Jun 2010
|
||||
class SearchRestrictionMixin(object):
|
||||
|
||||
def __init__(self):
|
||||
self.search_restriction.activated[str].connect(self.apply_search_restriction)
|
||||
self.search_restriction.initialize(help_text=_('Restrict to'))
|
||||
self.search_restriction.activated[int].connect(self.apply_search_restriction)
|
||||
self.library_view.model().count_changed_signal.connect(self.restriction_count_changed)
|
||||
self.search_restriction.setSizeAdjustPolicy(self.search_restriction.AdjustToMinimumContentsLengthWithIcon)
|
||||
self.search_restriction.setMinimumContentsLength(10)
|
||||
self.search_restriction.setStatusTip(self.search_restriction.toolTip())
|
||||
self.search_count.setText(_("(all books)"))
|
||||
|
||||
'''
|
||||
Adding and deleting books while restricted creates a complexity. When added,
|
||||
@ -27,8 +29,8 @@ class SearchRestrictionMixin(object):
|
||||
if self.restriction_in_effect:
|
||||
self.set_number_of_books_shown()
|
||||
|
||||
def apply_search_restriction(self, r):
|
||||
r = unicode(r)
|
||||
def apply_search_restriction(self, i):
|
||||
r = unicode(self.search_restriction.currentText())
|
||||
if r is not None and r != '':
|
||||
self.restriction_in_effect = True
|
||||
restriction = 'search:"%s"'%(r)
|
||||
|
@ -167,8 +167,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{
|
||||
self.eject_action = self.system_tray_menu.addAction(
|
||||
QIcon(I('eject.svg')), _('&Eject connected device'))
|
||||
self.eject_action.setEnabled(False)
|
||||
if not config['show_donate_button']:
|
||||
self.donate_button.setVisible(False)
|
||||
self.addAction(self.quit_action)
|
||||
self.action_restart = QAction(_('&Restart'), self)
|
||||
self.addAction(self.action_restart)
|
||||
@ -220,8 +218,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{
|
||||
|
||||
if self.system_tray_icon.isVisible() and opts.start_in_tray:
|
||||
self.hide_windows()
|
||||
self.library_view.model().count_changed_signal.connect \
|
||||
(self.location_view.count_changed)
|
||||
for t in (self.location_view, self.tool_bar):
|
||||
self.library_view.model().count_changed_signal.connect \
|
||||
(t.count_changed)
|
||||
if not gprefs.get('quick_start_guide_added', False):
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
mi = MetaInformation(_('Calibre Quick Start Guide'), ['John Schember'])
|
||||
@ -274,8 +273,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{
|
||||
SIGNAL('start_recipe_fetch(PyQt_PyObject)'),
|
||||
self.download_scheduled_recipe, Qt.QueuedConnection)
|
||||
|
||||
self.location_view.setCurrentIndex(self.location_view.model().index(0))
|
||||
|
||||
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
|
||||
AddAction.__init__(self)
|
||||
|
||||
|
@ -490,6 +490,7 @@ class EnComboBox(QComboBox):
|
||||
QComboBox.__init__(self, *args)
|
||||
self.setLineEdit(EnLineEdit(self))
|
||||
self.setAutoCompletionCaseSensitivity(Qt.CaseSensitive)
|
||||
self.setMinimumContentsLength(20)
|
||||
|
||||
def text(self):
|
||||
return unicode(self.currentText())
|
||||
@ -538,6 +539,53 @@ class HistoryLineEdit(QComboBox):
|
||||
def text(self):
|
||||
return self.currentText()
|
||||
|
||||
class ComboBoxWithHelp(QComboBox):
|
||||
'''
|
||||
A combobox where item 0 is help text. CurrentText will return '' for item 0.
|
||||
Be sure to always fetch the text with currentText. Don't use the signals
|
||||
that pass a string, because they will not correct the text.
|
||||
'''
|
||||
def __init__(self, parent=None):
|
||||
QComboBox.__init__(self, parent)
|
||||
self.currentIndexChanged[int].connect(self.index_changed)
|
||||
self.help_text = ''
|
||||
self.state_set = False
|
||||
|
||||
def initialize(self, help_text=_('Search')):
|
||||
self.help_text = help_text
|
||||
self.set_state()
|
||||
|
||||
def set_state(self):
|
||||
if not self.state_set:
|
||||
if self.currentIndex() == 0:
|
||||
self.setItemText(0, self.help_text)
|
||||
self.setStyleSheet('QComboBox { color: gray }')
|
||||
else:
|
||||
self.setItemText(0, '')
|
||||
self.setStyleSheet('QComboBox { color: black }')
|
||||
|
||||
def index_changed(self, index):
|
||||
self.state_set = False
|
||||
self.set_state()
|
||||
|
||||
def currentText(self):
|
||||
if self.currentIndex() == 0:
|
||||
return ''
|
||||
return QComboBox.currentText(self)
|
||||
|
||||
def itemText(self, idx):
|
||||
if idx == 0:
|
||||
return ''
|
||||
return QComboBox.itemText(self, idx)
|
||||
|
||||
def showPopup(self):
|
||||
self.setItemText(0, '')
|
||||
QComboBox.showPopup(self)
|
||||
|
||||
def hidePopup(self):
|
||||
QComboBox.hidePopup(self)
|
||||
self.set_state()
|
||||
|
||||
class PythonHighlighter(QSyntaxHighlighter):
|
||||
|
||||
Rules = []
|
||||
|
@ -209,13 +209,13 @@ class ResultCache(SearchQueryParser):
|
||||
if query == 'false':
|
||||
for item in self._data:
|
||||
if item is None: continue
|
||||
if item[loc] is None or item[loc] == UNDEFINED_DATE:
|
||||
if item[loc] is None or item[loc] <= UNDEFINED_DATE:
|
||||
matches.add(item[0])
|
||||
return matches
|
||||
if query == 'true':
|
||||
for item in self._data:
|
||||
if item is None: continue
|
||||
if item[loc] is not None and item[loc] != UNDEFINED_DATE:
|
||||
if item[loc] is not None and item[loc] > UNDEFINED_DATE:
|
||||
matches.add(item[0])
|
||||
return matches
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Greg Riker <griker at hotmail.com>'
|
||||
|
||||
import datetime, htmlentitydefs, os, re, shutil
|
||||
import datetime, htmlentitydefs, os, re, shutil, codecs
|
||||
|
||||
from collections import namedtuple
|
||||
from copy import deepcopy
|
||||
@ -9,6 +11,7 @@ from copy import deepcopy
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
from calibre import filesystem_encoding, prints, prepare_string_for_xml, strftime
|
||||
from calibre.constants import preferred_encoding
|
||||
from calibre.customize import CatalogPlugin
|
||||
from calibre.customize.conversion import OptionRecommendation, DummyReporter
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString
|
||||
@ -21,6 +24,10 @@ FIELDS = ['all', 'author_sort', 'authors', 'comments',
|
||||
'series_index', 'series', 'size', 'tags', 'timestamp', 'title',
|
||||
'uuid']
|
||||
|
||||
#Allowed fields for template
|
||||
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate',
|
||||
'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ]
|
||||
|
||||
class CSV_XML(CatalogPlugin):
|
||||
'CSV/XML catalog generator'
|
||||
|
||||
@ -89,17 +96,20 @@ class CSV_XML(CatalogPlugin):
|
||||
fields = self.get_output_fields(opts)
|
||||
|
||||
if self.fmt == 'csv':
|
||||
outfile = open(path_to_output, 'w')
|
||||
outfile = codecs.open(path_to_output, 'w', 'utf8')
|
||||
|
||||
# Output the field headers
|
||||
outfile.write(u'%s\n' % u','.join(fields))
|
||||
|
||||
# Output the entry fields
|
||||
for entry in data:
|
||||
outstr = ''
|
||||
for (x, field) in enumerate(fields):
|
||||
outstr = []
|
||||
for field in fields:
|
||||
item = entry[field]
|
||||
if field == 'formats':
|
||||
if item is None:
|
||||
outstr.append('""')
|
||||
continue
|
||||
elif field == 'formats':
|
||||
fmt_list = []
|
||||
for format in item:
|
||||
fmt_list.append(format.rpartition('.')[2].lower())
|
||||
@ -111,18 +121,13 @@ class CSV_XML(CatalogPlugin):
|
||||
item = u'%s' % re.sub(r'[\D]', '', item)
|
||||
elif field in ['pubdate', 'timestamp']:
|
||||
item = isoformat(item)
|
||||
elif field == 'comments':
|
||||
item = item.replace(u'\r\n',u' ')
|
||||
item = item.replace(u'\n',u' ')
|
||||
|
||||
if x < len(fields) - 1:
|
||||
if item is not None:
|
||||
outstr += u'"%s",' % unicode(item).replace('"','""')
|
||||
else:
|
||||
outstr += '"",'
|
||||
else:
|
||||
if item is not None:
|
||||
outstr += u'"%s"\n' % unicode(item).replace('"','""')
|
||||
else:
|
||||
outstr += '""\n'
|
||||
outfile.write(outstr.encode('utf-8'))
|
||||
outstr.append(u'"%s"' % unicode(item).replace('"','""'))
|
||||
|
||||
outfile.write(u','.join(outstr) + u'\n')
|
||||
outfile.close()
|
||||
|
||||
elif self.fmt == 'xml':
|
||||
@ -181,6 +186,329 @@ class CSV_XML(CatalogPlugin):
|
||||
f.write(etree.tostring(root, encoding='utf-8',
|
||||
xml_declaration=True, pretty_print=True))
|
||||
|
||||
class BIBTEX(CatalogPlugin):
|
||||
'BIBTEX catalog generator'
|
||||
|
||||
Option = namedtuple('Option', 'option, default, dest, action, help')
|
||||
|
||||
name = 'Catalog_BIBTEX'
|
||||
description = 'BIBTEX catalog generator'
|
||||
supported_platforms = ['windows', 'osx', 'linux']
|
||||
author = 'Sengian'
|
||||
version = (1, 0, 0)
|
||||
file_types = set(['bib'])
|
||||
|
||||
cli_options = [
|
||||
Option('--fields',
|
||||
default = 'all',
|
||||
dest = 'fields',
|
||||
action = None,
|
||||
help = _('The fields to output when cataloging books in the '
|
||||
'database. Should be a comma-separated list of fields.\n'
|
||||
'Available fields: %s.\n'
|
||||
"Default: '%%default'\n"
|
||||
"Applies to: BIBTEX output format")%', '.join(FIELDS)),
|
||||
|
||||
Option('--sort-by',
|
||||
default = 'id',
|
||||
dest = 'sort_by',
|
||||
action = None,
|
||||
help = _('Output field to sort on.\n'
|
||||
'Available fields: author_sort, id, rating, size, timestamp, title.\n'
|
||||
"Default: '%default'\n"
|
||||
"Applies to: BIBTEX output format")),
|
||||
|
||||
Option('--create-citation',
|
||||
default = 'True',
|
||||
dest = 'impcit',
|
||||
action = None,
|
||||
help = _('Create a citation for BibTeX entries.\n'
|
||||
'Boolean value: True, False\n'
|
||||
"Default: '%default'\n"
|
||||
"Applies to: BIBTEX output format")),
|
||||
|
||||
Option('--citation-template',
|
||||
default = '{authors}{id}',
|
||||
dest = 'bib_cit',
|
||||
action = None,
|
||||
help = _('The template for citation creation from database fields.\n'
|
||||
' Should be a template with {} enclosed fields.\n'
|
||||
'Available fields: %s.\n'
|
||||
"Default: '%%default'\n"
|
||||
"Applies to: BIBTEX output format")%', '.join(TEMPLATE_ALLOWED_FIELDS)),
|
||||
|
||||
Option('--choose-encoding',
|
||||
default = 'utf8',
|
||||
dest = 'bibfile_enc',
|
||||
action = None,
|
||||
help = _('BibTeX file encoding output.\n'
|
||||
'Available types: utf8, cp1252, ascii.\n'
|
||||
"Default: '%default'\n"
|
||||
"Applies to: BIBTEX output format")),
|
||||
|
||||
Option('--choose-encoding-configuration',
|
||||
default = 'strict',
|
||||
dest = 'bibfile_enctag',
|
||||
action = None,
|
||||
help = _('BibTeX file encoding flag.\n'
|
||||
'Available types: strict, replace, ignore, backslashreplace.\n'
|
||||
"Default: '%default'\n"
|
||||
"Applies to: BIBTEX output format")),
|
||||
|
||||
Option('--entry-type',
|
||||
default = 'book',
|
||||
dest = 'bib_entry',
|
||||
action = None,
|
||||
help = _('Entry type for BibTeX catalog.\n'
|
||||
'Available types: book, misc, mixed.\n'
|
||||
"Default: '%default'\n"
|
||||
"Applies to: BIBTEX output format"))]
|
||||
|
||||
def run(self, path_to_output, opts, db, notification=DummyReporter()):
|
||||
|
||||
from types import StringType, UnicodeType
|
||||
|
||||
from calibre.library.save_to_disk import preprocess_template
|
||||
#Bibtex functions
|
||||
from calibre.utils.bibtex import bibtex_author_format, utf8ToBibtex, ValidateCitationKey
|
||||
|
||||
def create_bibtex_entry(entry, fields, mode, template_citation,
|
||||
asccii_bibtex = True, citation_bibtex = True):
|
||||
|
||||
#Bibtex doesn't like UTF-8 but keep unicode until writing
|
||||
#Define starting chain or if book valid strict and not book return a Fail string
|
||||
|
||||
bibtex_entry = []
|
||||
if mode != "misc" and check_entry_book_valid(entry) :
|
||||
bibtex_entry.append(u'@book{')
|
||||
elif mode != "book" :
|
||||
bibtex_entry.append(u'@misc{')
|
||||
else :
|
||||
#case strict book
|
||||
return ''
|
||||
|
||||
if citation_bibtex :
|
||||
# Citation tag
|
||||
bibtex_entry.append(make_bibtex_citation(entry, template_citation, asccii_bibtex))
|
||||
bibtex_entry = [u' '.join(bibtex_entry)]
|
||||
|
||||
for field in fields:
|
||||
item = entry[field]
|
||||
#check if the field should be included (none or empty)
|
||||
if item is None:
|
||||
continue
|
||||
try:
|
||||
if len(item) == 0 :
|
||||
continue
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if field == 'authors' :
|
||||
bibtex_entry.append(u'author = "%s"' % bibtex_author_format(item))
|
||||
|
||||
elif field in ['title', 'publisher', 'cover', 'uuid',
|
||||
'author_sort', 'series'] :
|
||||
bibtex_entry.append(u'%s = "%s"' % (field, utf8ToBibtex(item, asccii_bibtex)))
|
||||
|
||||
elif field == 'id' :
|
||||
bibtex_entry.append(u'calibreid = "%s"' % int(item))
|
||||
|
||||
elif field == 'rating' :
|
||||
bibtex_entry.append(u'rating = "%s"' % int(item))
|
||||
|
||||
elif field == 'size' :
|
||||
bibtex_entry.append(u'%s = "%s octets"' % (field, int(item)))
|
||||
|
||||
elif field == 'tags' :
|
||||
#A list to flatten
|
||||
bibtex_entry.append(u'tags = "%s"' % utf8ToBibtex(u', '.join(item), asccii_bibtex))
|
||||
|
||||
elif field == 'comments' :
|
||||
#\n removal
|
||||
item = item.replace(u'\r\n',u' ')
|
||||
item = item.replace(u'\n',u' ')
|
||||
bibtex_entry.append(u'note = "%s"' % utf8ToBibtex(item, asccii_bibtex))
|
||||
|
||||
elif field == 'isbn' :
|
||||
# Could be 9, 10 or 13 digits
|
||||
bibtex_entry.append(u'isbn = "%s"' % re.sub(u'[\D]', u'', item))
|
||||
|
||||
elif field == 'formats' :
|
||||
item = u', '.join([format.rpartition('.')[2].lower() for format in item])
|
||||
bibtex_entry.append(u'formats = "%s"' % item)
|
||||
|
||||
elif field == 'series_index' :
|
||||
bibtex_entry.append(u'volume = "%s"' % int(item))
|
||||
|
||||
elif field == 'timestamp' :
|
||||
bibtex_entry.append(u'timestamp = "%s"' % isoformat(item).partition('T')[0])
|
||||
|
||||
elif field == 'pubdate' :
|
||||
bibtex_entry.append(u'year = "%s"' % item.year)
|
||||
bibtex_entry.append(u'month = "%s"' % utf8ToBibtex(strftime("%b", item),
|
||||
asccii_bibtex))
|
||||
|
||||
bibtex_entry = u',\n '.join(bibtex_entry)
|
||||
bibtex_entry += u' }\n\n'
|
||||
|
||||
return bibtex_entry
|
||||
|
||||
def check_entry_book_valid(entry):
|
||||
#Check that the required fields are ok for a book entry
|
||||
for field in ['title', 'authors', 'publisher'] :
|
||||
if entry[field] is None or len(entry[field]) == 0 :
|
||||
return False
|
||||
if entry['pubdate'] is None :
|
||||
return False
|
||||
else :
|
||||
return True
|
||||
|
||||
def make_bibtex_citation(entry, template_citation, asccii_bibtex):
|
||||
|
||||
#define a function to replace the template entry by its value
|
||||
def tpl_replace(objtplname) :
|
||||
|
||||
tpl_field = re.sub(u'[\{\}]', u'', objtplname.group())
|
||||
|
||||
if tpl_field in TEMPLATE_ALLOWED_FIELDS :
|
||||
if tpl_field in ['pubdate', 'timestamp'] :
|
||||
tpl_field = isoformat(entry[tpl_field]).partition('T')[0]
|
||||
elif tpl_field in ['tags', 'authors'] :
|
||||
tpl_field =entry[tpl_field][0]
|
||||
elif tpl_field in ['id', 'series_index'] :
|
||||
tpl_field = str(entry[tpl_field])
|
||||
else :
|
||||
tpl_field = entry[tpl_field]
|
||||
return tpl_field
|
||||
else:
|
||||
return u''
|
||||
|
||||
if len(template_citation) >0 :
|
||||
tpl_citation = utf8ToBibtex(ValidateCitationKey(re.sub(u'\{[^{}]*\}',
|
||||
tpl_replace, template_citation)), asccii_bibtex)
|
||||
|
||||
if len(tpl_citation) >0 :
|
||||
return tpl_citation
|
||||
|
||||
if len(entry["isbn"]) > 0 :
|
||||
template_citation = u'%s' % re.sub(u'[\D]',u'', entry["isbn"])
|
||||
|
||||
else :
|
||||
template_citation = u'%s' % str(entry["id"])
|
||||
|
||||
if asccii_bibtex :
|
||||
return ValidateCitationKey(template_citation.encode('ascii', 'replace'))
|
||||
else :
|
||||
return ValidateCitationKey(template_citation)
|
||||
|
||||
self.fmt = path_to_output.rpartition('.')[2]
|
||||
self.notification = notification
|
||||
|
||||
# Combobox options
|
||||
bibfile_enc = ['utf8', 'cp1252', 'ascii']
|
||||
bibfile_enctag = ['strict', 'replace', 'ignore', 'backslashreplace']
|
||||
bib_entry = ['mixed', 'misc', 'book']
|
||||
|
||||
# Needed beacause CLI return str vs int by widget
|
||||
try:
|
||||
bibfile_enc = bibfile_enc[opts.bibfile_enc]
|
||||
bibfile_enctag = bibfile_enctag[opts.bibfile_enctag]
|
||||
bib_entry = bib_entry[opts.bib_entry]
|
||||
except:
|
||||
if opts.bibfile_enc in bibfile_enc :
|
||||
bibfile_enc = opts.bibfile_enc
|
||||
else :
|
||||
log(" WARNING: incorrect --choose-encoding flag, revert to default")
|
||||
bibfile_enc = bibfile_enc[0]
|
||||
if opts.bibfile_enctag in bibfile_enctag :
|
||||
bibfile_enctag = opts.bibfile_enctag
|
||||
else :
|
||||
log(" WARNING: incorrect --choose-encoding-configuration flag, revert to default")
|
||||
bibfile_enctag = bibfile_enctag[0]
|
||||
if opts.bib_entry in bib_entry :
|
||||
bib_entry = opts.bib_entry
|
||||
else :
|
||||
log(" WARNING: incorrect --entry-type flag, revert to default")
|
||||
bib_entry = bib_entry[0]
|
||||
|
||||
if opts.verbose:
|
||||
opts_dict = vars(opts)
|
||||
log("%s(): Generating %s" % (self.name,self.fmt))
|
||||
if opts_dict['search_text']:
|
||||
log(" --search='%s'" % opts_dict['search_text'])
|
||||
|
||||
if opts_dict['ids']:
|
||||
log(" Book count: %d" % len(opts_dict['ids']))
|
||||
if opts_dict['search_text']:
|
||||
log(" (--search ignored when a subset of the database is specified)")
|
||||
|
||||
if opts_dict['fields']:
|
||||
if opts_dict['fields'] == 'all':
|
||||
log(" Fields: %s" % ', '.join(FIELDS[1:]))
|
||||
else:
|
||||
log(" Fields: %s" % opts_dict['fields'])
|
||||
|
||||
log(" Output file will be encoded in %s with %s flag" % (bibfile_enc, bibfile_enctag))
|
||||
|
||||
log(" BibTeX entry type is %s with a citation like '%s' flag" % (bib_entry, opts_dict['bib_cit']))
|
||||
|
||||
# If a list of ids are provided, don't use search_text
|
||||
if opts.ids:
|
||||
opts.search_text = None
|
||||
|
||||
data = self.search_sort_db(db, opts)
|
||||
|
||||
if not len(data):
|
||||
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
|
||||
|
||||
# Get the requested output fields as a list
|
||||
fields = self.get_output_fields(opts)
|
||||
|
||||
if not len(data):
|
||||
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
|
||||
|
||||
#Entries writing after Bibtex formating (or not)
|
||||
if bibfile_enc != 'ascii' :
|
||||
asccii_bibtex = False
|
||||
else :
|
||||
asccii_bibtex = True
|
||||
|
||||
#Check and go to default in case of bad CLI
|
||||
if isinstance(opts.impcit, (StringType, UnicodeType)) :
|
||||
if opts.impcit == 'False' :
|
||||
citation_bibtex= False
|
||||
elif opts.impcit == 'True' :
|
||||
citation_bibtex= True
|
||||
else :
|
||||
log(" WARNING: incorrect --create-citation, revert to default")
|
||||
citation_bibtex= True
|
||||
else :
|
||||
citation_bibtex= opts.impcit
|
||||
|
||||
template_citation = preprocess_template(opts.bib_cit)
|
||||
|
||||
#Open output and write entries
|
||||
outfile = codecs.open(path_to_output, 'w', bibfile_enc, bibfile_enctag)
|
||||
|
||||
#File header
|
||||
nb_entries = len(data)
|
||||
|
||||
#check in book strict if all is ok else throw a warning into log
|
||||
if bib_entry == 'book' :
|
||||
nb_books = len(filter(check_entry_book_valid, data))
|
||||
if nb_books < nb_entries :
|
||||
log(" WARNING: only %d entries in %d are book compatible" % (nb_books, nb_entries))
|
||||
nb_entries = nb_books
|
||||
|
||||
outfile.write(u'%%%Calibre catalog\n%%%{0} entries in catalog\n\n'.format(nb_entries))
|
||||
outfile.write(u'@preamble{"This catalog of %d entries was generated by calibre on %s"}\n\n'
|
||||
% (nb_entries, nowf().strftime("%A, %d. %B %Y %H:%M").decode(preferred_encoding)))
|
||||
|
||||
for entry in data:
|
||||
outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation,
|
||||
asccii_bibtex, citation_bibtex))
|
||||
|
||||
outfile.close()
|
||||
|
||||
class EPUB_MOBI(CatalogPlugin):
|
||||
'ePub catalog generator'
|
||||
|
@ -12,6 +12,7 @@ from math import floor
|
||||
|
||||
from calibre import prints
|
||||
from calibre.constants import preferred_encoding
|
||||
from calibre.library.field_metadata import FieldMetadata
|
||||
from calibre.utils.date import parse_date
|
||||
|
||||
class CustomColumns(object):
|
||||
@ -30,6 +31,10 @@ class CustomColumns(object):
|
||||
|
||||
|
||||
def __init__(self):
|
||||
# Verify that CUSTOM_DATA_TYPES is a (possibly improper) subset of
|
||||
# VALID_DATA_TYPES
|
||||
if len(self.CUSTOM_DATA_TYPES - FieldMetadata.VALID_DATA_TYPES) > 0:
|
||||
raise ValueError('Unknown custom column type in set')
|
||||
# Delete marked custom columns
|
||||
for record in self.conn.get(
|
||||
'SELECT id FROM custom_columns WHERE mark_for_delete=1'):
|
||||
|
@ -30,8 +30,8 @@ class FieldMetadata(dict):
|
||||
|
||||
label: the actual column label. No prefixing.
|
||||
|
||||
datatype: the type of the information in the field. Valid values are float,
|
||||
int, rating, bool, comments, datetime, text.
|
||||
datatype: the type of information in the field. Valid values are listed in
|
||||
VALID_DATA_TYPES below.
|
||||
is_multiple: valid for the text datatype. If None, the field is to be
|
||||
treated as a single term. If not None, it contains a string, and the field
|
||||
is assumed to contain a list of terms separated by that string
|
||||
@ -65,6 +65,10 @@ class FieldMetadata(dict):
|
||||
rec_index: the index of the field in the db metadata record.
|
||||
|
||||
'''
|
||||
|
||||
VALID_DATA_TYPES = frozenset([None, 'rating', 'text', 'comments', 'datetime',
|
||||
'int', 'float', 'bool', 'series'])
|
||||
|
||||
_field_metadata = [
|
||||
('authors', {'table':'authors',
|
||||
'column':'name',
|
||||
@ -296,6 +300,8 @@ class FieldMetadata(dict):
|
||||
self._search_term_map = {}
|
||||
self.custom_label_to_key_map = {}
|
||||
for k,v in self._field_metadata:
|
||||
if v['kind'] == 'field' and v['datatype'] not in self.VALID_DATA_TYPES:
|
||||
raise ValueError('Unknown datatype %s for field %s'%(v['datatype'], k))
|
||||
self._tb_cats[k] = v
|
||||
self._tb_cats[k]['label'] = k
|
||||
self._tb_cats[k]['display'] = {}
|
||||
@ -377,6 +383,8 @@ class FieldMetadata(dict):
|
||||
key = self.custom_field_prefix + label
|
||||
if key in self._tb_cats:
|
||||
raise ValueError('Duplicate custom field [%s]'%(label))
|
||||
if datatype not in self.VALID_DATA_TYPES:
|
||||
raise ValueError('Unknown datatype %s for field %s'%(datatype, key))
|
||||
self._tb_cats[key] = {'table':table, 'column':column,
|
||||
'datatype':datatype, 'is_multiple':is_multiple,
|
||||
'kind':'field', 'name':name,
|
||||
|
File diff suppressed because it is too large
Load Diff
2539
src/calibre/utils/bibtex.py
Normal file
2539
src/calibre/utils/bibtex.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -698,8 +698,8 @@ def _prefs():
|
||||
# calibre server can execute searches
|
||||
c.add_opt('saved_searches', default={}, help=_('List of named saved searches'))
|
||||
c.add_opt('user_categories', default={}, help=_('User-created tag browser categories'))
|
||||
c.add_opt('preserve_user_collections', default=True,
|
||||
help=_('Preserve all collections even if not in library metadata.'))
|
||||
c.add_opt('manage_device_metadata', default='manual',
|
||||
help=_('How and when calibre updates metadata on the device.'))
|
||||
|
||||
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
|
||||
return c
|
||||
|
Loading…
x
Reference in New Issue
Block a user