Merge from custcol trunk

This commit is contained in:
Charles Haley 2010-05-19 09:56:55 +01:00
commit c4e58f6d0a
20 changed files with 1136 additions and 575 deletions

View File

@ -0,0 +1,553 @@
<?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: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"
x="0.0000000"
y="0.0000000"
width="48.000000px"
height="48.000000px"
id="svg1"
sodipodi:version="0.32"
inkscape:version="0.44"
sodipodi:docname="folder.svg"
sodipodi:docbase="/home/lapo/Icone/Crux/crux-icon-theme/scalable/places"
inkscape:export-filename="/home/lapo/Icone/Crux/folderx-daritaliare.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<metadata
id="metadata162">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Folder</dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Lapo Calamandrei</dc:title>
</cc:Agent>
</dc:creator>
<dc:date>2006-06-26</dc:date>
<cc:license
rdf:resource="http://creativecommons.org/licenses/GPL/2.0/" />
<dc:identifier />
<dc:subject>
<rdf:Bag>
<rdf:li>folder</rdf:li>
<rdf:li>directory</rdf:li>
<rdf:li>storage</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/GPL/2.0/">
<cc:permits
rdf:resource="http://web.resource.org/cc/Reproduction" />
<cc:permits
rdf:resource="http://web.resource.org/cc/Distribution" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Notice" />
<cc:permits
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
<cc:requires
rdf:resource="http://web.resource.org/cc/ShareAlike" />
<cc:requires
rdf:resource="http://web.resource.org/cc/SourceCode" />
</cc:License>
</rdf:RDF>
</metadata>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666"
borderopacity="1"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:window-width="1041"
inkscape:window-height="655"
inkscape:cy="24.626698"
inkscape:cx="45.136759"
inkscape:zoom="8"
inkscape:document-units="px"
showgrid="false"
inkscape:window-x="504"
inkscape:window-y="101"
inkscape:current-layer="layer2"
inkscape:showpageshadow="false"
showguides="false"
inkscape:guide-bbox="true"
inkscape:object-paths="false"
gridspacingx="0.5px"
gridspacingy="0.5px"
gridempspacing="2"
inkscape:grid-points="false"
showborder="true"
borderlayer="true">
<sodipodi:guide
orientation="horizontal"
position="36.062446"
id="guide1934" />
<sodipodi:guide
orientation="horizontal"
position="15.003922"
id="guide1941" />
<sodipodi:guide
orientation="vertical"
position="4.5"
id="guide1943" />
<sodipodi:guide
orientation="vertical"
position="44.503533"
id="guide1945" />
<sodipodi:guide
orientation="horizontal"
position="43.125"
id="guide1947" />
<sodipodi:guide
orientation="horizontal"
position="39"
id="guide1949" />
<sodipodi:guide
orientation="horizontal"
position="19.003495"
id="guide2919" />
<sodipodi:guide
orientation="vertical"
position="0.97227183"
id="guide2212" />
<sodipodi:guide
orientation="vertical"
position="47.994873"
id="guide2214" />
<sodipodi:guide
orientation="horizontal"
position="111"
id="guide4328" />
<sodipodi:guide
orientation="vertical"
position="65.75"
id="guide3135" />
<sodipodi:guide
orientation="vertical"
position="129.75"
id="guide3137" />
<sodipodi:guide
orientation="vertical"
position="190.75"
id="guide3139" />
<sodipodi:guide
orientation="vertical"
position="212.48559"
id="guide3316" />
<sodipodi:guide
orientation="horizontal"
position="178.01413"
id="guide3318" />
</sodipodi:namedview>
<defs
id="defs3">
<linearGradient
inkscape:collect="always"
id="linearGradient4232">
<stop
style="stop-color:#ad7fa8;stop-opacity:1;"
offset="0"
id="stop4234" />
<stop
style="stop-color:#75507b;stop-opacity:1"
offset="1"
id="stop4236" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient3311">
<stop
style="stop-color:#888a85;stop-opacity:1;"
offset="0"
id="stop3313" />
<stop
style="stop-color:#555753;stop-opacity:1"
offset="1"
id="stop3315" />
</linearGradient>
<linearGradient
id="linearGradient3076">
<stop
id="stop3078"
offset="0"
style="stop-color:#5c3566;stop-opacity:1" />
<stop
id="stop3080"
offset="1"
style="stop-color:#5c3566;stop-opacity:0" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient2994">
<stop
style="stop-color:#5c3566;stop-opacity:1"
offset="0"
id="stop2996" />
<stop
style="stop-color:#5c3566;stop-opacity:0"
offset="1"
id="stop2998" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient3923">
<stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop3925" />
<stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop3927" />
</linearGradient>
<linearGradient
id="linearGradient3908">
<stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop3910" />
<stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop3912" />
</linearGradient>
<linearGradient
id="linearGradient2894">
<stop
style="stop-color:#39213f;stop-opacity:1;"
offset="0"
id="stop2896" />
<stop
id="stop2900"
offset="0.47619048"
style="stop-color:#75507b;stop-opacity:1;" />
<stop
style="stop-color:#5c3566;stop-opacity:1"
offset="1"
id="stop2898" />
</linearGradient>
<linearGradient
id="linearGradient3100">
<stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop3102" />
<stop
id="stop2071"
offset="1"
style="stop-color:white;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient3018">
<stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop3020" />
<stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop3022" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3100"
id="linearGradient3685"
gradientUnits="userSpaceOnUse"
x1="17.02047"
y1="-16.276186"
x2="17.02047"
y2="-29.344501" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2894"
id="linearGradient3687"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.996152,0,85.74795)"
x1="9.4176369"
y1="-44.922661"
x2="9.4176369"
y2="-59.636772" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3076"
id="linearGradient3689"
gradientUnits="userSpaceOnUse"
x1="16.749592"
y1="21.616077"
x2="16.749592"
y2="32.797989" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2994"
id="linearGradient3691"
gradientUnits="userSpaceOnUse"
x1="16.749592"
y1="40.51022"
x2="16.749592"
y2="36.268337" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3908"
id="linearGradient3693"
gradientUnits="userSpaceOnUse"
x1="8.25"
y1="-14.375"
x2="8.25"
y2="-30.879261" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3923"
id="radialGradient3695"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.268741,0,-1.646661e-6,0.184077,-10.86781,18.45272)"
cx="10.189716"
cy="16.554359"
fx="10.189716"
fy="16.554359"
r="22.5" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3311"
id="linearGradient3938"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,1)"
x1="20.625"
y1="-90.064087"
x2="20.625"
y2="-84.029831" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3018"
id="linearGradient3129"
gradientUnits="userSpaceOnUse"
x1="9"
y1="-92.805496"
x2="9"
y2="-83.4375" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4232"
id="linearGradient4238"
x1="0.99999888"
y1="30.499076"
x2="53.999733"
y2="37.624077"
gradientUnits="userSpaceOnUse" />
</defs>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="vectors"
style="display:inline">
<g
id="g891"
transform="matrix(0.186703,0,0,0.186703,-21.1073,57.62299)" />
<g
id="g3131"
inkscape:export-filename="/home/lapo/Icone/Crux/folderx.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
transform="translate(-1,102)">
<path
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/lapo/Icone/Crux/folderx-alt.png"
sodipodi:nodetypes="czcccccccscc"
id="path3108"
d="M 8.5,-94.500002 C 7.5975,-94.500002 7.168127,-94.186392 7,-93.04412 C 5.118026,-83.070943 5.215756,-74.96574 5.5,-64.864899 C 5.5,-64.115602 6.116307,-63.50001 6.875,-63.50001 L 42.125,-63.50001 C 42.88369,-63.50001 43.44816,-64.117293 43.5,-64.864899 C 44.5,-82.91177 42.5,-88.135111 42.5,-88.135111 C 42.5,-88.884408 41.88369,-89.5 41.125,-89.5 L 23.5,-89.5 L 22.5,-92.897058 C 22.254867,-93.729789 21.9025,-94.500002 21,-94.500002 L 8.5,-94.500002 z "
style="fill:url(#linearGradient3938);fill-opacity:1;stroke:#555753;stroke-width:0.99999934;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" />
<path
id="path3110"
d="M 8.5,-93.5 C 8.167757,-93.5 8.1531396,-93.465856 8.15625,-93.46875 C 8.1593604,-93.471644 8.0371607,-93.339789 7.96875,-92.875 C 7.9689149,-92.864584 7.9689149,-92.854166 7.96875,-92.84375 C 6.2697576,-83.840251 6.2178889,-74.709407 6.4375,-65.5 L 42.625,-65.5 C 43.308005,-81.554915 41.5625,-87.875 41.5625,-87.875 C 41.530831,-87.955244 41.509819,-88.039293 41.5,-88.125 C 41.5,-88.333118 41.341065,-88.5 41.125,-88.5 L 23.5,-88.5 C 23.062797,-88.50549 22.681309,-88.797964 22.5625,-89.21875 L 21.5625,-92.625 C 21.45311,-92.996605 21.310238,-93.289949 21.21875,-93.40625 C 21.127262,-93.522551 21.173845,-93.5 21,-93.5 L 8.5,-93.5 z "
style="opacity:0.5;fill:none;fill-opacity:1;stroke:url(#linearGradient3129);stroke-width:0.99999934;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" />
</g>
<g
style="display:inline"
transform="matrix(0.216083,0,0,0.263095,-1.89323,-11.2424)"
id="g3112"
inkscape:export-filename="/home/lapo/Icone/Crux/folderx.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<path
d="M 32.706693,164.36026 C 22.319193,164.36026 13.956693,172.72276 13.956693,183.11026 C 13.956693,193.49776 22.319193,201.86026 32.706693,201.86026 L 205.20669,201.86026 C 215.59419,201.86026 223.95669,193.49776 223.95669,183.11026 C 223.95669,172.72276 215.59419,164.36026 205.20669,164.36026 L 32.706693,164.36026 z "
style="opacity:0.04787233;fill-rule:evenodd;stroke-width:3pt"
id="path3114" />
<path
d="M 32.706693,165.61026 C 23.011693,165.61026 15.206693,173.41526 15.206693,183.11026 C 15.206693,192.80526 23.011693,200.61026 32.706693,200.61026 L 205.20669,200.61026 C 214.90169,200.61026 222.70669,192.80526 222.70669,183.11026 C 222.70669,173.41526 214.90169,165.61026 205.20669,165.61026 L 32.706693,165.61026 z "
style="opacity:0.04787233;fill-rule:evenodd;stroke-width:3pt"
id="path3116" />
<path
d="M 32.706694,166.86026 C 23.704194,166.86026 16.456694,174.10776 16.456694,183.11026 C 16.456694,192.11276 23.704194,199.36026 32.706694,199.36026 L 205.20669,199.36026 C 214.20919,199.36026 221.45669,192.11276 221.45669,183.11026 C 221.45669,174.10776 214.20919,166.86026 205.20669,166.86026 L 32.706694,166.86026 z "
style="opacity:0.04787233;fill-rule:evenodd;stroke-width:3pt"
id="path3118" />
<path
d="M 32.706694,168.11026 C 24.396694,168.11026 17.706694,174.80026 17.706694,183.11026 C 17.706694,191.42026 24.396694,198.11026 32.706694,198.11026 L 205.20669,198.11026 C 213.51669,198.11026 220.20669,191.42026 220.20669,183.11026 C 220.20669,174.80026 213.51669,168.11026 205.20669,168.11026 L 32.706694,168.11026 z "
style="opacity:0.04787233;fill-rule:evenodd;stroke-width:3pt"
id="path3120" />
<path
d="M 32.707764,169.36026 C 25.090264,169.36026 18.957764,175.49276 18.957764,183.11026 C 18.957764,190.72776 25.090264,196.86026 32.707764,196.86026 L 205.20618,196.86026 C 212.82368,196.86026 218.95618,190.72776 218.95618,183.11026 C 218.95618,175.49276 212.82368,169.36026 205.20618,169.36026 L 32.707764,169.36026 z "
style="opacity:0.04787233;fill-rule:evenodd;stroke-width:3pt"
id="path3122" />
<path
d="M 32.706694,170.61026 C 25.781694,170.61026 20.206694,176.18526 20.206694,183.11026 C 20.206694,190.03526 25.781694,195.61026 32.706694,195.61026 L 205.20669,195.61026 C 212.13169,195.61026 217.70669,190.03526 217.70669,183.11026 C 217.70669,176.18526 212.13169,170.61026 205.20669,170.61026 L 32.706694,170.61026 z "
style="opacity:0.04787233;fill-rule:evenodd;stroke-width:3pt"
id="path3124" />
<path
d="M 32.706694,171.86026 C 26.474194,171.86026 21.456694,176.87776 21.456694,183.11026 C 21.456694,189.34276 26.474194,194.36026 32.706694,194.36026 L 205.20669,194.36026 C 211.43919,194.36026 216.45669,189.34276 216.45669,183.11026 C 216.45669,176.87776 211.43919,171.86026 205.20669,171.86026 L 32.706694,171.86026 z "
style="opacity:0.04787233;fill-rule:evenodd;stroke-width:3pt"
id="path3126" />
<path
d="M 32.706694,173.11026 C 27.166694,173.11026 22.706694,177.57026 22.706694,183.11026 C 22.706694,188.65026 27.166694,193.11026 32.706694,193.11026 L 205.20669,193.11026 C 210.74669,193.11026 215.20669,188.65026 215.20669,183.11026 C 215.20669,177.57026 210.74669,173.11026 205.20669,173.11026 L 32.706694,173.11026 z "
style="opacity:0.04787233;fill-rule:evenodd;stroke-width:3pt"
id="path3128" />
</g>
<g
style="display:inline"
id="g3661"
transform="translate(-1,-2)"
inkscape:export-filename="/home/lapo/Icone/Crux/folderx.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<g
id="g3663">
<path
sodipodi:type="inkscape:offset"
inkscape:radius="-0.99436891"
inkscape:original="M 2.65625 -33.5 C 2.021877 -33.5 1.5 -33.140103 1.5 -32.6875 C 4.500001 -25.004483 4.5 -23.258435 4.5 -16.5 C 4.5 -15.5 5.5111718 -13.51291 6.65625 -13.4375 L 42.34375 -13.4375 C 43.989096 -13.51291 44.500268 -15.758435 44.5 -16.5 C 44.5 -21.258435 44.500267 -25.004482 47.5 -32.6875 C 47.5 -33.140104 46.978125 -33.5 46.34375 -33.5 L 2.65625 -33.5 z "
style="opacity:0.32156863;fill:none;fill-opacity:1;stroke:url(#linearGradient3685);stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;display:inline"
id="path3667"
d="M 2.625,-32.5 C 5.3597846,-25.370787 5.5,-23.037216 5.5,-16.5 C 5.5,-16.335159 5.6904313,-15.649773 6,-15.15625 C 6.3095687,-14.662727 6.6985247,-14.438832 6.71875,-14.4375 L 42.3125,-14.4375 L 42.34375,-14.4375 C 42.706073,-14.463351 42.916739,-14.679637 43.15625,-15.15625 C 43.401014,-15.643317 43.500051,-16.358058 43.5,-16.5 C 43.5,-21.130327 43.573003,-25.119501 46.375,-32.5 C 46.360642,-32.501163 46.358943,-32.5 46.34375,-32.5 L 2.65625,-32.5 C 2.6410568,-32.5 2.6393578,-32.501163 2.625,-32.5 z "
transform="translate(0,54)" />
<path
style="opacity:0.3254902;fill:none;fill-rule:evenodd;stroke:white;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
d="M 2.5,21.5 L 46.5,21.5"
id="path3669"
sodipodi:nodetypes="cc" />
</g>
<path
sodipodi:nodetypes="ccccccccc"
id="path3671"
d="M 2.645078,20.5 L 46.354654,20.5 C 46.989027,20.5 47.499732,20.862969 47.499732,21.313831 C 44.5,28.967284 44.5,32.694452 44.5,37.434576 C 44.500268,38.173288 44,40.423032 42.354654,40.498152 L 6.645078,40.498152 C 5.5,40.423032 4.5,38.430728 4.5,37.434576 C 4.5,30.702148 4.5,28.967284 1.499999,21.313831 C 1.499999,20.862969 2.010705,20.5 2.645078,20.5 z "
style="fill:url(#linearGradient4238);fill-opacity:1;stroke:url(#linearGradient3687);stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;display:inline" />
<path
id="path3673"
d="M 2.65625,21 C 2.4314409,21 2.2397168,21.044736 2.125,21.125 C 2.0102832,21.205264 2,21.274638 2,21.3125 C 4.2218558,27.007434 4.7883072,29.513938 4.9375,33.3125 L 44.125,33.3125 C 44.328358,30.089308 44.943507,26.607344 47,21.3125 C 47,21.274637 46.989716,21.205264 46.875,21.125 C 46.760284,21.044736 46.56856,21 46.34375,21 L 2.65625,21 z "
style="opacity:0.8;fill:url(#linearGradient3689);fill-opacity:1;stroke:none;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;display:inline" />
<path
id="path3675"
d="M 4.625,30 C 4.9603306,32.150219 5,34.257337 5,37.4375 C 5,37.771087 5.2086862,38.497978 5.5625,39.0625 C 5.9077942,39.613428 6.3538112,39.964089 6.65625,40 L 6.6875,40 L 42.3125,40 L 42.34375,40 C 42.929781,39.95968 43.308738,39.567494 43.59375,39 C 43.883899,38.422277 44.000093,37.694547 44,37.4375 C 44,34.957143 44.019836,32.683092 44.46875,30 L 4.625,30 z "
style="opacity:0.44313725;fill:url(#linearGradient3691);fill-opacity:1;stroke:none;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;display:inline" />
<path
transform="translate(0,54)"
d="M 2.625,-32.5 C 5.3597846,-25.370787 5.5,-23.037216 5.5,-16.5 C 5.5,-16.335159 5.6904313,-15.649773 6,-15.15625 C 6.3095687,-14.662727 6.6985247,-14.438832 6.71875,-14.4375 L 42.3125,-14.4375 L 42.34375,-14.4375 C 42.706073,-14.463351 42.916739,-14.679637 43.15625,-15.15625 C 43.401014,-15.643317 43.500051,-16.358058 43.5,-16.5 C 43.5,-21.130327 43.573003,-25.119501 46.375,-32.5 C 46.360642,-32.501163 46.358943,-32.5 46.34375,-32.5 L 2.65625,-32.5 C 2.6410568,-32.5 2.6393578,-32.501163 2.625,-32.5 z "
id="path3677"
style="opacity:0.2;fill:none;fill-opacity:1;stroke:url(#linearGradient3693);stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;display:inline"
inkscape:original="M 2.65625 -33.5 C 2.021877 -33.5 1.5 -33.140103 1.5 -32.6875 C 4.500001 -25.004483 4.5 -23.258435 4.5 -16.5 C 4.5 -15.5 5.5111718 -13.51291 6.65625 -13.4375 L 42.34375 -13.4375 C 43.989096 -13.51291 44.500268 -15.758435 44.5 -16.5 C 44.5 -21.258435 44.500267 -25.004482 47.5 -32.6875 C 47.5 -33.140104 46.978125 -33.5 46.34375 -33.5 L 2.65625 -33.5 z "
inkscape:radius="-0.99436891"
sodipodi:type="inkscape:offset" />
<path
sodipodi:nodetypes="cc"
id="path3679"
d="M 2.5,21.5 L 46.5,21.5"
style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#radialGradient3695);stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
id="g6102"
inkscape:label="pixmap"
style="display:inline"
transform="translate(188.7605,-103.2651)" />
<g
transform="matrix(0.186703,0,0,0.186703,167.6532,-45.64211)"
id="g6106" />
<g
inkscape:label="pattern"
id="g30621"
inkscape:r_cx="true"
inkscape:r_cy="true"
transform="translate(190.4218,35.092)" />
<g
inkscape:label="pattern"
id="g35222"
inkscape:r_cx="true"
inkscape:r_cy="true"
transform="translate(190.4218,81.092)" />
<g
id="g3198"
inkscape:export-filename="/home/lapo/Icone/Crux/folderx.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
transform="translate(-1,102)">
<rect
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/lapo/Icone/Crux/folderx-alt.png"
ry="1.5"
rx="1.5"
y="-91.5"
x="9.4999981"
height="3"
width="10.000002"
id="rect2118"
style="fill:#eeeeec;fill-opacity:1;stroke:#d3d7cf;stroke-width:0.99999988;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" />
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/lapo/Icone/Crux/folderx-alt.png"
transform="translate(66,0)"
id="g3371"
style="display:inline">
<rect
style="opacity:1;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4.4000001;stroke-dasharray:none;stroke-dashoffset:10;stroke-opacity:1"
id="rect3373"
width="1"
height="1"
x="-55"
y="-90"
rx="0.5"
ry="0.5" />
<rect
style="opacity:1;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4.4000001;stroke-dasharray:none;stroke-dashoffset:10;stroke-opacity:1;display:inline"
id="rect3375"
width="1"
height="1"
x="-53"
y="-90"
rx="0.5"
ry="0.5" />
<rect
style="opacity:1;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4.4000001;stroke-dasharray:none;stroke-dashoffset:10;stroke-opacity:1;display:inline"
id="rect3377"
width="1"
height="1"
x="-51"
y="-90"
rx="0.5"
ry="0.5" />
<rect
style="opacity:1;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4.4000001;stroke-dasharray:none;stroke-dashoffset:10;stroke-opacity:1;display:inline"
id="rect3379"
width="1"
height="1"
x="-49"
y="-90"
rx="0.5"
ry="0.5" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,26 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe
class AmericanProspect(BasicNewsRecipe):
title = u'American Prospect'
__author__ = u'Michael Heinz'
oldest_article = 30
language = 'en'
max_articles_per_feed = 100
recursions = 0
no_stylesheets = True
remove_javascript = True
preprocess_regexps = [
(re.compile(r'<body.*?<div class="pad_10L10R">', re.DOTALL|re.IGNORECASE), lambda match: '<body><div>'),
(re.compile(r'</div>.*</body>', re.DOTALL|re.IGNORECASE), lambda match: '</div></body>'),
(re.compile('\r'),lambda match: ''),
(re.compile(r'<!-- .+? -->', re.DOTALL|re.IGNORECASE), lambda match: ''),
(re.compile(r'<link .+?>', re.DOTALL|re.IGNORECASE), lambda match: ''),
(re.compile(r'<script.*?</script>', re.DOTALL|re.IGNORECASE), lambda match: ''),
(re.compile(r'<noscript.*?</noscript>', re.DOTALL|re.IGNORECASE), lambda match: ''),
(re.compile(r'<meta .*?/>', re.DOTALL|re.IGNORECASE), lambda match: ''),
]
feeds = [(u'Articles', u'feed://www.prospect.org/articles_rss.jsp')]

View File

@ -0,0 +1,19 @@
from calibre.web.feeds.news import BasicNewsRecipe
class FactCheckOrg(BasicNewsRecipe):
title = u'Factcheck'
__author__ = u'Michael Heinz'
language = 'en'
oldest_article = 7
max_articles_per_feed = 100
recursion = 0
publication_type = 'magazine'
masthead_url = 'http://factcheck.org/wp-content/themes/Streamline/images/headernew.jpg'
cover_url = 'http://factcheck.org/wp-content/themes/Streamline/images/headernew.jpg'
remove_tags = [ dict({'id':['footer','footerabout','sidebar']}) ]
feeds = [(u'Factcheck', u'feed://www.factcheck.org/feed/')]

View File

@ -0,0 +1,30 @@
from calibre.wb.feeds.news import BasicNewsRecipe
class PolitiFactCom(BasicNewsRecipe):
title = u'Politifact'
__author__ = u'Michael Heinz'
oldest_article = 21
max_articles_per_feed = 100
recursion = 0
language = 'en'
no_stylesheets = True
publication_type = 'magazine'
masthead_url = 'http://static.politifact.com.s3.amazonaws.com/images/politifactdotcom-flag-fff_01.png'
cover_url = 'http://static.politifact.com.s3.amazonaws.com/images/politifactdotcom-flag-fff_01.png'
remove_tags = [
dict(name='div', attrs={'class':'pfstoryarchive'}),
dict(name='div', attrs={'class':'pfhead'}),
dict(name='div', attrs={'class':'boxmid'}),
]
keep_only_tags = [dict(name='div', attrs={'class':'pfcontentleft'})]
feeds = [
(u'Articles', u'http://www.politifact.com/feeds/articles/truth-o-meter/'),
(u'Obamameter', u'http://politifact.com/feeds/updates/'),
(u'Statements', u'http://www.politifact.com/feeds/statements/truth-o-meter/')
]

View File

@ -42,7 +42,7 @@ class FOLDER_DEVICE(USBMS):
SUPPORTS_SUB_DIRS = True
#: Icon for this device
icon = I('sd.svg')
icon = I('devices/folder.svg')
METADATA_CACHE = '.metadata.calibre'
_main_prefix = ''

View File

@ -1,370 +0,0 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''
'''
import re, time, functools
from uuid import uuid4 as _uuid
import xml.dom.minidom as dom
from base64 import b64encode as encode
from calibre.devices.usbms.books import BookList as _BookList
from calibre.devices import strftime as _strftime
from calibre.devices.prs505 import MEDIA_XML, CACHE_XML
from calibre.devices.errors import PathError
strftime = functools.partial(_strftime, zone=time.gmtime)
MIME_MAP = {
"lrf" : "application/x-sony-bbeb",
'lrx' : 'application/x-sony-bbeb',
"rtf" : "application/rtf",
"pdf" : "application/pdf",
"txt" : "text/plain" ,
'epub': 'application/epub+zip',
}
def uuid():
return str(_uuid()).replace('-', '', 1).upper()
def sortable_title(title):
return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', title).rstrip()
class BookList(_BookList):
def __init__(self, oncard, prefix, settings):
_BookList.__init__(self, oncard, prefix, settings)
if prefix is None:
return
self.sony_id_cache = {}
self.books_lpath_cache = {}
opts = settings()
self.collections = opts.extra_customization.split(',') if opts.extra_customization else []
db = CACHE_XML if oncard else MEDIA_XML
with open(prefix + db, 'rb') as xml_file:
xml_file.seek(0)
self.document = dom.parse(xml_file)
self.root_element = self.document.documentElement
self.mountpath = prefix
records = self.root_element.getElementsByTagName('records')
if records:
self.prefix = 'xs1:'
self.root_element = records[0]
else:
self.prefix = ''
for child in self.root_element.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
self.sony_id_cache[child.getAttribute('id')] = child.getAttribute('path')
# set the key to none. Will be filled in later when booklist is built
self.books_lpath_cache[child.getAttribute('path')] = None
self.tag_order = {}
paths = self.purge_corrupted_files()
for path in paths:
try:
self.del_file(path, end_session=False)
except PathError: # Incase this is a refetch without a sync in between
continue
def max_id(self):
max = 0
for child in self.root_element.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
nid = int(child.getAttribute('id'))
if nid > max:
max = nid
return max
def is_id_valid(self, id):
'''Return True iff there is an element with C{id==id}.'''
id = str(id)
for child in self.root_element.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
if child.getAttribute('id') == id:
return True
return False
def supports_tags(self):
return True
def add_book(self, book, replace_metadata):
# Add a node into the DOM tree, representing a book. Also add to booklist
if book in self:
# replacing metadata for book
self.delete_node(book.lpath)
else:
self.append(book)
if not replace_metadata:
if self.books_lpath_cache.has_key(book.lpath):
self.books_lpath_cache[book.lpath] = book
return
# Book not in metadata. Add it. Note that we don't need to worry about
# extra books in the Sony metadata. The reader deletes them for us when
# we disconnect. That said, if it becomes important one day, we can do
# it by scanning the books_lpath_cache for None entries and removing the
# corresponding nodes.
self.books_lpath_cache[book.lpath] = book
cid = self.max_id()+1
node = self.document.createElement(self.prefix + "text")
self.sony_id_cache[cid] = book.lpath
mime = MIME_MAP.get(book.lpath.rpartition('.')[-1].lower(), MIME_MAP['epub'])
try:
sourceid = str(self[0].sourceid) if len(self) else '1'
except:
sourceid = '1'
attrs = {
"title" : book.title,
'titleSorter' : sortable_title(book.title),
"author" : book.format_authors() if book.format_authors() else _('Unknown'),
"page":"0", "part":"0", "scale":"0", \
"sourceid":sourceid, "id":str(cid), "date":"", \
"mime":mime, "path":book.lpath, "size":str(book.size)
}
for attr in attrs.keys():
node.setAttributeNode(self.document.createAttribute(attr))
node.setAttribute(attr, attrs[attr])
try:
w, h, data = book.thumbnail
except:
w, h, data = None, None, None
if data:
th = self.document.createElement(self.prefix + "thumbnail")
th.setAttribute("width", str(w))
th.setAttribute("height", str(h))
jpeg = self.document.createElement(self.prefix + "jpeg")
jpeg.appendChild(self.document.createTextNode(encode(data)))
th.appendChild(jpeg)
node.appendChild(th)
self.root_element.appendChild(node)
tags = []
for item in self.collections:
item = item.strip()
mitem = getattr(book, item, None)
titems = []
if mitem:
if isinstance(mitem, list):
titems = mitem
else:
titems = [mitem]
if item == 'tags' and titems:
litems = []
for i in titems:
if not i.strip().startswith('[') and not i.strip().endswith(']'):
litems.append(i)
titems = litems
tags.extend(titems)
if tags:
tags = list(set(tags))
if hasattr(book, 'tag_order'):
self.tag_order.update(book.tag_order)
self.set_playlists(cid, tags)
return True # metadata cache has changed. Must sync at end
def _delete_node(self, node):
nid = node.getAttribute('id')
self.remove_from_playlists(nid)
node.parentNode.removeChild(node)
node.unlink()
def delete_node(self, lpath):
'''
Remove DOM node corresponding to book with lpath.
Also remove book from any collections it is part of.
'''
for child in self.root_element.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
if child.getAttribute('path') == lpath:
self._delete_node(child)
break
def remove_book(self, book):
'''
Remove DOM node corresponding to book with C{path == path}.
Also remove book from any collections it is part of, and remove
from the booklist
'''
self.remove(book)
self.delete_node(book.lpath)
def playlists(self):
ans = []
for c in self.root_element.childNodes:
if hasattr(c, 'tagName') and c.tagName.endswith('playlist'):
ans.append(c)
return ans
def playlist_items(self):
plitems = []
for pl in self.playlists():
for c in pl.childNodes:
if hasattr(c, 'tagName') and c.tagName.endswith('item') and \
hasattr(c, 'getAttribute'):
try:
c.getAttribute('id')
except: # Unlinked node
continue
plitems.append(c)
return plitems
def purge_corrupted_files(self):
if not self.root_element:
return []
corrupted = self.root_element.getElementsByTagName(self.prefix+'corrupted')
paths = []
for c in corrupted:
paths.append(c.getAttribute('path'))
c.parentNode.removeChild(c)
c.unlink()
return paths
def purge_empty_playlists(self):
''' Remove all playlists that have no children. Also removes any invalid playlist items.'''
for pli in self.playlist_items():
try:
if not self.is_id_valid(pli.getAttribute('id')):
pli.parentNode.removeChild(pli)
pli.unlink()
except:
continue
for pl in self.playlists():
empty = True
for c in pl.childNodes:
if hasattr(c, 'tagName') and c.tagName.endswith('item'):
empty = False
break
if empty:
pl.parentNode.removeChild(pl)
pl.unlink()
def playlist_by_title(self, title):
for pl in self.playlists():
if pl.getAttribute('title').lower() == title.lower():
return pl
def add_playlist(self, title):
cid = self.max_id()+1
pl = self.document.createElement(self.prefix+'playlist')
pl.setAttribute('id', str(cid))
pl.setAttribute('title', title)
pl.setAttribute('uuid', uuid())
self.root_element.insertBefore(pl, self.root_element.childNodes[-1])
return pl
def remove_from_playlists(self, id):
for pli in self.playlist_items():
if pli.getAttribute('id') == str(id):
pli.parentNode.removeChild(pli)
pli.unlink()
def set_tags(self, book, tags):
tags = [t for t in tags if t]
book.tags = tags
self.set_playlists(book.id, tags)
def set_playlists(self, id, collections):
self.remove_from_playlists(id)
for collection in set(collections):
coll = self.playlist_by_title(collection)
if not coll:
coll = self.add_playlist(collection)
item = self.document.createElement(self.prefix+'item')
item.setAttribute('id', str(id))
coll.appendChild(item)
def next_id(self):
return self.document.documentElement.getAttribute('nextID')
def set_next_id(self, id):
self.document.documentElement.setAttribute('nextID', str(id))
def write(self, stream):
""" Write XML representation of DOM tree to C{stream} """
src = self.document.toxml('utf-8') + '\n'
stream.write(src.replace("'", '&apos;'))
def reorder_playlists(self):
for title in self.tag_order.keys():
pl = self.playlist_by_title(title)
if not pl:
continue
# make a list of the ids
sony_ids = [id.getAttribute('id') \
for id in pl.childNodes if hasattr(id, 'getAttribute')]
# convert IDs in playlist to a list of lpaths
sony_paths = [self.sony_id_cache[id] for id in sony_ids]
# create list of books containing lpaths
books = [self.books_lpath_cache.get(p, None) for p in sony_paths]
# create dict of db_id -> sony_id
imap = {}
for book, sony_id in zip(books, sony_ids):
if book is not None:
db_id = book.application_id
if db_id is None:
db_id = book.db_id
if db_id is not None:
imap[book.application_id] = sony_id
# filter the list, removing books not on device but on playlist
books = [i for i in books if i is not None]
# filter the order specification to the books we have
ordered_ids = [db_id for db_id in self.tag_order[title] if db_id in imap]
# rewrite the playlist in the correct order
if len(ordered_ids) < len(pl.childNodes):
continue
children = [i for i in pl.childNodes if hasattr(i, 'getAttribute')]
for child in children:
pl.removeChild(child)
child.unlink()
for id in ordered_ids:
item = self.document.createElement(self.prefix+'item')
item.setAttribute('id', str(imap[id]))
pl.appendChild(item)
def fix_ids(main, carda, cardb):
'''
Adjust ids the XML databases.
'''
if hasattr(main, 'purge_empty_playlists'):
main.purge_empty_playlists()
if hasattr(carda, 'purge_empty_playlists'):
carda.purge_empty_playlists()
if hasattr(cardb, 'purge_empty_playlists'):
cardb.purge_empty_playlists()
def regen_ids(db):
if not hasattr(db, 'root_element'):
return
id_map = {}
db.purge_empty_playlists()
cid = 0 if db == main else 1
for child in db.root_element.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute('id'):
id_map[child.getAttribute('id')] = str(cid)
child.setAttribute("sourceid",
'0' if getattr(child, 'tagName', '').endswith('playlist') else '1')
child.setAttribute('id', str(cid))
cid += 1
for item in db.playlist_items():
oid = item.getAttribute('id')
try:
item.setAttribute('id', id_map[oid])
except KeyError:
item.parentNode.removeChild(item)
item.unlink()
db.reorder_playlists()
db.sony_id_cache = {}
for child in db.root_element.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
db.sony_id_cache[child.getAttribute('id')] = child.getAttribute('path')
regen_ids(main)
regen_ids(carda)
regen_ids(cardb)
main.set_next_id(str(main.max_id()+1))

View File

@ -13,9 +13,9 @@ import os
import re
from calibre.devices.usbms.driver import USBMS
from calibre.devices.prs505.books import BookList as PRS_BookList, fix_ids
from calibre.devices.prs505 import MEDIA_XML
from calibre.devices.prs505 import CACHE_XML
from calibre.devices.prs505.sony_cache import XMLCache
from calibre import __appname__
class PRS505(USBMS):
@ -27,8 +27,6 @@ class PRS505(USBMS):
supported_platforms = ['windows', 'osx', 'linux']
path_sep = '/'
booklist_class = PRS_BookList # See usbms.driver for some explanation of this
FORMATS = ['epub', 'lrf', 'lrx', 'rtf', 'pdf', 'txt']
VENDOR_ID = [0x054c] #: SONY Vendor Id
@ -72,23 +70,34 @@ class PRS505(USBMS):
fname = base + suffix + '.' + fname.rpartition('.')[-1]
return fname
def sync_booklists(self, booklists, end_session=True):
fix_ids(*booklists)
if not os.path.exists(self._main_prefix):
os.makedirs(self._main_prefix)
with open(self._main_prefix + MEDIA_XML, 'wb') as f:
booklists[0].write(f)
def initialize_XML_cache(self):
paths = {}
for prefix, path, source_id in [
('main', MEDIA_XML, 0),
('card_a', CACHE_XML, 1),
('card_b', CACHE_XML, 2)
]:
prefix = getattr(self, '_%s_prefix'%prefix)
if prefix is not None and os.path.exists(prefix):
paths[source_id] = os.path.join(prefix, *(path.split('/')))
d = os.path.dirname(paths[source_id])
if not os.path.exists(d):
os.makedirs(d)
return XMLCache(paths)
def write_card_prefix(prefix, listid):
if prefix is not None and hasattr(booklists[listid], 'write'):
tgt = os.path.join(prefix, *(CACHE_XML.split('/')))
base = os.path.dirname(tgt)
if not os.path.exists(base):
os.makedirs(base)
with open(tgt, 'wb') as f:
booklists[listid].write(f)
write_card_prefix(self._card_a_prefix, 1)
write_card_prefix(self._card_b_prefix, 2)
def books(self, oncard=None, end_session=True):
bl = USBMS.books(self, oncard=oncard, end_session=end_session)
c = self.initialize_XML_cache()
c.update_booklist(bl, {'carda':1, 'cardb':2}.get(oncard, 0))
return bl
def sync_booklists(self, booklists, end_session=True):
c = self.initialize_XML_cache()
blists = {}
for i in c.paths:
blists[i] = booklists[i]
c.update(blists)
c.write()
USBMS.sync_booklists(self, booklists, end_session)

View File

@ -0,0 +1,249 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os
from pprint import pprint
from base64 import b64decode
from lxml import etree
from calibre import prints
from calibre.devices.errors import DeviceError
from calibre.constants import DEBUG
from calibre.ebooks.chardet import xml_to_unicode
from calibre.ebooks.metadata import string_to_authors
EMPTY_CARD_CACHE = '''\
<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns="http://www.kinoma.com/FskCache/1">
</cache>
'''
class XMLCache(object):
def __init__(self, paths):
if DEBUG:
pprint(paths)
self.paths = paths
parser = etree.XMLParser(recover=True)
self.roots = {}
for source_id, path in paths.items():
if source_id == 0:
if not os.path.exists(path):
raise DeviceError('The SONY XML cache media.xml does not exist. Try'
' disconnecting and reconnecting your reader.')
with open(path, 'rb') as f:
raw = f.read()
else:
raw = EMPTY_CARD_CACHE
if os.access(path, os.R_OK):
with open(path, 'rb') as f:
raw = f.read()
self.roots[source_id] = etree.fromstring(xml_to_unicode(
raw, strip_encoding_pats=True, assume_utf8=True,
verbose=DEBUG)[0],
parser=parser)
recs = self.roots[0].xpath('//*[local-name()="records"]')
if not recs:
raise DeviceError('The SONY XML database is corrupted (no <records>)')
self.record_roots = {}
self.record_roots.update(self.roots)
self.record_roots[0] = recs[0]
self.detect_namespaces()
# Playlist management {{{
def purge_broken_playlist_items(self, root):
for item in root.xpath(
'//*[local-name()="playlist"]/*[local-name()="item"]'):
id_ = item.get('id', None)
if id_ is None or not root.xpath(
'//*[local-name()!="item" and @id="%s"]'%id_):
if DEBUG:
prints('Purging broken playlist item:',
etree.tostring(item, with_tail=False))
item.getparent().remove(item)
def prune_empty_playlists(self):
for i, root in self.record_roots.items():
self.purge_broken_playlist_items(root)
for playlist in root.xpath('//*[local-name()="playlist"]'):
if len(playlist) == 0:
if DEBUG:
prints('Removing playlist:', playlist.get('id', None))
playlist.getparent().remove(playlist)
# }}}
def fix_ids(self): # {{{
def ensure_numeric_ids(root):
idmap = {}
for x in root.xpath('//*[@id]'):
id_ = x.get('id')
try:
id_ = int(id_)
except:
x.set('id', '-1')
idmap[id_] = '-1'
if DEBUG and idmap:
prints('Found non numeric ids:')
prints(list(idmap.keys()))
return idmap
def remap_playlist_references(root, idmap):
for playlist in root.xpath('//*[local-name()="playlist"]'):
for item in playlist.xpath(
'descendant::*[@id and local-name()="item"]'):
id_ = item.get('id')
if id_ in idmap:
item.set('id', idmap[id_])
if DEBUG:
prints('Remapping id %s to %s'%(id_, idmap[id_]))
def ensure_media_xml_base_ids(root):
for num, tag in enumerate(('library', 'watchSpecial')):
for x in root.xpath('//*[local-name()="%s"]'%tag):
x.set('id', str(num))
def rebase_ids(root, base, sourceid, pl_sourceid):
'Rebase all ids and also make them consecutive'
for item in root.xpath('//*[@sourceid]'):
sid = pl_sourceid if item.tag.endswith('playlist') else sourceid
item.set('sourceid', str(sid))
items = root.xpath('//*[@id]')
items.sort(cmp=lambda x,y:cmp(int(x.get('id')), int(y.get('id'))))
idmap = {}
for i, item in enumerate(items):
old = int(item.get('id'))
new = base + i
if old != new:
item.set('id', str(new))
idmap[old] = str(new)
return idmap
self.prune_empty_playlists()
for i in sorted(self.roots.keys()):
root = self.roots[i]
if i == 0:
ensure_media_xml_base_ids(root)
idmap = ensure_numeric_ids(root)
remap_playlist_references(root, idmap)
if i == 0:
sourceid, playlist_sid = 1, 0
base = 0
else:
previous = i-1
if previous not in self.roots:
previous = 0
max_id = self.max_id(self.roots[previous])
sourceid = playlist_sid = max_id + 1
base = max_id + 2
idmap = rebase_ids(root, base, sourceid, playlist_sid)
remap_playlist_references(root, idmap)
last_bl = max(self.roots.keys())
max_id = self.max_id(self.roots[last_bl])
self.roots[0].set('nextID', str(max_id+1))
# }}}
def update_booklist(self, bl, bl_index): # {{{
if bl_index not in self.record_roots:
return
root = self.record_roots[bl_index]
for book in bl:
record = self.book_by_lpath(book.lpath, root)
if record is not None:
title = record.get('title', None)
if title is not None and title != book.title:
if DEBUG:
prints('Renaming title', book.title, 'to', title)
book.title = title
authors = record.get('author', None)
if authors is not None:
authors = string_to_authors(authors)
if authors != book.authors:
if DEBUG:
prints('Renaming authors', book.authors, 'to',
authors)
book.authors = authors
for thumbnail in record.xpath(
'descendant::*[local-name()="thumbnail"]'):
for img in thumbnail.xpath(
'descendant::*[local-name()="jpeg"]|'
'descendant::*[local-name()="png"]'):
if img.text:
raw = b64decode(img.text.strip())
ext = img.tag.split('}')[-1]
book.cover_data = [ext, raw]
break
break
# }}}
def update(self, booklists):
pass
def write(self):
return
for i, path in self.paths.items():
raw = etree.tostring(self.roots[i], encoding='utf-8',
xml_declaration=True)
with open(path, 'wb') as f:
f.write(raw)
def book_by_lpath(self, lpath, root):
matches = root.xpath(u'//*[local-name()="text" and @path="%s"]'%lpath)
if matches:
return matches[0]
def max_id(self, root):
ans = -1
for x in root.xpath('//*[@id]'):
id_ = x.get('id')
try:
num = int(id_)
if num > ans:
ans = num
except:
continue
return ans
def detect_namespaces(self):
self.nsmaps = {}
for i, root in self.roots.items():
self.nsmaps[i] = root.nsmap
self.namespaces = {}
for i in self.roots:
for c in ('library', 'text', 'image', 'playlist', 'thumbnail',
'watchSpecial'):
matches = self.record_roots[i].xpath('//*[local-name()="%s"]'%c)
if matches:
e = matches[0]
self.namespaces[i] = e.nsmap[e.prefix]
break
if i not in self.namespaces:
ns = self.nsmaps[i].get(None, None)
for prefix in self.nsmaps[i]:
if prefix is not None:
ns = self.nsmaps[i][prefix]
break
self.namespaces[i] = ns
if DEBUG:
prints('Found nsmaps:')
pprint(self.nsmaps)
prints('Found namespaces:')
pprint(self.namespaces)

View File

@ -8,7 +8,7 @@ from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSiz
QByteArray, QTranslator, QCoreApplication, QThread, \
QEvent, QTimer, pyqtSignal, QDate
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
QIcon, QTableView, QApplication, QDialog, QPushButton
QIcon, QApplication, QDialog, QPushButton
ORG_NAME = 'KovidsBrain'
APP_UID = 'libprs500'
@ -294,34 +294,6 @@ class GetMetadata(QObject):
mi = MetaInformation('', [_('Unknown')])
self.emit(SIGNAL('metadata(PyQt_PyObject, PyQt_PyObject)'), id, mi)
class TableView(QTableView):
def __init__(self, parent):
QTableView.__init__(self, parent)
self.read_settings()
def read_settings(self):
self.cw = dynamic[self.__class__.__name__+'column width map']
def write_settings(self):
m = dynamic[self.__class__.__name__+'column width map']
if m is None:
m = {}
cmap = getattr(self.model(), 'column_map', None)
if cmap is not None:
for i,c in enumerate(cmap):
m[c] = self.columnWidth(i)
dynamic[self.__class__.__name__+'column width map'] = m
self.cw = m
def restore_column_widths(self):
if self.cw and len(self.cw):
for i,c in enumerate(self.model().column_map):
if c in self.cw:
self.setColumnWidth(i, self.cw[c])
return True
return False
class FileIconProvider(QFileIconProvider):
ICONS = {

View File

@ -327,15 +327,17 @@ class DeviceManager(Thread):
class DeviceAction(QAction):
a_s = pyqtSignal(object)
def __init__(self, dest, delete, specific, icon_path, text, parent=None):
if delete:
text += ' ' + _('and delete from library')
QAction.__init__(self, QIcon(icon_path), text, parent)
self.dest = dest
self.delete = delete
self.specific = specific
self.connect(self, SIGNAL('triggered(bool)'),
lambda x : self.emit(SIGNAL('a_s(QAction)'), self))
self.triggered.connect(self.emit_triggered)
def emit_triggered(self, *args):
self.a_s.emit(self)
def __repr__(self):
return self.__class__.__name__ + ':%s:%s:%s'%(self.dest, self.delete,
@ -354,8 +356,9 @@ class DeviceMenu(QMenu):
self.actions = []
self._memory = []
self.set_default_menu = self.addMenu(_('Set default send to device'
' action'))
self.set_default_menu = QMenu(_('Set default send to device action'))
self.set_default_menu.setIcon(QIcon(I('config.svg')))
opts = email_config().parse()
default_account = None
if opts.accounts:
@ -379,51 +382,65 @@ class DeviceMenu(QMenu):
self.connect(action2, SIGNAL('a_s(QAction)'),
self.action_triggered)
_actions = [
basic_actions = [
('main:', False, False, I('reader.svg'),
_('Send to main memory')),
('carda:0', False, False, I('sd.svg'),
_('Send to storage card A')),
('cardb:0', False, False, I('sd.svg'),
_('Send to storage card B')),
'-----',
]
delete_actions = [
('main:', True, False, I('reader.svg'),
_('Send to main memory')),
_('Main Memory')),
('carda:0', True, False, I('sd.svg'),
_('Send to storage card A')),
_('Storage Card A')),
('cardb:0', True, False, I('sd.svg'),
_('Send to storage card B')),
'-----',
_('Storage Card B')),
]
specific_actions = [
('main:', False, True, I('reader.svg'),
_('Send specific format to main memory')),
_('Main Memory')),
('carda:0', False, True, I('sd.svg'),
_('Send specific format to storage card A')),
_('Storage Card A')),
('cardb:0', False, True, I('sd.svg'),
_('Send specific format to storage card B')),
_('Storage Card B')),
]
]
if default_account is not None:
_actions.insert(2, default_account)
_actions.insert(6, list(default_account))
_actions[6][1] = True
for round in (0, 1):
for dest, delete, specific, icon, text in _actions:
if dest == '-':
(self.set_default_menu if round else self).addSeparator()
continue
action = DeviceAction(dest, delete, specific, icon, text, self)
self._memory.append(action)
if round == 1:
action.setCheckable(True)
action.setText(action.text())
self.group.addAction(action)
self.set_default_menu.addAction(action)
else:
self.connect(action, SIGNAL('a_s(QAction)'),
self.action_triggered)
self.actions.append(action)
self.addAction(action)
for x in (basic_actions, delete_actions):
ac = list(default_account)
if x is delete_actions:
ac[1] = True
x.insert(1, tuple(ac))
for menu in (self, self.set_default_menu):
for actions, desc in (
(basic_actions, ''),
(delete_actions, _('Send and delete from library')),
(specific_actions, _('Send specific format'))
):
mdest = menu
if actions is not basic_actions:
mdest = menu.addMenu(desc)
self._memory.append(mdest)
for dest, delete, specific, icon, text in actions:
action = DeviceAction(dest, delete, specific, icon, text, self)
self._memory.append(action)
if menu is self.set_default_menu:
action.setCheckable(True)
action.setText(action.text())
self.group.addAction(action)
else:
action.a_s.connect(self.action_triggered)
self.actions.append(action)
mdest.addAction(action)
if actions is not specific_actions:
menu.addSeparator()
da = config['default_send_to_device_action']
done = False
@ -437,8 +454,7 @@ class DeviceMenu(QMenu):
action.setChecked(True)
config['default_send_to_device_action'] = repr(action)
self.connect(self.group, SIGNAL('triggered(QAction*)'),
self.change_default_action)
self.group.triggered.connect(self.change_default_action)
if opts.accounts:
self.addSeparator()
self.addMenu(self.email_to_menu)
@ -454,6 +470,8 @@ class DeviceMenu(QMenu):
mitem.triggered.connect(lambda x : self.disconnect_from_folder.emit())
self.disconnect_from_folder_action = mitem
self.addSeparator()
self.addMenu(self.set_default_menu)
self.addSeparator()
annot = self.addAction(_('Fetch annotations (experimental)'))
annot.setEnabled(False)

View File

@ -1,6 +1,7 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, re, time, textwrap, copy
import os, re, time, textwrap, copy, sys
from PyQt4.Qt import QDialog, QListWidgetItem, QIcon, \
QDesktopServices, QVBoxLayout, QLabel, QPlainTextEdit, \
@ -10,7 +11,7 @@ from PyQt4.Qt import QDialog, QListWidgetItem, QIcon, \
QDialogButtonBox, QTabWidget, QBrush, QLineEdit, \
QProgressDialog
from calibre.constants import iswindows, isosx, preferred_encoding
from calibre.constants import iswindows, isosx
from calibre.gui2.dialogs.config.config_ui import Ui_Dialog
from calibre.gui2.dialogs.config.create_custom_column import CreateCustomColumn
from calibre.gui2 import choose_dir, error_dialog, config, \
@ -330,7 +331,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
def category_current_changed(self, n, p):
self.stackedWidget.setCurrentIndex(n.row())
def __init__(self, parent, model, server=None):
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()
@ -338,8 +339,9 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.category_view.currentChanged = self.category_current_changed
self.category_view.setModel(self._category_model)
self.parent = parent
self.model = model
self.db = model.db
self.library_view = library_view
self.model = library_view.model()
self.db = self.model.db
self.server = server
path = prefs['library_path']
self.location.setText(path if path else '')
@ -364,26 +366,27 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.new_version_notification.setChecked(config['new_version_notification'])
# Set up columns
# Make copies of maps so that internal changes aren't put into the real maps
self.colmap = config['column_map'][:]
colmap = list(self.model.column_map)
state = self.library_view.get_state()
hidden_cols = state['hidden_columns']
positions = state['column_positions']
colmap.sort(cmp=lambda x,y: cmp(positions[x], positions[y]))
self.custcols = copy.deepcopy(self.db.custom_column_label_map)
cm = [c.decode(preferred_encoding, 'replace') for c in self.colmap]
ac = [c.decode(preferred_encoding, 'replace') for c in ALL_COLUMNS]
for col in cm + \
[i for i in ac if i not in cm] + \
[i for i in self.custcols if i not in cm]:
if col in ALL_COLUMNS:
item = QListWidgetItem(model.orig_headers[col], self.columns)
else:
item = QListWidgetItem(self.custcols[col]['name'], self.columns)
for col in colmap:
item = QListWidgetItem(self.model.headers[col], self.columns)
item.setData(Qt.UserRole, QVariant(col))
item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable)
item.setCheckState(Qt.Checked if col in self.colmap else Qt.Unchecked)
self.connect(self.column_up, SIGNAL('clicked()'), self.up_column)
self.connect(self.column_down, SIGNAL('clicked()'), self.down_column)
self.connect(self.del_custcol_button, SIGNAL('clicked()'), self.del_custcol)
self.connect(self.add_custcol_button, SIGNAL('clicked()'), self.add_custcol)
self.connect(self.edit_custcol_button, SIGNAL('clicked()'), self.edit_custcol)
flags = Qt.ItemIsEnabled|Qt.ItemIsSelectable
if col != 'ondevice':
flags |= Qt.ItemIsUserCheckable
item.setFlags(flags)
if col != 'ondevice':
item.setCheckState(Qt.Unchecked if col in hidden_cols else
Qt.Checked)
self.column_up.clicked.connect(self.up_column)
self.column_down.clicked.connect(self.down_column)
self.del_custcol_button.clicked.connect(self.del_custcol)
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)
@ -647,6 +650,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.input_order.insertItem(idx+1, self.input_order.takeItem(idx))
self.input_order.setCurrentRow(idx+1)
# Column settings {{{
def up_column(self):
idx = self.columns.currentRow()
if idx > 0:
@ -683,6 +687,53 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
def edit_custcol(self):
CreateCustomColumn(self, True, self.model.orig_headers, ALL_COLUMNS)
def apply_custom_column_changes(self):
config_cols = [unicode(self.columns.item(i).data(Qt.UserRole).toString())\
for i in range(self.columns.count())]
if not config_cols:
config_cols = ['title']
removed_cols = set(self.model.column_map) - set(config_cols)
hidden_cols = set([unicode(self.columns.item(i).data(Qt.UserRole).toString())\
for i in range(self.columns.count()) \
if self.columns.item(i).checkState()==Qt.Unchecked])
hidden_cols = hidden_cols.union(removed_cols) # Hide removed cols
hidden_cols = list(hidden_cols.intersection(set(self.model.column_map)))
if 'ondevice' in hidden_cols:
hidden_cols.remove('ondevice')
def col_pos(x, y):
xidx = config_cols.index(x) if x in config_cols else sys.maxint
yidx = config_cols.index(y) if y in config_cols else sys.maxint
return cmp(xidx, yidx)
positions = {}
for i, col in enumerate((sorted(self.model.column_map, cmp=col_pos))):
positions[col] = i
state = {'hidden_columns': hidden_cols, 'column_positions':positions}
self.library_view.apply_state(state)
self.library_view.save_state()
must_restart = False
for c in self.custcols:
if self.custcols[c]['num'] is None:
self.db.create_custom_column(
label=c,
name=self.custcols[c]['name'],
datatype=self.custcols[c]['datatype'],
is_multiple=self.custcols[c]['is_multiple'],
display = self.custcols[c]['display'])
must_restart = True
elif '*deleteme' in self.custcols[c]:
self.db.delete_custom_column(label=c)
must_restart = True
elif '*edited' in self.custcols[c]:
cc = self.custcols[c]
self.db.set_custom_column_metadata(cc['num'], name=cc['name'],
label=cc['label'],
display = self.custcols[c]['display'])
if '*must_restart' in self.custcols[c]:
must_restart = True
return must_restart
# }}}
def view_server_logs(self):
from calibre.library.server import log_access_file, log_error_file
d = QDialog(self)
@ -776,33 +827,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
input_cols = [unicode(self.input_order.item(i).data(Qt.UserRole).toString()) for i in range(self.input_order.count())]
prefs['input_format_order'] = input_cols
####### Now deal with changes to columns
cols = [unicode(self.columns.item(i).data(Qt.UserRole).toString())\
for i in range(self.columns.count()) \
if self.columns.item(i).checkState()==Qt.Checked]
if not cols:
cols = ['title']
config['column_map'] = cols
must_restart = False
for c in self.custcols:
if self.custcols[c]['num'] is None:
self.db.create_custom_column(
label=c,
name=self.custcols[c]['name'],
datatype=self.custcols[c]['datatype'],
is_multiple=self.custcols[c]['is_multiple'],
display = self.custcols[c]['display'])
must_restart = True
elif '*deleteme' in self.custcols[c]:
self.db.delete_custom_column(label=c)
must_restart = True
elif '*edited' in self.custcols[c]:
cc = self.custcols[c]
self.db.set_custom_column_metadata(cc['num'], name=cc['name'],
label=cc['label'],
display = self.custcols[c]['display'])
if '*must_restart' in self.custcols[c]:
must_restart = True
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())

View File

@ -123,7 +123,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
if ':' in col or ' ' in col or col.lower() != col:
return self.simple_error('', _('The lookup name must be lower case and cannot contain ":"s or spaces'))
date_format = None
date_format = {}
if col_type == 'datetime':
if self.date_format_box.text():
date_format = {'date_format':unicode(self.date_format_box.text())}

View File

@ -14,12 +14,12 @@
<string>Active Jobs</string>
</property>
<property name="windowIcon">
<iconset resource="../../../work/calibre/resources/images.qrc">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/jobs.svg</normaloff>:/images/jobs.svg</iconset>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="JobsView" name="jobs_view">
<widget class="QTableView" name="jobs_view">
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</property>
@ -66,15 +66,8 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>JobsView</class>
<extends>QTableView</extends>
<header>widgets.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../../work/calibre/resources/images.qrc"/>
<include location="../../../../resources/images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -15,10 +15,11 @@ from PyQt4.Qt import QAbstractTableModel, QVariant, QModelIndex, Qt, \
from calibre.utils.ipc.server import Server
from calibre.utils.ipc.job import ParallelJob
from calibre.gui2 import Dispatcher, error_dialog, NONE, config
from calibre.gui2 import Dispatcher, error_dialog, NONE, config, gprefs
from calibre.gui2.device import DeviceJob
from calibre.gui2.dialogs.jobs_ui import Ui_JobsDialog
from calibre import __appname__
from calibre.gui2.dialogs.job_view_ui import Ui_Dialog
class JobManager(QAbstractTableModel):
@ -243,7 +244,32 @@ class ProgressBarDelegate(QAbstractItemDelegate):
opts.text = QString(_('Unavailable') if percent == 0 else '%d%%'%percent)
QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter)
class DetailView(QDialog, Ui_Dialog):
def __init__(self, parent, job):
QDialog.__init__(self, parent)
self.setupUi(self)
self.setWindowTitle(job.description)
self.job = job
self.next_pos = 0
self.update()
self.timer = QTimer(self)
self.timer.timeout.connect(self.update)
self.timer.start(1000)
def update(self):
f = self.job.log_file
f.seek(self.next_pos)
more = f.read()
self.next_pos = f.tell()
if more:
self.log.appendPlainText(more.decode('utf-8', 'replace'))
class JobsDialog(QDialog, Ui_JobsDialog):
def __init__(self, window, model):
QDialog.__init__(self, window)
Ui_JobsDialog.__init__(self)
@ -252,8 +278,6 @@ class JobsDialog(QDialog, Ui_JobsDialog):
self.model = model
self.setWindowModality(Qt.NonModal)
self.setWindowTitle(__appname__ + _(' - Jobs'))
self.connect(self.jobs_view.model(), SIGNAL('modelReset()'),
self.jobs_view.resizeColumnsToContents)
self.connect(self.kill_button, SIGNAL('clicked()'),
self.kill_job)
self.connect(self.details_button, SIGNAL('clicked()'),
@ -264,7 +288,21 @@ class JobsDialog(QDialog, Ui_JobsDialog):
self.jobs_view.model().kill_job)
self.pb_delegate = ProgressBarDelegate(self)
self.jobs_view.setItemDelegateForColumn(2, self.pb_delegate)
self.jobs_view.doubleClicked.connect(self.show_job_details)
self.jobs_view.horizontalHeader().setMovable(True)
state = gprefs.get('jobs view column layout', None)
if state is not None:
try:
self.jobs_view.horizontalHeader().restoreState(bytes(state))
except:
pass
def show_job_details(self, index):
row = index.row()
job = self.jobs_view.model().row_to_job(row)
d = DetailView(self, job)
d.exec_()
d.timer.stop()
def kill_job(self):
for index in self.jobs_view.selectedIndexes():
@ -281,5 +319,9 @@ class JobsDialog(QDialog, Ui_JobsDialog):
self.model.kill_all_jobs()
def closeEvent(self, e):
self.jobs_view.write_settings()
try:
state = bytearray(self.jobs_view.horizontalHeader().saveState())
gprefs['jobs view column layout'] = state
except:
pass
e.accept()

View File

@ -1011,6 +1011,12 @@ class DeviceBooksModel(BooksModel): # {{{
elif role == Qt.DecorationRole and cname == 'inlibrary':
if self.db[self.map[row]].in_library:
return QVariant(self.bool_yes_icon)
elif role == Qt.TextAlignmentRole:
cname = self.column_map[index.column()]
ans = Qt.AlignVCenter | ALIGNMENT_MAP[self.alignment_map.get(cname,
'left')]
return QVariant(ans)
return NONE

View File

@ -44,6 +44,7 @@ class BooksView(QTableView): # {{{
self.selectionModel().currentRowChanged.connect(self._model.current_changed)
# {{{ Column Header setup
self.was_restored = False
self.column_header = self.horizontalHeader()
self.column_header.setMovable(True)
self.column_header.sectionMoved.connect(self.save_state)
@ -99,19 +100,28 @@ class BooksView(QTableView): # {{{
column=col))
m = self.column_header_context_menu.addMenu(
_('Sort on %s') % name)
m.addAction(_('Ascending'),
a = m.addAction(_('Ascending'),
partial(self.column_header_context_handler,
action='ascending', column=col))
m.addAction(_('Descending'),
d = m.addAction(_('Descending'),
partial(self.column_header_context_handler,
action='descending', column=col))
if self._model.sorted_on[0] == col:
ac = a if self._model.sorted_on[1] == Qt.AscendingOrder else d
ac.setCheckable(True)
ac.setChecked(True)
m = self.column_header_context_menu.addMenu(
_('Change text alignment for %s') % name)
al = self._model.alignment_map.get(col, 'left')
for x, t in (('left', _('Left')), ('right', _('Right')), ('center',
_('Center'))):
m.addAction(t,
a = m.addAction(t,
partial(self.column_header_context_handler,
action='align_'+x, column=col))
if al == x:
a.setCheckable(True)
a.setChecked(True)
hidden_cols = [self.column_map[i] for i in
@ -189,7 +199,7 @@ class BooksView(QTableView): # {{{
def save_state(self):
# Only save if we have been initialized (set_database called)
if len(self.column_map) > 0:
if len(self.column_map) > 0 and self.was_restored:
state = self.get_state()
name = unicode(self.objectName())
if name:
@ -278,6 +288,7 @@ class BooksView(QTableView): # {{{
old_state['sort_history'] = tweaks['sort_columns_at_startup']
self.apply_state(old_state)
self.was_restored = True
# }}}

View File

@ -33,16 +33,12 @@ class JobsButton(QFrame):
def initialize(self, jobs_dialog):
self.jobs_dialog = jobs_dialog
self.jobs_dialog.jobs_view.restore_column_widths()
def mouseReleaseEvent(self, event):
if self.jobs_dialog.isVisible():
self.jobs_dialog.jobs_view.write_settings()
self.jobs_dialog.hide()
else:
self.jobs_dialog.jobs_view.read_settings()
self.jobs_dialog.show()
self.jobs_dialog.jobs_view.restore_column_widths()
@property
def is_running(self):

View File

@ -2237,7 +2237,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
_('Cannot configure before calibre is restarted.'))
d.exec_()
return
d = ConfigDialog(self, self.library_view.model(),
d = ConfigDialog(self, self.library_view,
server=self.content_server)
d.exec_()
@ -2255,9 +2255,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.save_menu.actions()[3].setText(
_('Save only %s format to disk in a single directory')%
prefs['output_format'].upper())
self.library_view.model().read_config()
self.library_view.model().refresh()
self.library_view.model().research()
self.tags_view.set_new_model() # in case columns changed
self.tags_view.recount()
self.create_device_menu()

View File

@ -7,15 +7,15 @@ import re, os, traceback
from PyQt4.Qt import QListView, QIcon, QFont, QLabel, QListWidget, \
QListWidgetItem, QTextCharFormat, QApplication, \
QSyntaxHighlighter, QCursor, QColor, QWidget, \
QPixmap, QPalette, QTimer, QDialog, QSplitterHandle, \
QPixmap, QPalette, QSplitterHandle, \
QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, \
QRegExp, QSettings, QSize, QModelIndex, QSplitter, \
QAbstractButton, QPainter, QLineEdit, QComboBox, \
QMenu, QStringListModel, QCompleter, QStringList
from calibre.gui2 import human_readable, NONE, TableView, \
from calibre.gui2 import human_readable, NONE, \
error_dialog, pixmap_to_data, dynamic
from calibre.gui2.dialogs.job_view_ui import Ui_Dialog
from calibre.gui2.filename_pattern_ui import Ui_Form
from calibre import fit_image
from calibre.utils.fonts import fontconfig
@ -399,41 +399,6 @@ class EjectButton(QAbstractButton):
painter.drawPixmap(0, 0, image)
class DetailView(QDialog, Ui_Dialog):
def __init__(self, parent, job):
QDialog.__init__(self, parent)
self.setupUi(self)
self.setWindowTitle(job.description)
self.job = job
self.next_pos = 0
self.update()
self.timer = QTimer(self)
self.connect(self.timer, SIGNAL('timeout()'), self.update)
self.timer.start(1000)
def update(self):
f = self.job.log_file
f.seek(self.next_pos)
more = f.read()
self.next_pos = f.tell()
if more:
self.log.appendPlainText(more.decode('utf-8', 'replace'))
class JobsView(TableView):
def __init__(self, parent):
TableView.__init__(self, parent)
self.connect(self, SIGNAL('doubleClicked(QModelIndex)'), self.show_details)
def show_details(self, index):
row = index.row()
job = self.model().row_to_job(row)
d = DetailView(self, job)
d.exec_()
d.timer.stop()
class FontFamilyModel(QAbstractListModel):

View File

@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en'
'''
Manage application-wide preferences.
'''
import os, re, cPickle, textwrap, traceback, plistlib, json
import os, re, cPickle, textwrap, traceback, plistlib, json, base64
from copy import deepcopy
from functools import partial
from optparse import OptionParser as _OptionParser
@ -636,11 +636,31 @@ class JSONConfig(XMLConfig):
EXTENSION = '.json'
def to_json(self, obj):
if isinstance(obj, bytearray):
return {'__class__': 'bytearray',
'__value__': base64.standard_b64encode(bytes(obj))}
raise TypeError(repr(obj) + ' is not JSON serializable')
def from_json(self, obj):
if '__class__' in obj:
if obj['__class__'] == 'bytearray':
return bytearray(base64.standard_b64decode(obj['__value__']))
return obj
def raw_to_object(self, raw):
return json.loads(raw.decode('utf-8'))
return json.loads(raw.decode('utf-8'), object_hook=self.from_json)
def to_raw(self):
return json.dumps(self, indent=2)
return json.dumps(self, indent=2, default=self.to_json)
def __getitem__(self, key):
return dict.__getitem__(self, key)
def __setitem__(self, key, val):
dict.__setitem__(self, key, val)
self.commit()
def _prefs():