KG revisions

This commit is contained in:
GRiker 2010-07-16 16:02:40 -06:00
commit a5287acd47
54 changed files with 4385 additions and 911 deletions

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

View File

@ -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'),
]
]

View 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'

View File

@ -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')

View File

@ -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')

View 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'}),
]

View 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'}),
]

View File

@ -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">

View File

@ -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',

View File

@ -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'

View File

@ -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

View File

@ -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,

View File

@ -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'

View File

@ -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'

View File

@ -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:

View File

@ -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 = []

View File

@ -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)

View File

@ -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 = [

View File

@ -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)

View File

@ -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):

View File

@ -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.')

View File

@ -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.')

View File

@ -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

View 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

View 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>

View File

@ -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 &amp;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 &amp;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">

View File

@ -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())

View File

@ -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">

View File

@ -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>

View File

@ -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:

View File

@ -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)
# }}}

View File

@ -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()]

View File

@ -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([])

View File

@ -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 (&quot;the&quot;, &quot;a&quot;,
<attribute name="title">
<string>Sending to &amp;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 &amp;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>&lt;li&gt;&lt;b&gt;Manual Management&lt;/b&gt;: Calibre updates the metadata and adds collections only when a book is sent. With this option, calibre will never remove a collection.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Only on send&lt;/b&gt;: Calibre updates metadata and adds/removes collections for a book only when it is sent to the device. &lt;/li&gt;
&lt;li&gt;&lt;b&gt;Automatic management&lt;/b&gt;: Calibre automatically keeps metadata on the device in sync with the calibre library, on every connect&lt;/li&gt;&lt;/ul&gt;</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-&gt;Plugins</string>
@ -212,7 +261,7 @@ Title match ignores leading indefinite articles (&quot;the&quot;, &quot;a&quot;,
</property>
</widget>
</item>
<item>
<item row="4" column="0" colspan="3">
<widget class="SaveTemplate" name="send_template" native="true"/>
</item>
</layout>

View File

@ -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>&amp;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 &amp;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">

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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 = []

View File

@ -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

View File

@ -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'

View File

@ -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'):

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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