KG 0.7.8
@ -4,6 +4,74 @@
|
||||
# for important features/bug fixes.
|
||||
# Also, each release can have new and improved recipes.
|
||||
|
||||
- version: 0.7.8
|
||||
date: 2010-07-09
|
||||
|
||||
new features:
|
||||
- title: "New tool to help prepare EPUBs for publication"
|
||||
type: major
|
||||
description: >
|
||||
"calibre now contains a new command line tool called epub-fix that can automatically fix
|
||||
common problems in EPUB files that cause them to be rejected by poorly designed publishing services.
|
||||
The tool is plugin based for extensible functionality in the future. Currently, it can fix unmanifested files
|
||||
and workaround the date and svg preserveaspectratio bugs of epubcheck."
|
||||
|
||||
- title: "New icons for the toolbar buttons by Kamil Tatara"
|
||||
|
||||
- title: "Display rating (when available) in cover browser"
|
||||
|
||||
- title: "Clicking on the central cover int the cover browser now opens that book in the viewer"
|
||||
|
||||
- title: "Use the status bar instead of the area to the right of the location view to display status information"
|
||||
|
||||
- title: "Driver for the Pandigital Novel e-book reader"
|
||||
|
||||
bug fixes:
|
||||
- title: "News download: Don not specify a font family for article descriptions"
|
||||
|
||||
- title: "News download: Fix regression introduced in 0.7.0 that broke download of some embedded content feeds"
|
||||
|
||||
- title: "MOBI Output: Partial support for nested superscript and subscripts."
|
||||
tickets: [6132]
|
||||
|
||||
- title: "CHM Input: Fix handling of buggy CHM files with no .hhc"
|
||||
tickets: [6087]
|
||||
|
||||
- title: "EPUB Input: Fix bug in unzipping EPUB files that have been zipped in depth first order."
|
||||
tickets: [6127]
|
||||
|
||||
- title: "TXT Input: Convert HTML entities to characters."
|
||||
tickets: [6114]
|
||||
|
||||
- title: "LRF Input: Handle LRF files with random null bytes in the text"
|
||||
tickets: [6097]
|
||||
|
||||
- title: "Kobo driver: Fix detection of txt/html files on the device"
|
||||
|
||||
- title: "Fix opening of books when calibre library is on an unmapped network share in windows"
|
||||
|
||||
- title: "SONY driver: Only update the timestamp in the XML db for newly added books"
|
||||
|
||||
- title: "Cover browser: Fix rendering of center cover when width of cover browser is less than the width of a single cover"
|
||||
|
||||
- title: "Cover browser: Correct fix for setPixel out of bounds warning causing UI slowdown in calibre"
|
||||
|
||||
new recipes:
|
||||
- title: "evz.ro"
|
||||
author: Darko Miletic
|
||||
|
||||
- title: "Anchorage Daily News, China Economic Net, BBC Chinese and Singtao Daily"
|
||||
author: rty
|
||||
|
||||
- title: Big Oven
|
||||
author: Starson17
|
||||
|
||||
improved recipes:
|
||||
- Haaretz
|
||||
- Editor and Publisher
|
||||
- Estadao
|
||||
|
||||
|
||||
- version: 0.7.7
|
||||
date: 2010-07-02
|
||||
|
||||
@ -44,7 +112,7 @@
|
||||
|
||||
- title: "MOBI Output: Fix a memory leak and a crash in the palmdoc compression routine"
|
||||
|
||||
- title: "Metadata download: Fix a regressiont at resulted in a failed download for some books"
|
||||
- title: "Metadata download: Fix a regression that resulted in a failed download for some books"
|
||||
|
||||
new recipes:
|
||||
- title: "Foreign Policy and Alo!"
|
||||
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 5.0 KiB |
@ -1752,7 +1752,7 @@
|
||||
sodipodi:cy="93.331604"
|
||||
sodipodi:cx="-166.53223"
|
||||
id="path6082"
|
||||
style="opacity:1;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.08779998;stroke-opacity:1;filter:url(#filter6074)"
|
||||
style="opacity:1;fill:url(#radialGradient6084);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.08779998;stroke-opacity:1;filter:url(#filter6074)"
|
||||
sodipodi:type="arc" /></clipPath><radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5990"
|
||||
@ -2513,7 +2513,7 @@
|
||||
transform="matrix(-1.7332269,0,0,1.7332269,-228.13814,-101.76485)"
|
||||
clip-path="none" /><path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.08779998;stroke-opacity:1;filter:url(#filter6074)"
|
||||
style="opacity:1;fill:url(#radialGradient6084);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.08779998;stroke-opacity:1;filter:url(#filter6074)"
|
||||
id="path3915"
|
||||
sodipodi:cx="-166.53223"
|
||||
sodipodi:cy="93.331604"
|
||||
@ -2901,22 +2901,8 @@
|
||||
id="g133">
|
||||
<defs
|
||||
id="defs135" />
|
||||
<use
|
||||
|
||||
id="use138"
|
||||
x="0"
|
||||
y="0"
|
||||
width="121"
|
||||
height="120" />
|
||||
<clipPath
|
||||
id="XMLID_215_">
|
||||
<use
|
||||
|
||||
id="use141"
|
||||
x="0"
|
||||
y="0"
|
||||
width="121"
|
||||
height="120" />
|
||||
</clipPath>
|
||||
<g
|
||||
clip-path="url(#XMLID_215_)"
|
||||
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
269
resources/images/dialog_question.svg
Normal file
@ -0,0 +1,269 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://web.resource.org/cc/"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.0"
|
||||
id="Livello_1"
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 139 139"
|
||||
overflow="visible"
|
||||
enable-background="new 0 0 139 139"
|
||||
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
|
||||
id="defs3162"><filter
|
||||
inkscape:collect="always"
|
||||
x="-0.132641"
|
||||
width="1.265282"
|
||||
y="-0.34752154"
|
||||
height="1.6950431"
|
||||
id="filter3547"><feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="2.7512044"
|
||||
id="feGaussianBlur3549" /></filter><filter
|
||||
inkscape:collect="always"
|
||||
id="filter5097"><feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="2.32"
|
||||
id="feGaussianBlur5099" /></filter><filter
|
||||
inkscape:collect="always"
|
||||
x="-0.143268"
|
||||
width="1.286536"
|
||||
y="-0.072184406"
|
||||
height="1.1443688"
|
||||
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" />
|
||||
<filter
|
||||
id="AI_Sfocatura_4">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4"
|
||||
id="feGaussianBlur3096" />
|
||||
</filter>
|
||||
<filter
|
||||
id="AI_Sfocatura_2">
|
||||
<feGaussianBlur
|
||||
stdDeviation="2"
|
||||
id="feGaussianBlur3099" />
|
||||
</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">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#000000"
|
||||
id="stop3102" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
id="stop3104" />
|
||||
</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"
|
||||
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)" />
|
||||
|
||||
<radialGradient
|
||||
id="XMLID_13_"
|
||||
cx="69.600098"
|
||||
cy="69.600098"
|
||||
r="58"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
offset="0.6154"
|
||||
style="stop-color:#EEEEEE"
|
||||
id="stop3113" />
|
||||
<stop
|
||||
offset="0.8225"
|
||||
style="stop-color:#DDDDDD"
|
||||
id="stop3115" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop3117" />
|
||||
</radialGradient>
|
||||
<circle
|
||||
cx="69.599998"
|
||||
cy="69.599998"
|
||||
r="58"
|
||||
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)" />
|
||||
<linearGradient
|
||||
id="XMLID_14_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="27.6001"
|
||||
y1="69.600098"
|
||||
x2="111.6001"
|
||||
y2="69.600098"
|
||||
gradientTransform="matrix(1.0859375,0,0,1.0859375,-3.9093733,-8.2531233)">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#2A94EC"
|
||||
id="stop3122" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#0057AE"
|
||||
id="stop3124" />
|
||||
</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"
|
||||
id="path3126"
|
||||
style="fill:url(#XMLID_14_)" />
|
||||
<g
|
||||
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)">
|
||||
<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"
|
||||
id="path3129"
|
||||
style="fill:#a8dde0" />
|
||||
</g>
|
||||
<linearGradient
|
||||
id="circle16776_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="135.5601"
|
||||
y1="417.66461"
|
||||
x2="161.87621"
|
||||
y2="417.66461"
|
||||
gradientTransform="matrix(0,1.7280523,1.7280523,0,-650.07477,-218.71693)">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop3132" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
id="stop3134" />
|
||||
</linearGradient>
|
||||
<path
|
||||
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)">
|
||||
<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>
|
||||
<clipPath
|
||||
id="XMLID_6_">
|
||||
<use
|
||||
xlink:href="#XMLID_10_"
|
||||
id="use3143"
|
||||
x="0"
|
||||
y="0"
|
||||
width="139"
|
||||
height="139" />
|
||||
</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"
|
||||
id="path3147"
|
||||
style="fill:none;stroke:#00316e;stroke-width:2" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
||||
|
||||
<g
|
||||
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"
|
||||
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="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"
|
||||
style="fill:#ffffff" /></g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.4 KiB |
4298
resources/images/edit_copy.svg
Normal file
After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 3.1 KiB |
269
resources/images/help.svg
Normal file
@ -0,0 +1,269 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://web.resource.org/cc/"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.0"
|
||||
id="Livello_1"
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 139 139"
|
||||
overflow="visible"
|
||||
enable-background="new 0 0 139 139"
|
||||
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
|
||||
id="defs3162"><filter
|
||||
inkscape:collect="always"
|
||||
x="-0.132641"
|
||||
width="1.265282"
|
||||
y="-0.34752154"
|
||||
height="1.6950431"
|
||||
id="filter3547"><feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="2.7512044"
|
||||
id="feGaussianBlur3549" /></filter><filter
|
||||
inkscape:collect="always"
|
||||
id="filter5097"><feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="2.32"
|
||||
id="feGaussianBlur5099" /></filter><filter
|
||||
inkscape:collect="always"
|
||||
x="-0.143268"
|
||||
width="1.286536"
|
||||
y="-0.072184406"
|
||||
height="1.1443688"
|
||||
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" />
|
||||
<filter
|
||||
id="AI_Sfocatura_4">
|
||||
<feGaussianBlur
|
||||
stdDeviation="4"
|
||||
id="feGaussianBlur3096" />
|
||||
</filter>
|
||||
<filter
|
||||
id="AI_Sfocatura_2">
|
||||
<feGaussianBlur
|
||||
stdDeviation="2"
|
||||
id="feGaussianBlur3099" />
|
||||
</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">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#000000"
|
||||
id="stop3102" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
id="stop3104" />
|
||||
</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"
|
||||
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)" />
|
||||
|
||||
<radialGradient
|
||||
id="XMLID_13_"
|
||||
cx="69.600098"
|
||||
cy="69.600098"
|
||||
r="58"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
offset="0.6154"
|
||||
style="stop-color:#EEEEEE"
|
||||
id="stop3113" />
|
||||
<stop
|
||||
offset="0.8225"
|
||||
style="stop-color:#DDDDDD"
|
||||
id="stop3115" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop3117" />
|
||||
</radialGradient>
|
||||
<circle
|
||||
cx="69.599998"
|
||||
cy="69.599998"
|
||||
r="58"
|
||||
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)" />
|
||||
<linearGradient
|
||||
id="XMLID_14_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="27.6001"
|
||||
y1="69.600098"
|
||||
x2="111.6001"
|
||||
y2="69.600098"
|
||||
gradientTransform="matrix(1.0859375,0,0,1.0859375,-3.9093733,-8.2531233)">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#2A94EC"
|
||||
id="stop3122" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#0057AE"
|
||||
id="stop3124" />
|
||||
</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"
|
||||
id="path3126"
|
||||
style="fill:url(#XMLID_14_)" />
|
||||
<g
|
||||
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)">
|
||||
<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"
|
||||
id="path3129"
|
||||
style="fill:#a8dde0" />
|
||||
</g>
|
||||
<linearGradient
|
||||
id="circle16776_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="135.5601"
|
||||
y1="417.66461"
|
||||
x2="161.87621"
|
||||
y2="417.66461"
|
||||
gradientTransform="matrix(0,1.7280523,1.7280523,0,-650.07477,-218.71693)">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop3132" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
id="stop3134" />
|
||||
</linearGradient>
|
||||
<path
|
||||
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)">
|
||||
<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>
|
||||
<clipPath
|
||||
id="XMLID_6_">
|
||||
<use
|
||||
xlink:href="#XMLID_10_"
|
||||
id="use3143"
|
||||
x="0"
|
||||
y="0"
|
||||
width="139"
|
||||
height="139" />
|
||||
</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"
|
||||
id="path3147"
|
||||
style="fill:none;stroke:#00316e;stroke-width:2" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
||||
|
||||
<g
|
||||
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"
|
||||
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="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"
|
||||
style="fill:#ffffff" /></g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 4.1 KiB |
BIN
resources/images/news/evz.ro.png
Normal file
After Width: | Height: | Size: 836 B |
BIN
resources/images/news/haaretz.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 2.2 KiB |
40
resources/recipes/anchorage_daily.recipe
Normal file
@ -0,0 +1,40 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1278347258(BasicNewsRecipe):
|
||||
title = u'Anchorage Daily News'
|
||||
__author__ = 'rty'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
|
||||
feeds = [(u'Alaska News', u'http://www.adn.com/news/alaska/index.xml'),
|
||||
(u'Business', u'http://www.adn.com/money/index.xml'),
|
||||
(u'Sports', u'http://www.adn.com/sports/index.xml'),
|
||||
(u'Politics', u'http://www.adn.com/politics/index.xml'),
|
||||
(u'Lifestyles', u'http://www.adn.com/life/index.xml'),
|
||||
(u'Iditarod', u'http://www.adn.com/iditarod/index.xml')
|
||||
]
|
||||
description = ''''Alaska's Newspaper'''
|
||||
publisher = 'http://www.adn.com'
|
||||
category = 'news, Alaska, Anchorage'
|
||||
language = 'en'
|
||||
extra_css = '''
|
||||
p{font-weight: normal;text-align: justify}
|
||||
'''
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
no_stylesheets = True
|
||||
language = 'en'
|
||||
encoding = 'latin-1'
|
||||
conversion_options = {'linearize_tables':True}
|
||||
masthead_url = 'http://media.adn.com/includes/assets/images/adn_logo.2.gif'
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':'left_col story_mainbar'}),
|
||||
]
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':'story_tools'}),
|
||||
dict(name='p', attrs={'class':'ad_label'}),
|
||||
]
|
||||
remove_tags_after = [
|
||||
dict(name='div', attrs={'class':'advertisement'}),
|
||||
]
|
39
resources/recipes/bbc_chinese.recipe
Normal file
@ -0,0 +1,39 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1277443634(BasicNewsRecipe):
|
||||
title = u'BBC Chinese'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
|
||||
feeds = [
|
||||
(u'\u4e3b\u9875', u'http://www.bbc.co.uk/zhongwen/simp/index.xml'),
|
||||
(u'\u56fd\u9645\u65b0\u95fb', u'http://www.bbc.co.uk/zhongwen/simp/world/index.xml'),
|
||||
(u'\u4e24\u5cb8\u4e09\u5730', u'http://www.bbc.co.uk/zhongwen/simp/china/index.xml'),
|
||||
(u'\u91d1\u878d\u8d22\u7ecf', u'http://www.bbc.co.uk/zhongwen/simp/business/index.xml'),
|
||||
(u'\u7f51\u4e0a\u4e92\u52a8', u'http://www.bbc.co.uk/zhongwen/simp/interactive/index.xml'),
|
||||
(u'\u97f3\u89c6\u56fe\u7247', u'http://www.bbc.co.uk/zhongwen/simp/multimedia/index.xml'),
|
||||
(u'\u5206\u6790\u8bc4\u8bba', u'http://www.bbc.co.uk/zhongwen/simp/indepth/index.xml')
|
||||
]
|
||||
extra_css = '''
|
||||
@font-face {font-family: "DroidFont", serif, sans-serif; src: url(res:///system/fonts/DroidSansFallback.ttf); }\n
|
||||
body {margin-right: 8pt; font-family: 'DroidFont', serif;}\n
|
||||
h1 {font-family: 'DroidFont', serif;}\n
|
||||
.articledescription {font-family: 'DroidFont', serif;}
|
||||
'''
|
||||
__author__ = 'rty'
|
||||
__version__ = '1.0'
|
||||
language = 'zh'
|
||||
pubisher = 'British Broadcasting Corporation'
|
||||
description = 'BBC news in Chinese'
|
||||
category = 'News, Chinese'
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
no_stylesheets = True
|
||||
encoding = 'UTF-8'
|
||||
conversion_options = {'linearize_tables':True}
|
||||
masthead_url = 'http://wscdn.bbc.co.uk/zhongwen/simp/images/1024/brand.jpg'
|
||||
keep_only_tags = [
|
||||
dict(name='h1'),
|
||||
dict(name='p', attrs={'class':['primary-topic','summary']}),
|
||||
dict(name='div', attrs={'class':['bodytext','datestamp']}),
|
||||
]
|
64
resources/recipes/big_oven.recipe
Normal file
@ -0,0 +1,64 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class BigOven(BasicNewsRecipe):
|
||||
title = 'BigOven'
|
||||
__author__ = 'Starson17'
|
||||
description = 'Recipes for the Foodie in us all. Registration is free. A fake username and password just gives smaller photos.'
|
||||
language = 'en'
|
||||
category = 'news, food, recipes, gourmet'
|
||||
publisher = 'Starson17'
|
||||
use_embedded_content= False
|
||||
no_stylesheets = True
|
||||
oldest_article = 24
|
||||
remove_javascript = True
|
||||
remove_empty_feeds = True
|
||||
cover_url = 'http://www.software.com/images/products/BigOven%20Logo_177_216.JPG'
|
||||
max_articles_per_feed = 30
|
||||
needs_subscription = True
|
||||
|
||||
conversion_options = {'linearize_tables' : True
|
||||
, 'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
if self.username is not None and self.password is not None:
|
||||
br.open('http://www.bigoven.com/')
|
||||
br.select_form(name='form1')
|
||||
br['TopMenu_bo1$email'] = self.username
|
||||
br['TopMenu_bo1$password'] = self.password
|
||||
br.submit()
|
||||
return br
|
||||
|
||||
remove_attributes = ['style', 'font']
|
||||
|
||||
keep_only_tags = [dict(name='h1')
|
||||
,dict(name='div', attrs={'class':'img'})
|
||||
,dict(name='div', attrs={'id':'intro'})
|
||||
]
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'style':["overflow: visible;"]})
|
||||
,dict(name='div', attrs={'class':['ctas']})
|
||||
#,dict(name='a', attrs={'class':['edit']})
|
||||
,dict(name='p', attrs={'class':['byline']})
|
||||
]
|
||||
|
||||
feeds = [(u'4 & 5 Star Rated Recipes', u'http://feeds.feedburner.com/Bigovencom-RecipeRaves?format=xml')]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for tag in soup.findAll(name='a', attrs={'class':['edit']}):
|
||||
tag.parent.extract()
|
||||
for tag in soup.findAll(name='a', attrs={'class':['deflink']}):
|
||||
tag.replaceWith(tag.string)
|
||||
return soup
|
||||
|
||||
extra_css = '''
|
||||
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:medium;}
|
||||
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||
'''
|
||||
|
39
resources/recipes/china_economic_net.recipe
Normal file
@ -0,0 +1,39 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1278162597(BasicNewsRecipe):
|
||||
__author__ = 'rty'
|
||||
title = u'China Economic Net'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
|
||||
pubisher = 'www.ce.cn - China Economic net - Beijing'
|
||||
description = 'China Economic Net Magazine'
|
||||
category = 'Economic News Magazine, Chinese, China'
|
||||
feeds = [
|
||||
(u'Stock Market 股市', u'http://finance.ce.cn/stock/index_6304.xml'),
|
||||
(u'Money 理财', u'http://finance.ce.cn/money/index_6301.xml'),
|
||||
(u'Health 健康', u'http://www.ce.cn/health/index_6294.xml'),
|
||||
(u'Technology 科技', u'http://sci.ce.cn/mainpage/index_6307.xml'),
|
||||
(u'Domestic Politics 国内时政', u'http://www.ce.cn/xwzx/gnsz/index_6273.xml')
|
||||
]
|
||||
masthead_url = 'http://finance.ce.cn/images/08mdy_logo.gif'
|
||||
extra_css = '''
|
||||
@font-face {font-family: "DroidFont", serif, sans-serif; src: url(res:///system/fonts/DroidSansFallback.ttf); }\n
|
||||
body {margin-right: 8pt; font-family: 'DroidFont', serif;}\n
|
||||
h1 {font-family: 'DroidFont', serif;}\n
|
||||
.articledescription {font-family: 'DroidFont', serif;}
|
||||
'''
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
no_stylesheets = True
|
||||
language = 'zh-cn'
|
||||
encoding = 'gb2312'
|
||||
conversion_options = {'linearize_tables':True}
|
||||
|
||||
|
||||
keep_only_tags = [
|
||||
|
||||
dict(name='h1', attrs={'id':'articleTitle'}),
|
||||
dict(name='div', attrs={'class':'laiyuan'}),
|
||||
dict(name='div', attrs={'id':'articleText'}),
|
||||
]
|
@ -1,14 +1,29 @@
|
||||
import re
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010 elsuave'
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
class EandP(BasicNewsRecipe):
|
||||
title = u'Editor and Publisher'
|
||||
__author__ = u'Xanthan Gum'
|
||||
__author__ = u'elsuave (modified from Xanthan Gum)'
|
||||
description = 'News about newspapers and journalism.'
|
||||
publisher = 'Editor and Publisher'
|
||||
category = 'news, journalism, industry'
|
||||
language = 'en'
|
||||
max_articles_per_feed = 25
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'utf8'
|
||||
cover_url = 'http://www.editorandpublisher.com/images/EP_main_logo.gif'
|
||||
remove_javascript = True
|
||||
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
html2lrf_options = [
|
||||
'--comment', description
|
||||
, '--category', category
|
||||
, '--publisher', publisher
|
||||
]
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
|
||||
# Font formatting code borrowed from kwetal
|
||||
|
||||
@ -18,17 +33,21 @@ class EandP(BasicNewsRecipe):
|
||||
h2{font-size: large;}
|
||||
'''
|
||||
|
||||
# Delete everything before the article
|
||||
# Keep only div:itemmgap
|
||||
|
||||
remove_tags_before = dict(name='font', attrs={'class':'titlebar_black'})
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':'itemmgap'})
|
||||
]
|
||||
|
||||
# Delete everything after the article
|
||||
# Remove commenting/social media lins
|
||||
|
||||
preprocess_regexps = [(re.compile(r'<!--endclickprintinclude-->.*</body>', re.DOTALL|re.IGNORECASE),
|
||||
lambda match: '</body>'),]
|
||||
remove_tags_after = [dict(name='div', attrs={'class':'clear'})]
|
||||
|
||||
|
||||
feeds = [(u'Breaking News', u'http://www.editorandpublisher.com/GenerateRssFeed.aspx'),
|
||||
(u'Business News', u'http://www.editorandpublisher.com/GenerateRssFeed.aspx?CategoryId=2'),
|
||||
(u'Ad/Circ News', u'http://www.editorandpublisher.com/GenerateRssFeed.aspx?CategoryId=3'),
|
||||
(u'Newsroom', u'http://www.editorandpublisher.com/GenerateRssFeed.aspx?CategoryId=4'),
|
||||
(u'Technology News', u'http://www.editorandpublisher.com/GenerateRssFeed.aspx?CategoryId=5'),
|
||||
(u'Syndicates News', u'http://www.editorandpublisher.com/GenerateRssFeed.aspx?CategoryId=7')]
|
||||
|
||||
feeds = [(u'Breaking News', u'http://feeds.feedburner.com/EditorAndPublisher-BreakingNews'),
|
||||
(u'Business News', u'http://feeds.feedburner.com/EditorAndPublisher-BusinessNews'),
|
||||
(u'Newsroom', u'http://feeds.feedburner.com/EditorAndPublisher-Newsroom'),
|
||||
(u'Technology News', u'http://feeds.feedburner.com/EditorAndPublisher-Technology'),
|
||||
(u'Syndicates News', u'http://feeds.feedburner.com/EditorAndPublisher-Syndicates')]
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__copyright__ = '2010, elsuave'
|
||||
'''
|
||||
estadao.com.br
|
||||
'''
|
||||
@ -10,12 +10,12 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Estadao(BasicNewsRecipe):
|
||||
title = 'O Estado de S. Paulo'
|
||||
__author__ = 'Darko Miletic'
|
||||
__author__ = 'elsuave (modified from Darko Miletic)'
|
||||
description = 'News from Brasil in Portuguese'
|
||||
publisher = 'O Estado de S. Paulo'
|
||||
category = 'news, politics, Brasil'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
max_articles_per_feed = 25
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'utf8'
|
||||
@ -30,13 +30,14 @@ class Estadao(BasicNewsRecipe):
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'c1'})]
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':['bb-md-noticia','c5']})
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name=['script','object','form','ul'])
|
||||
,dict(name='div', attrs={'id':['votacao','estadaohoje']})
|
||||
,dict(name='p', attrs={'id':'ctrl_texto'})
|
||||
,dict(name='p', attrs={'class':'texto'})
|
||||
,dict(name='div', attrs={'class':['fnt2 Color_04 bold','right fnt2 innerTop15 dvTmFont','™_01 right outerLeft15','tituloBox','tags']})
|
||||
,dict(name='div', attrs={'id':['bb-md-noticia-subcom']})
|
||||
]
|
||||
|
||||
feeds = [
|
||||
@ -51,13 +52,12 @@ class Estadao(BasicNewsRecipe):
|
||||
,(u'Vida &', u'http://www.estadao.com.br/rss/vidae.xml')
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
ifr = soup.find('iframe')
|
||||
if ifr:
|
||||
ifr.extract()
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
||||
|
||||
|
||||
language = 'pt'
|
||||
|
||||
def get_article_url(self, article):
|
||||
url = BasicNewsRecipe.get_article_url(self, article)
|
||||
if '/Multimidia/' not in url:
|
||||
return url
|
||||
|
||||
|
52
resources/recipes/evz.ro.recipe
Normal file
@ -0,0 +1,52 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
evz.ro
|
||||
'''
|
||||
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class EVZ_Ro(BasicNewsRecipe):
|
||||
title = 'evz.ro'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'News from Romania'
|
||||
publisher = 'evz.ro'
|
||||
category = 'news, politics, Romania'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 200
|
||||
no_stylesheets = True
|
||||
encoding = 'utf8'
|
||||
use_embedded_content = False
|
||||
language = 'ro'
|
||||
masthead_url = 'http://www.evz.ro/fileadmin/images/logo.gif'
|
||||
extra_css = ' body{font-family: Georgia,Arial,Helvetica,sans-serif } .firstP{font-size: 1.125em} .author,.articleInfo{font-size: small} '
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
preprocess_regexps = [
|
||||
(re.compile(r'<head>.*?<title>', re.DOTALL|re.IGNORECASE),lambda match: '<head><title>')
|
||||
,(re.compile(r'</title>.*?</head>', re.DOTALL|re.IGNORECASE),lambda match: '</title></head>')
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name=['form','embed','iframe','object','base','link','script','noscript'])
|
||||
,dict(attrs={'class':['section','statsInfo','email il']})
|
||||
,dict(attrs={'id' :'gallery'})
|
||||
]
|
||||
|
||||
remove_tags_after = dict(attrs={'class':'section'})
|
||||
keep_only_tags = [dict(attrs={'class':'single'})]
|
||||
remove_attributes = ['height','width']
|
||||
|
||||
feeds = [(u'Articles', u'http://www.evz.ro/rss.xml')]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
@ -1,56 +1,95 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
haaretz.com
|
||||
www.haaretz.com
|
||||
'''
|
||||
|
||||
import re
|
||||
from calibre import strftime
|
||||
from time import gmtime
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Haaretz_en(BasicNewsRecipe):
|
||||
title = 'Haaretz in English'
|
||||
class HaaretzPrint_en(BasicNewsRecipe):
|
||||
title = 'Haaretz - print edition'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Haaretz.com, the online edition of Haaretz Newspaper in Israel, and analysis from Israel and the Middle East. Haaretz.com provides extensive and in-depth coverage of Israel, the Jewish World and the Middle East, including defense, diplomacy, the Arab-Israeli conflict, the peace process, Israeli politics, Jerusalem affairs, international relations, Iran, Iraq, Syria, Lebanon, the Palestinian Authority, the West Bank and the Gaza Strip, the Israeli business world and Jewish life in Israel and the Diaspora. '
|
||||
publisher = 'haaretz.com'
|
||||
category = 'news, politics, Israel'
|
||||
description = "Haaretz.com is the world's leading English-language Website for real-time news and analysis of Israel and the Middle East."
|
||||
publisher = 'Haaretz'
|
||||
category = "news, Haaretz, Israel news, Israel newspapers, Israel business news, Israel financial news, Israeli news,Israeli newspaper, Israeli newspapers, news from Israel, news in Israel, news Israel, news on Israel, newspaper Israel, Israel sports news, Israel diplomacy news"
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 200
|
||||
no_stylesheets = True
|
||||
encoding = 'cp1252'
|
||||
encoding = 'utf8'
|
||||
use_embedded_content = False
|
||||
language = 'en_IL'
|
||||
publication_type = 'newspaper'
|
||||
remove_empty_feeds = True
|
||||
masthead_url = 'http://www.haaretz.com/images/logos/logoGrey.gif'
|
||||
PREFIX = 'http://www.haaretz.com'
|
||||
masthead_url = PREFIX + '/images/logos/logoGrey.gif'
|
||||
extra_css = ' body{font-family: Verdana,Arial,Helvetica,sans-serif } '
|
||||
|
||||
preprocess_regexps = [(re.compile(r'</body>.*?</html>', re.DOTALL|re.IGNORECASE),lambda match: '</body></html>')]
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'publisher': publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'class':['rightcol']}),dict(name='table')]
|
||||
remove_tags_before = dict(name='h1')
|
||||
remove_tags_after = dict(attrs={'id':'innerArticle'})
|
||||
keep_only_tags = [dict(attrs={'id':'content'})]
|
||||
keep_only_tags = [dict(attrs={'id':'threecolumns'})]
|
||||
remove_attributes = ['width','height']
|
||||
remove_tags = [
|
||||
dict(name=['iframe','link','object','embed'])
|
||||
,dict(name='div',attrs={'class':'rightcol'})
|
||||
]
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Opinion' , u'http://www.haaretz.com/cmlink/opinion-rss-1.209234?localLinksEnabled=false' )
|
||||
,(u'Defense and diplomacy' , u'http://www.haaretz.com/cmlink/defense-and-diplomacy-rss-1.208894?localLinksEnabled=false')
|
||||
,(u'National' , u'http://www.haaretz.com/cmlink/national-rss-1.208896?localLinksEnabled=false' )
|
||||
,(u'International' , u'http://www.haaretz.com/cmlink/international-rss-1.208898?localLinksEnabled=false' )
|
||||
,(u'Jewish World' , u'http://www.haaretz.com/cmlink/jewish-world-rss-1.209085?localLinksEnabled=false' )
|
||||
,(u'Business' , u'http://www.haaretz.com/cmlink/business-print-rss-1.264904?localLinksEnabled=false' )
|
||||
,(u'Real Estate' , u'http://www.haaretz.com/cmlink/real-estate-print-rss-1.264977?localLinksEnabled=false' )
|
||||
,(u'Features' , u'http://www.haaretz.com/cmlink/features-print-rss-1.264912?localLinksEnabled=false' )
|
||||
,(u'Arts and leisure' , u'http://www.haaretz.com/cmlink/arts-and-leisure-rss-1.286090?localLinksEnabled=false' )
|
||||
,(u'Books' , u'http://www.haaretz.com/cmlink/books-rss-1.264947?localLinksEnabled=false' )
|
||||
,(u'Food and Wine' , u'http://www.haaretz.com/cmlink/food-and-wine-print-rss-1.265034?localLinksEnabled=false' )
|
||||
,(u'Sports' , u'http://www.haaretz.com/cmlink/sports-rss-1.286092?localLinksEnabled=false' )
|
||||
(u'News' , PREFIX + u'/print-edition/news' )
|
||||
,(u'Opinion' , PREFIX + u'/print-edition/opinion' )
|
||||
,(u'Business' , PREFIX + u'/print-edition/business' )
|
||||
,(u'Real estate' , PREFIX + u'/print-edition/real-estate' )
|
||||
,(u'Sports' , PREFIX + u'/print-edition/sports' )
|
||||
,(u'Travel' , PREFIX + u'/print-edition/travel' )
|
||||
,(u'Books' , PREFIX + u'/print-edition/books' )
|
||||
,(u'Food & Wine' , PREFIX + u'/print-edition/food-wine' )
|
||||
,(u'Arts & Leisure', PREFIX + u'/print-edition/arts-leisure' )
|
||||
,(u'Features' , PREFIX + u'/print-edition/features' )
|
||||
]
|
||||
|
||||
|
||||
def print_version(self, url):
|
||||
article = url.rpartition('/')[2]
|
||||
return 'http://www.haaretz.com/misc/article-print-page/' + article
|
||||
|
||||
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(attrs={'class':'text'}):
|
||||
sp = item.find('span',attrs={'class':'h3 font-weight-normal'})
|
||||
desc = item.find('p')
|
||||
description = ''
|
||||
if sp:
|
||||
if desc:
|
||||
description = self.tag_to_string(desc)
|
||||
link = sp.a
|
||||
url = self.PREFIX + link['href']
|
||||
title = self.tag_to_string(link)
|
||||
times = strftime('%a, %d %b %Y %H:%M:%S +0000',gmtime())
|
||||
articles.append({
|
||||
'title' :title
|
||||
,'date' :times
|
||||
,'url' :url
|
||||
,'description':description
|
||||
})
|
||||
totalfeeds.append((feedtitle, articles))
|
||||
return totalfeeds
|
||||
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
|
@ -336,7 +336,7 @@ class NYTimes(BasicNewsRecipe):
|
||||
self.log(">>> No class:'columnGroup first' found <<<")
|
||||
# Change class="kicker" to <h3>
|
||||
kicker = soup.find(True, {'class':'kicker'})
|
||||
if kicker and kicker.contents[0]:
|
||||
if kicker and kicker.contents and kicker.contents[0]:
|
||||
h3Tag = Tag(soup, "h3")
|
||||
h3Tag.insert(0, self.fixChars(self.tag_to_string(kicker,
|
||||
use_alt=False)))
|
||||
@ -460,7 +460,9 @@ class NYTimes(BasicNewsRecipe):
|
||||
return self.massageNCXText(self.tag_to_string(p,use_alt=False))
|
||||
return None
|
||||
|
||||
if not article.author:
|
||||
article.author = extract_author(soup)
|
||||
if not article.summary:
|
||||
article.summary = article.text_summary = extract_description(soup)
|
||||
|
||||
def strip_anchors(self,soup):
|
||||
|
79
resources/recipes/singtao_daily.recipe
Normal file
@ -0,0 +1,79 @@
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1278063072(BasicNewsRecipe):
|
||||
title = u'Singtao Daily - Canada'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
__author__ = 'rty'
|
||||
description = 'Toronto Canada Chinese Newspaper'
|
||||
publisher = 'news.singtao.ca'
|
||||
category = 'Chinese, News, Canada'
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
no_stylesheets = True
|
||||
language = 'zh'
|
||||
conversion_options = {'linearize_tables':True}
|
||||
masthead_url = 'http://news.singtao.ca/i/site_2009/logo.jpg'
|
||||
extra_css = '''
|
||||
@font-face {font-family: "DroidFont", serif, sans-serif; src: url(res:///system/fonts/DroidSansFallback.ttf); }\
|
||||
|
||||
body {text-align: justify; margin-right: 8pt; font-family: 'DroidFont', serif;}\
|
||||
|
||||
h1 {font-family: 'DroidFont', serif;}\
|
||||
|
||||
.articledescription {font-family: 'DroidFont', serif;}
|
||||
'''
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'id':['title','storybody']}),
|
||||
dict(name='div', attrs={'class':'content'})
|
||||
]
|
||||
|
||||
def parse_index(self):
|
||||
feeds = []
|
||||
for title, url in [
|
||||
('Editorial',
|
||||
'http://news.singtao.ca/toronto/editorial.html'),
|
||||
('Toronto \xe5\x9f\x8e\xe5\xb8\x82/\xe7\xa4\xbe\xe5\x8d\x80'.decode('utf-8'),
|
||||
'http://news.singtao.ca/toronto/city.html'),
|
||||
('Canada \xe5\x8a\xa0\xe5\x9c\x8b'.decode('utf-8'),
|
||||
'http://news.singtao.ca/toronto/canada.html'),
|
||||
('Entertainment',
|
||||
'http://news.singtao.ca/toronto/entertainment.html'),
|
||||
('World',
|
||||
'http://news.singtao.ca/toronto/world.html'),
|
||||
('Finance \xe5\x9c\x8b\xe9\x9a\x9b\xe8\xb2\xa1\xe7\xb6\x93'.decode('utf-8'),
|
||||
'http://news.singtao.ca/toronto/finance.html'),
|
||||
('Sports', 'http://news.singtao.ca/toronto/sports.html'),
|
||||
]:
|
||||
articles = self.parse_section(url)
|
||||
if articles:
|
||||
feeds.append((title, articles))
|
||||
return feeds
|
||||
|
||||
def parse_section(self, url):
|
||||
soup = self.index_to_soup(url)
|
||||
div = soup.find(attrs={'class': ['newslist paddingL10T10','newslist3 paddingL10T10']})
|
||||
#date = div.find(attrs={'class': 'underlineBLK'})
|
||||
current_articles = []
|
||||
for li in div.findAll('li'):
|
||||
a = li.find('a', href = True)
|
||||
if a is None:
|
||||
continue
|
||||
title = self.tag_to_string(a)
|
||||
url = a.get('href', False)
|
||||
if not url or not title:
|
||||
continue
|
||||
if url.startswith('/'):
|
||||
url = 'http://news.singtao.ca'+url
|
||||
# self.log('\ \ Found article:', title)
|
||||
# self.log('\ \ \ ', url)
|
||||
current_articles.append({'title': title, 'url': url, 'description':''})
|
||||
|
||||
return current_articles
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
for item in soup.findAll(width=True):
|
||||
del item['width']
|
||||
return soup
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.7.7'
|
||||
__version__ = '0.7.8'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
import re
|
||||
|
@ -9,6 +9,7 @@ from calibre.customize import FileTypePlugin, MetadataReaderPlugin, MetadataWrit
|
||||
from calibre.constants import numeric_version
|
||||
from calibre.ebooks.metadata.archive import ArchiveExtract, get_cbz_metadata
|
||||
|
||||
# To archive plugins {{{
|
||||
class HTML2ZIP(FileTypePlugin):
|
||||
name = 'HTML to ZIP'
|
||||
author = 'Kovid Goyal'
|
||||
@ -82,6 +83,8 @@ class PML2PMLZ(FileTypePlugin):
|
||||
|
||||
return of.name
|
||||
|
||||
# }}}
|
||||
|
||||
# Metadata reader plugins {{{
|
||||
class ComicMetadataReader(MetadataReaderPlugin):
|
||||
|
||||
@ -457,7 +460,7 @@ from calibre.devices.hanvon.driver import N516, EB511, ALEX, AZBOOKA, THEBOOK
|
||||
from calibre.devices.edge.driver import EDGE
|
||||
from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS
|
||||
from calibre.devices.sne.driver import SNE
|
||||
from calibre.devices.misc import PALMPRE, AVANT, SWEEX
|
||||
from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL
|
||||
from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG
|
||||
from calibre.devices.kobo.driver import KOBO
|
||||
|
||||
@ -465,8 +468,11 @@ 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.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]
|
||||
LibraryThing, DoubanBooks, CSV_XML, EPUB_MOBI, Unmanifested, Epubcheck]
|
||||
plugins += [
|
||||
ComicInput,
|
||||
EPUBInput,
|
||||
@ -556,6 +562,7 @@ plugins += [
|
||||
AVANT,
|
||||
MENTOR,
|
||||
SWEEX,
|
||||
PDNOVEL,
|
||||
ITUNES,
|
||||
]
|
||||
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
||||
|
@ -16,6 +16,7 @@ from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.ebooks.metadata.fetch import MetadataSource
|
||||
from calibre.utils.config import make_config_dir, Config, ConfigProxy, \
|
||||
plugin_dir, OptionParser, prefs
|
||||
from calibre.ebooks.epub.fix import ePubFixer
|
||||
|
||||
|
||||
platform = 'linux'
|
||||
@ -194,7 +195,6 @@ def plugin_customization(plugin):
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
# Input/Output profiles {{{
|
||||
def input_profiles():
|
||||
for plugin in _initialized_plugins:
|
||||
@ -444,6 +444,14 @@ def device_plugins(): # {{{
|
||||
yield plugin
|
||||
# }}}
|
||||
|
||||
# epub fixers {{{
|
||||
def epub_fixers():
|
||||
for plugin in _initialized_plugins:
|
||||
if isinstance(plugin, ePubFixer):
|
||||
if not is_disabled(plugin):
|
||||
if platform in plugin.supported_platforms:
|
||||
yield plugin
|
||||
# }}}
|
||||
|
||||
# Initialize plugins {{{
|
||||
|
||||
|
@ -106,9 +106,11 @@ class BOOX(HANLINV3):
|
||||
description = _('Communicate with the BOOX eBook reader.')
|
||||
author = 'Jesus Manuel Marinho Valcarce'
|
||||
supported_platforms = ['windows', 'osx', 'linux']
|
||||
METADATA_CACHE = '.metadata.calibre'
|
||||
|
||||
# Ordered list of supported formats
|
||||
FORMATS = ['epub', 'fb2', 'djvu', 'pdf', 'html', 'txt', 'rtf', 'mobi', 'prc', 'chm']
|
||||
FORMATS = ['epub', 'fb2', 'djvu', 'pdf', 'html', 'txt', 'rtf', 'mobi',
|
||||
'prc', 'chm', 'doc']
|
||||
|
||||
VENDOR_ID = [0x0525]
|
||||
PRODUCT_ID = [0xa4a5]
|
||||
|
@ -213,7 +213,7 @@ class KINDLE_DX(KINDLE2):
|
||||
PRODUCT_ID = [0x0003]
|
||||
BCD = [0x0100]
|
||||
|
||||
class Bookmark():
|
||||
class Bookmark(): # {{{
|
||||
'''
|
||||
A simple class fetching bookmark data
|
||||
Kindle-specific
|
||||
@ -517,3 +517,6 @@ class Bookmark():
|
||||
|
||||
else:
|
||||
print "unsupported bookmark_extension: %s" % self.bookmark_extension
|
||||
|
||||
# }}}
|
||||
|
||||
|
@ -54,6 +54,7 @@ class Book(MetaInformation):
|
||||
except:
|
||||
self.datetime = time.gmtime()
|
||||
|
||||
if thumbnail_name is not None:
|
||||
self.thumbnail = ImageWrapper(thumbnail_name)
|
||||
self.tags = []
|
||||
if other:
|
||||
|
@ -85,8 +85,10 @@ class KOBO(USBMS):
|
||||
|
||||
idx = bl_cache.get(lpath, None)
|
||||
if idx is not None:
|
||||
imagename = self.normalize_path(prefix + '.kobo/images/' + ImageID + ' - NickelBookCover.parsed')
|
||||
if ImageID is not None:
|
||||
imagename = self.normalize_path(self._main_prefix + '.kobo/images/' + ImageID + ' - NickelBookCover.parsed')
|
||||
#print "Image name Normalized: " + imagename
|
||||
if imagename is not None:
|
||||
bl[idx].thumbnail = ImageWrapper(imagename)
|
||||
bl_cache[lpath] = None
|
||||
if ContentType != '6':
|
||||
@ -174,7 +176,6 @@ class KOBO(USBMS):
|
||||
ImageID = row[0]
|
||||
cursor.close()
|
||||
|
||||
if ImageID != None:
|
||||
cursor = connection.cursor()
|
||||
if ContentType == 6:
|
||||
# Delete the shortcover_pages first
|
||||
@ -190,7 +191,7 @@ class KOBO(USBMS):
|
||||
connection.commit()
|
||||
|
||||
cursor.close()
|
||||
else:
|
||||
if ImageID != None:
|
||||
print "Error condition ImageID was not found"
|
||||
print "You likely tried to delete a book that the kobo has not yet added to the database"
|
||||
|
||||
@ -225,12 +226,16 @@ class KOBO(USBMS):
|
||||
#print "kobo book"
|
||||
ContentType = 6
|
||||
ContentID = self.contentid_from_path(path, ContentType)
|
||||
if extension == '.pdf' or extension == '.epub':
|
||||
elif extension == '.pdf' or extension == '.epub':
|
||||
# print "ePub or pdf"
|
||||
ContentType = 16
|
||||
#print "Path: " + path
|
||||
ContentID = self.contentid_from_path(path, ContentType)
|
||||
# print "ContentID: " + ContentID
|
||||
else: # if extension == '.html' or extension == '.txt':
|
||||
ContentType = 999 # Yet another hack: to get around Kobo changing how ContentID is stored
|
||||
ContentID = self.contentid_from_path(path, ContentType)
|
||||
|
||||
ImageID = self.delete_via_sql(ContentID, ContentType)
|
||||
#print " We would now delete the Images for" + ImageID
|
||||
self.delete_images(ImageID)
|
||||
@ -314,6 +319,11 @@ class KOBO(USBMS):
|
||||
ContentID = ContentID.replace(self._main_prefix, '')
|
||||
if self._card_a_prefix is not None:
|
||||
ContentID = ContentID.replace(self._card_a_prefix, '')
|
||||
elif ContentType == 999: # HTML Files
|
||||
ContentID = path
|
||||
ContentID = ContentID.replace(self._main_prefix, "/mnt/onboard/")
|
||||
if self._card_a_prefix is not None:
|
||||
ContentID = ContentID.replace(self._card_a_prefix, "/mnt/sd/")
|
||||
else: # ContentType = 16
|
||||
ContentID = path
|
||||
ContentID = ContentID.replace(self._main_prefix, "file:///mnt/onboard/")
|
||||
@ -341,6 +351,7 @@ class KOBO(USBMS):
|
||||
else:
|
||||
# if path.startswith("file:///mnt/onboard/"):
|
||||
path = path.replace("file:///mnt/onboard/", self._main_prefix)
|
||||
path = path.replace("/mnt/onboard/", self._main_prefix)
|
||||
# print "Internal: " + filename
|
||||
|
||||
return path
|
||||
|
@ -69,3 +69,21 @@ class SWEEX(USBMS):
|
||||
EBOOK_DIR_MAIN = ''
|
||||
SUPPORTS_SUB_DIRS = True
|
||||
|
||||
class PDNOVEL(USBMS):
|
||||
name = 'Pandigital Novel device interface'
|
||||
gui_name = 'PD Novel'
|
||||
description = _('Communicate with the Pandigital Novel')
|
||||
author = 'Kovid Goyal'
|
||||
supported_platforms = ['windows', 'linux', 'osx']
|
||||
FORMATS = ['epub', 'pdf']
|
||||
|
||||
VENDOR_ID = [0x18d1]
|
||||
PRODUCT_ID = [0xb004]
|
||||
BCD = [0x224]
|
||||
|
||||
VENDOR_NAME = 'ANDROID'
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '__UMS_COMPOSITE'
|
||||
|
||||
EBOOK_DIR_MAIN = 'eBooks'
|
||||
SUPPORTS_SUB_DIRS = False
|
||||
|
||||
|
@ -345,17 +345,20 @@ class XMLCache(object):
|
||||
debug_print('Updating XML Cache:', i)
|
||||
root = self.record_roots[i]
|
||||
lpath_map = self.build_lpath_map(root)
|
||||
gtz_count = ltz_count = 0
|
||||
for book in booklist:
|
||||
path = os.path.join(self.prefixes[i], *(book.lpath.split('/')))
|
||||
record = lpath_map.get(book.lpath, None)
|
||||
if record is None:
|
||||
record = self.create_text_record(root, i, book.lpath)
|
||||
self.update_text_record(record, book, path, i)
|
||||
(gtz_count, ltz_count) = self.update_text_record(record, book,
|
||||
path, i, gtz_count, ltz_count)
|
||||
# Ensure the collections in the XML database are recorded for
|
||||
# this book
|
||||
if book.device_collections is None:
|
||||
book.device_collections = []
|
||||
book.device_collections = playlist_map.get(book.lpath, [])
|
||||
debug_print('Timezone votes: %d GMT, %d LTZ'%(gtz_count, ltz_count))
|
||||
self.update_playlists(i, root, booklist, collections_attributes)
|
||||
# Update the device collections because update playlist could have added
|
||||
# some new ones.
|
||||
@ -453,15 +456,38 @@ class XMLCache(object):
|
||||
root.append(ans)
|
||||
return ans
|
||||
|
||||
def update_text_record(self, record, book, path, bl_index):
|
||||
def update_text_record(self, record, book, path, bl_index, gtz_count, ltz_count):
|
||||
'''
|
||||
Update the Sony database from the book. This is done if the timestamp in
|
||||
the db differs from the timestamp on the file.
|
||||
'''
|
||||
|
||||
# It seems that a Sony device can sometimes know what timezone it is in,
|
||||
# and apparently converts the dates to GMT when it writes them to the
|
||||
# db. Unfortunately, we can't tell when it does this, so we use a
|
||||
# horrible heuristic. First, set dates only for new books, trying to
|
||||
# avoid upsetting the sony. Use the timezone determined through the
|
||||
# voting described next. Second, voting: if a book is not new, compare
|
||||
# its Sony DB date against localtime and gmtime. Count the matches. When
|
||||
# we must set a date, use the one with the most matches. Use localtime
|
||||
# if the case of a tie, and hope it is right.
|
||||
timestamp = os.path.getmtime(path)
|
||||
date = strftime(timestamp)
|
||||
if date != record.get('date', None):
|
||||
rec_date = record.get('date', None)
|
||||
if not getattr(book, '_new_book', False): # book is not new
|
||||
if strftime(timestamp, zone=time.gmtime) == rec_date:
|
||||
gtz_count += 1
|
||||
elif strftime(timestamp, zone=time.localtime) == rec_date:
|
||||
ltz_count += 1
|
||||
else: # book is new. Set the time using the current votes
|
||||
if ltz_count >= gtz_count:
|
||||
tz = time.localtime
|
||||
debug_print("Using localtime TZ for new book", book.lpath)
|
||||
else:
|
||||
tz = time.gmtime
|
||||
debug_print("Using GMT TZ for new book", book.lpath)
|
||||
date = strftime(timestamp, zone=tz)
|
||||
record.set('date', date)
|
||||
|
||||
record.set('size', str(os.stat(path).st_size))
|
||||
title = book.title if book.title else _('Unknown')
|
||||
record.set('title', title)
|
||||
@ -486,6 +512,7 @@ class XMLCache(object):
|
||||
if 'id' not in record.attrib:
|
||||
num = self.max_id(record.getroottree().getroot())
|
||||
record.set('id', str(num+1))
|
||||
return (gtz_count, ltz_count)
|
||||
# }}}
|
||||
|
||||
# Writing the XML files {{{
|
||||
|
@ -49,7 +49,6 @@ class CHMInput(InputFormatPlugin):
|
||||
log.debug('stream.name=%s' % stream.name)
|
||||
mainname = self._chmtohtml(tdir, chm_name, no_images, log)
|
||||
mainpath = os.path.join(tdir, mainname)
|
||||
#raw_input()
|
||||
|
||||
metadata = get_metadata_from_reader(self._chm_reader)
|
||||
|
||||
@ -141,10 +140,9 @@ class CHMInput(InputFormatPlugin):
|
||||
log.debug('Found %d section nodes' % len(chapters))
|
||||
htmlpath = os.path.splitext(hhcpath)[0] + ".html"
|
||||
f = open(htmlpath, 'wb')
|
||||
if chapters:
|
||||
f.write('<html><head><meta http-equiv="Content-type"'
|
||||
' content="text/html;charset=UTF-8" /></head><body>\n')
|
||||
|
||||
if chapters:
|
||||
path0 = chapters[0][1]
|
||||
subpath = os.path.dirname(path0)
|
||||
|
||||
@ -159,6 +157,8 @@ class CHMInput(InputFormatPlugin):
|
||||
f.write(url)
|
||||
|
||||
f.write("</body></html>")
|
||||
else:
|
||||
f.write(hhcdata)
|
||||
f.close()
|
||||
return htmlpath
|
||||
|
||||
|
@ -8,7 +8,7 @@ import os, re
|
||||
from mimetypes import guess_type as guess_mimetype
|
||||
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString
|
||||
from calibre.constants import iswindows
|
||||
from calibre.constants import iswindows, filesystem_encoding
|
||||
from calibre.utils.chm.chm import CHMFile
|
||||
from calibre.utils.chm.chmlib import (
|
||||
CHM_RESOLVE_SUCCESS, CHM_ENUMERATE_NORMAL,
|
||||
@ -78,6 +78,8 @@ class CHMError(Exception):
|
||||
class CHMReader(CHMFile):
|
||||
def __init__(self, input, log):
|
||||
CHMFile.__init__(self)
|
||||
if isinstance(input, unicode):
|
||||
input = input.encode(filesystem_encoding)
|
||||
if not self.LoadCHM(input):
|
||||
raise CHMError("Unable to open CHM file '%s'"%(input,))
|
||||
self.log = log
|
||||
@ -91,7 +93,6 @@ class CHMReader(CHMFile):
|
||||
self.root, ext = os.path.splitext(self.topics.lstrip('/'))
|
||||
self.hhc_path = self.root + ".hhc"
|
||||
|
||||
|
||||
def _parse_toc(self, ul, basedir=os.getcwdu()):
|
||||
toc = TOC(play_order=self._playorder, base_path=basedir, text='')
|
||||
self._playorder += 1
|
||||
@ -152,6 +153,8 @@ class CHMReader(CHMFile):
|
||||
if f.lower() == self.hhc_path.lower():
|
||||
self.hhc_path = f
|
||||
break
|
||||
if self.hhc_path not in files and files:
|
||||
self.hhc_path = files[0]
|
||||
|
||||
def _reformat(self, data):
|
||||
try:
|
||||
@ -159,7 +162,7 @@ class CHMReader(CHMFile):
|
||||
soup = BeautifulSoup(data)
|
||||
except ValueError:
|
||||
# hit some strange encoding problems...
|
||||
print "Unable to parse html for cleaning, leaving it :("
|
||||
self.log.exception("Unable to parse html for cleaning, leaving it")
|
||||
return data
|
||||
# nuke javascript...
|
||||
[s.extract() for s in soup('script')]
|
||||
|
@ -25,13 +25,13 @@ convert_entities = functools.partial(entity_to_unicode,
|
||||
_span_pat = re.compile('<span.*?</span>', re.DOTALL|re.IGNORECASE)
|
||||
|
||||
LIGATURES = {
|
||||
u'\u00c6': u'AE',
|
||||
u'\u00e6': u'ae',
|
||||
u'\u0152': u'OE',
|
||||
u'\u0153': u'oe',
|
||||
u'\u0132': u'IJ',
|
||||
u'\u0133': u'ij',
|
||||
u'\u1D6B': u'ue',
|
||||
# u'\u00c6': u'AE',
|
||||
# u'\u00e6': u'ae',
|
||||
# u'\u0152': u'OE',
|
||||
# u'\u0153': u'oe',
|
||||
# u'\u0132': u'IJ',
|
||||
# u'\u0133': u'ij',
|
||||
# u'\u1D6B': u'ue',
|
||||
u'\uFB00': u'ff',
|
||||
u'\uFB01': u'fi',
|
||||
u'\uFB02': u'fl',
|
||||
|
58
src/calibre/ebooks/epub/fix/__init__.py
Normal file
@ -0,0 +1,58 @@
|
||||
#!/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'
|
||||
|
||||
|
||||
from calibre.customize import Plugin
|
||||
|
||||
class InvalidEpub(ValueError):
|
||||
pass
|
||||
|
||||
class ePubFixer(Plugin):
|
||||
|
||||
supported_platforms = ['windows', 'osx', 'linux']
|
||||
author = 'Kovid Goyal'
|
||||
type = _('ePub Fixer')
|
||||
can_be_disabled = True
|
||||
|
||||
# API that subclasses must implement {{{
|
||||
@property
|
||||
def short_description(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def long_description(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def fix_name(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
'''
|
||||
Return a list of 4-tuples
|
||||
(option_name, type, default, help_text)
|
||||
type is one of 'bool', 'int', 'string'
|
||||
'''
|
||||
return []
|
||||
|
||||
def run(self, container, opts, log, fix=False):
|
||||
raise NotImplementedError
|
||||
# }}}
|
||||
|
||||
def add_options_to_parser(self, parser):
|
||||
parser.add_option('--' + self.fix_name.replace('_', '-'),
|
||||
help=self.long_description, action='store_true', default=False)
|
||||
for option in self.options:
|
||||
action = 'store'
|
||||
if option[1] == 'bool':
|
||||
action = 'store_true'
|
||||
kwargs = {'action': action, 'default':option[2], 'help':option[3]}
|
||||
if option[1] != 'bool':
|
||||
kwargs['type'] = option[1]
|
||||
parser.add_option('--'+option[0].replace('_', '-'), **kwargs)
|
||||
|
182
src/calibre/ebooks/epub/fix/container.py
Normal file
@ -0,0 +1,182 @@
|
||||
#!/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, posixpath, urllib, sys
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from calibre.ebooks.epub.fix import InvalidEpub
|
||||
from calibre import guess_type, prepare_string_for_xml
|
||||
from calibre.ebooks.chardet import xml_to_unicode
|
||||
from calibre.constants import iswindows
|
||||
from calibre.utils.zipfile import ZipFile, ZIP_STORED
|
||||
|
||||
exists, join = os.path.exists, os.path.join
|
||||
|
||||
OCF_NS = 'urn:oasis:names:tc:opendocument:xmlns:container'
|
||||
OPF_NS = 'http://www.idpf.org/2007/opf'
|
||||
|
||||
class Container(object):
|
||||
|
||||
META_INF = {
|
||||
'container.xml' : True,
|
||||
'manifest.xml' : False,
|
||||
'encryption.xml' : False,
|
||||
'metadata.xml' : False,
|
||||
'signatures.xml' : False,
|
||||
'rights.xml' : False,
|
||||
}
|
||||
|
||||
def __init__(self, path, log):
|
||||
self.root = os.path.abspath(path)
|
||||
self.log = log
|
||||
self.dirtied = set([])
|
||||
self.cache = {}
|
||||
self.mime_map = {}
|
||||
|
||||
if exists(join(self.root, 'mimetype')):
|
||||
os.remove(join(self.root, 'mimetype'))
|
||||
|
||||
container_path = join(self.root, 'META-INF', 'container.xml')
|
||||
if not exists(container_path):
|
||||
raise InvalidEpub('No META-INF/container.xml in epub')
|
||||
self.container = etree.fromstring(open(container_path, 'rb').read())
|
||||
opf_files = self.container.xpath((
|
||||
r'child::ocf:rootfiles/ocf:rootfile'
|
||||
'[@media-type="%s" and @full-path]'%guess_type('a.opf')[0]
|
||||
), namespaces={'ocf':OCF_NS}
|
||||
)
|
||||
if not opf_files:
|
||||
raise InvalidEpub('META-INF/container.xml contains no link to OPF file')
|
||||
opf_path = os.path.join(self.root,
|
||||
*opf_files[0].get('full-path').split('/'))
|
||||
if not exists(opf_path):
|
||||
raise InvalidEpub('OPF file does not exist at location pointed to'
|
||||
' by META-INF/container.xml')
|
||||
|
||||
# Map of relative paths with / separators to absolute
|
||||
# paths on filesystem with os separators
|
||||
self.name_map = {}
|
||||
for dirpath, dirnames, filenames in os.walk(self.root):
|
||||
for f in filenames:
|
||||
path = join(dirpath, f)
|
||||
name = os.path.relpath(path, self.root).replace(os.sep, '/')
|
||||
self.name_map[name] = path
|
||||
if path == opf_path:
|
||||
self.opf_name = name
|
||||
self.mime_map[name] = guess_type('a.opf')[0]
|
||||
|
||||
for item in self.opf.xpath(
|
||||
'//opf:manifest/opf:item[@href and @media-type]',
|
||||
namespaces={'opf':OPF_NS}):
|
||||
href = item.get('href')
|
||||
self.mime_map[self.href_to_name(href,
|
||||
posixpath.dirname(self.opf_name))] = item.get('media-type')
|
||||
|
||||
def manifest_worthy_names(self):
|
||||
for name in self.name_map:
|
||||
if name.endswith('.opf'): continue
|
||||
if name.startswith('META-INF') and \
|
||||
posixpath.basename(name) in self.META_INF: continue
|
||||
yield name
|
||||
|
||||
def delete_name(self, name):
|
||||
self.mime_map.pop(name, None)
|
||||
path = self.name_map[name]
|
||||
os.remove(path)
|
||||
self.name_map.pop(name)
|
||||
|
||||
def manifest_item_for_name(self, name):
|
||||
href = self.name_to_href(name,
|
||||
posixpath.dirname(self.opf_name))
|
||||
q = prepare_string_for_xml(href, attribute=True)
|
||||
existing = self.opf.xpath('//opf:manifest/opf:item[@href="%s"]'%q,
|
||||
namespaces={'opf':OPF_NS})
|
||||
if not existing:
|
||||
return None
|
||||
return existing[0]
|
||||
|
||||
def add_name_to_manifest(self, name):
|
||||
item = self.manifest_item_for_name(name)
|
||||
if item is not None:
|
||||
return
|
||||
manifest = self.opf.xpath('//opf:manifest', namespaces={'opf':OPF_NS})[0]
|
||||
item = manifest.makeelement('{%s}item'%OPF_NS, nsmap={'opf':OPF_NS},
|
||||
href=self.name_to_href(name, posixpath.dirname(self.opf_name)),
|
||||
id=self.generate_manifest_id())
|
||||
mt = guess_type(posixpath.basename(name))[0]
|
||||
if not mt:
|
||||
mt = 'application/octest-stream'
|
||||
item.set('media-type', mt)
|
||||
manifest.append(item)
|
||||
|
||||
def generate_manifest_id(self):
|
||||
items = self.opf.xpath('//opf:manifest/opf:item[@id]',
|
||||
namespaces={'opf':OPF_NS})
|
||||
ids = set([x.get('id') for x in items])
|
||||
for x in xrange(sys.maxint):
|
||||
c = 'id%d'%x
|
||||
if c not in ids:
|
||||
return c
|
||||
|
||||
@property
|
||||
def opf(self):
|
||||
return self.get(self.opf_name)
|
||||
|
||||
def href_to_name(self, href, base=''):
|
||||
href = urllib.unquote(href.partition('#')[0])
|
||||
name = href
|
||||
if base:
|
||||
name = posixpath.join(base, href)
|
||||
return name
|
||||
|
||||
def name_to_href(self, name, base):
|
||||
if not base:
|
||||
return name
|
||||
return posixpath.relpath(name, base)
|
||||
|
||||
def get_raw(self, name):
|
||||
path = self.name_map[name]
|
||||
return open(path, 'rb').read()
|
||||
|
||||
def get(self, name):
|
||||
if name in self.cache:
|
||||
return self.cache[name]
|
||||
raw = self.get_raw(name)
|
||||
if name in self.mime_map:
|
||||
raw = self._parse(raw, self.mime_map[name])
|
||||
self.cache[name] = raw
|
||||
return raw
|
||||
|
||||
def set(self, name, val):
|
||||
self.cache[name] = val
|
||||
self.dirtied.add(name)
|
||||
|
||||
def _parse(self, raw, mimetype):
|
||||
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)
|
||||
return raw
|
||||
|
||||
def write(self, path):
|
||||
for name in self.dirtied:
|
||||
data = self.cache[name]
|
||||
raw = data
|
||||
if hasattr(data, 'xpath'):
|
||||
raw = etree.tostring(data, encoding='utf-8',
|
||||
xml_declaration=True)
|
||||
with open(self.name_map[name], 'wb') as f:
|
||||
f.write(raw)
|
||||
self.dirtied.clear()
|
||||
zf = ZipFile(path, 'w')
|
||||
zf.writestr('mimetype', bytes(guess_type('a.epub')[0]),
|
||||
compression=ZIP_STORED)
|
||||
zf.add_dir(self.root)
|
||||
zf.close()
|
||||
|
82
src/calibre/ebooks/epub/fix/epubcheck.py
Normal file
@ -0,0 +1,82 @@
|
||||
#!/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'
|
||||
|
||||
from calibre.ebooks.epub.fix import ePubFixer, InvalidEpub
|
||||
from calibre.utils.date import parse_date, strptime
|
||||
|
||||
|
||||
class Epubcheck(ePubFixer):
|
||||
|
||||
name = 'Workaround epubcheck bugs'
|
||||
|
||||
@property
|
||||
def short_description(self):
|
||||
return _('Workaround epubcheck bugs')
|
||||
|
||||
@property
|
||||
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 '
|
||||
'them with constructs that epubcheck likes. This may cause '
|
||||
'significant changes to your epub, complain to the epubcheck '
|
||||
'project.')
|
||||
|
||||
@property
|
||||
def fix_name(self):
|
||||
return 'epubcheck'
|
||||
|
||||
def fix_pubdates(self):
|
||||
dirtied = False
|
||||
opf = self.container.opf
|
||||
for dcdate in opf.xpath('//dc:date',
|
||||
namespaces={'dc':'http://purl.org/dc/elements/1.1/'}):
|
||||
raw = dcdate.text
|
||||
if not raw: raw = ''
|
||||
default = strptime('2000-1-1', '%Y-%m-%d', as_utc=True)
|
||||
try:
|
||||
ts = parse_date(raw, assume_utc=False, as_utc=True,
|
||||
default=default)
|
||||
except:
|
||||
raise InvalidEpub('Invalid date set in OPF', raw)
|
||||
sval = ts.strftime('%Y-%m-%d')
|
||||
if sval != raw:
|
||||
self.log.error(
|
||||
'OPF contains date', raw, 'that epubcheck does not like')
|
||||
if self.fix:
|
||||
dcdate.text = sval
|
||||
self.log('\tReplaced', raw, 'with', sval)
|
||||
dirtied = True
|
||||
if dirtied:
|
||||
self.container.set(self.container.opf_name, opf)
|
||||
|
||||
def fix_preserve_aspect_ratio(self):
|
||||
for name in self.container.name_map:
|
||||
mt = self.container.mime_map.get(name, '')
|
||||
if mt.lower() == 'application/xhtml+xml':
|
||||
root = self.container.get(name)
|
||||
dirtied = False
|
||||
for svg in root.xpath('//svg:svg[@preserveAspectRatio="none"]',
|
||||
namespaces={'svg':'http://www.w3.org/2000/svg'}):
|
||||
self.log.error('Found <svg> element with'
|
||||
' preserveAspectRatio="none" which epubcheck '
|
||||
'cannot handle')
|
||||
if self.fix:
|
||||
svg.set('preserveAspectRatio', 'xMidYMid meet')
|
||||
dirtied = True
|
||||
self.log('\tReplaced none with xMidYMid meet')
|
||||
if dirtied:
|
||||
self.container.set(name, root)
|
||||
|
||||
|
||||
def run(self, container, opts, log, fix=False):
|
||||
self.container = container
|
||||
self.opts = opts
|
||||
self.log = log
|
||||
self.fix = fix
|
||||
self.fix_pubdates()
|
||||
self.fix_preserve_aspect_ratio()
|
56
src/calibre/ebooks/epub/fix/main.py
Normal file
@ -0,0 +1,56 @@
|
||||
#!/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 sys, os
|
||||
|
||||
from calibre.utils.config import OptionParser
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre import CurrentDir
|
||||
from calibre.utils.zipfile import ZipFile
|
||||
from calibre.utils.logging import default_log
|
||||
from calibre.customize.ui import epub_fixers
|
||||
from calibre.ebooks.epub.fix.container import Container
|
||||
|
||||
def option_parser():
|
||||
parser = OptionParser(usage=_(
|
||||
'%prog [options] file.epub\n\n'
|
||||
'Fix common problems in EPUB files that can cause them '
|
||||
'to be rejected by poorly designed publishing services.\n\n'
|
||||
'By default, no fixing is done and messages are printed out '
|
||||
'for each error detected. Use the options to control which errors '
|
||||
'are automatically fixed.'))
|
||||
for fixer in epub_fixers():
|
||||
fixer.add_options_to_parser(parser)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def run(epub, opts, log):
|
||||
with TemporaryDirectory('_epub-fix') as tdir:
|
||||
with CurrentDir(tdir):
|
||||
zf = ZipFile(epub)
|
||||
zf.extractall()
|
||||
zf.close()
|
||||
container = Container(tdir, log)
|
||||
for fixer in epub_fixers():
|
||||
fix = getattr(opts, fixer.fix_name, False)
|
||||
fixer.run(container, opts, log, fix=fix)
|
||||
container.write(epub)
|
||||
|
||||
def main(args=sys.argv):
|
||||
parser = option_parser()
|
||||
opts, args = parser.parse_args(args)
|
||||
if len(args) != 2:
|
||||
parser.print_help()
|
||||
print
|
||||
default_log.error(_('You must specify an epub file'))
|
||||
return
|
||||
epub = os.path.abspath(args[1])
|
||||
run(epub, opts, default_log)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
49
src/calibre/ebooks/epub/fix/unmanifested.py
Normal file
@ -0,0 +1,49 @@
|
||||
#!/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'
|
||||
|
||||
|
||||
from calibre.ebooks.epub.fix import ePubFixer
|
||||
|
||||
class Unmanifested(ePubFixer):
|
||||
|
||||
name = 'Fix unmanifested files'
|
||||
|
||||
@property
|
||||
def short_description(self):
|
||||
return _('Fix unmanifested files')
|
||||
|
||||
@property
|
||||
def long_description(self):
|
||||
return _('Fix unmanifested files. %prog can either add them to '
|
||||
'the manifest or delete them as specified by the '
|
||||
'delete unmanifested option.')
|
||||
|
||||
@property
|
||||
def fix_name(self):
|
||||
return 'unmanifested'
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
return [('delete_unmanifested', 'bool', False,
|
||||
_('Delete unmanifested files instead of adding them to the manifest'))]
|
||||
|
||||
def run(self, container, opts, log, fix=False):
|
||||
dirtied = False
|
||||
for name in list(container.manifest_worthy_names()):
|
||||
item = container.manifest_item_for_name(name)
|
||||
if item is None:
|
||||
log.error(name, 'not in manifest')
|
||||
if fix:
|
||||
if opts.delete_unmanifested:
|
||||
container.delete_name(name)
|
||||
log('\tDeleted')
|
||||
else:
|
||||
container.add_name_to_manifest(name)
|
||||
log('\tAdded to manifest')
|
||||
dirtied = True
|
||||
if dirtied:
|
||||
container.set(container.opf_name, container.opf)
|
@ -367,7 +367,7 @@ class LRFInput(InputFormatPlugin):
|
||||
xml = d.to_xml(write_files=True)
|
||||
if options.verbose > 2:
|
||||
open('lrs.xml', 'wb').write(xml.encode('utf-8'))
|
||||
parser = etree.XMLParser(recover=True, no_network=True, huge_tree=True)
|
||||
parser = etree.XMLParser(no_network=True, huge_tree=True)
|
||||
doc = etree.fromstring(xml, parser=parser)
|
||||
char_button_map = {}
|
||||
for x in doc.xpath('//CharButton[@refobj]'):
|
||||
|
@ -870,7 +870,7 @@ class Text(LRFStream):
|
||||
open_containers = collections.deque()
|
||||
for c in self.content:
|
||||
if isinstance(c, basestring):
|
||||
s += prepare_string_for_xml(c)
|
||||
s += prepare_string_for_xml(c).replace('\0', '')
|
||||
elif c is None:
|
||||
if open_containers:
|
||||
p = open_containers.pop()
|
||||
|
@ -351,9 +351,13 @@ def search(title=None, author=None, publisher=None, isbn=None, isbndb_key=None,
|
||||
if len(results) > 1:
|
||||
if not results[0].comments or len(results[0].comments) == 0:
|
||||
for r in results[1:]:
|
||||
if title.lower() == r.title[:len(title)].lower() and r.comments and len(r.comments):
|
||||
try:
|
||||
if title and title.lower() == r.title[:len(title)].lower() \
|
||||
and r.comments and len(r.comments):
|
||||
results[0].comments = r.comments
|
||||
break
|
||||
except:
|
||||
pass
|
||||
# Find a pubdate
|
||||
pubdate = None
|
||||
for r in results:
|
||||
|
@ -58,6 +58,7 @@ class FormatState(object):
|
||||
self.fsize = 3
|
||||
self.ids = set()
|
||||
self.valign = 'baseline'
|
||||
self.nest = False
|
||||
self.italic = False
|
||||
self.bold = False
|
||||
self.strikethrough = False
|
||||
@ -233,9 +234,17 @@ class MobiMLizer(object):
|
||||
inline = etree.SubElement(inline, XHTML('a'), href=href)
|
||||
bstate.anchor = inline
|
||||
if valign == 'super':
|
||||
inline = etree.SubElement(inline, XHTML('sup'))
|
||||
parent = inline
|
||||
if istate.nest and bstate.inline is not None:
|
||||
parent = bstate.inline
|
||||
istate.nest = False
|
||||
inline = etree.SubElement(parent, XHTML('sup'))
|
||||
elif valign == 'sub':
|
||||
inline = etree.SubElement(inline, XHTML('sub'))
|
||||
parent = inline
|
||||
if istate.nest and bstate.inline is not None:
|
||||
parent = bstate.inline
|
||||
istate.nest = False
|
||||
inline = etree.SubElement(parent, XHTML('sub'))
|
||||
elif fsize != 3:
|
||||
inline = etree.SubElement(inline, XHTML('font'),
|
||||
size=str(fsize))
|
||||
@ -343,8 +352,10 @@ class MobiMLizer(object):
|
||||
istate.family = 'serif'
|
||||
valign = style['vertical-align']
|
||||
if valign in ('super', 'text-top') or asfloat(valign) > 0:
|
||||
istate.nest = istate.valign in ('sub', 'super')
|
||||
istate.valign = 'super'
|
||||
elif valign == 'sub' or asfloat(valign) < 0:
|
||||
istate.nest = istate.valign in ('sub', 'super')
|
||||
istate.valign = 'sub'
|
||||
else:
|
||||
istate.valign = 'baseline'
|
||||
|
@ -26,7 +26,7 @@ from calibre.ebooks.chardet import xml_to_unicode
|
||||
from calibre.ebooks.oeb.entitydefs import ENTITYDEFS
|
||||
from calibre.ebooks.conversion.preprocess import CSSPreProcessor
|
||||
|
||||
RECOVER_PARSER = etree.XMLParser(recover=True, no_network=True, huge_tree=True)
|
||||
RECOVER_PARSER = etree.XMLParser(recover=True, no_network=True)
|
||||
|
||||
XML_NS = 'http://www.w3.org/XML/1998/namespace'
|
||||
XHTML_NS = 'http://www.w3.org/1999/xhtml'
|
||||
|
@ -10,6 +10,7 @@ from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation
|
||||
from calibre.ebooks.txt.processor import convert_basic, convert_markdown, \
|
||||
separate_paragraphs_single_line, separate_paragraphs_print_formatted, \
|
||||
preserve_spaces
|
||||
from calibre import _ent_pat, xml_entity_to_unicode
|
||||
|
||||
class TXTInput(InputFormatPlugin):
|
||||
|
||||
@ -55,6 +56,8 @@ class TXTInput(InputFormatPlugin):
|
||||
if options.preserve_spaces:
|
||||
txt = preserve_spaces(txt)
|
||||
|
||||
txt = _ent_pat.sub(xml_entity_to_unicode, txt)
|
||||
|
||||
if options.markdown:
|
||||
log.debug('Running text though markdown conversion...')
|
||||
try:
|
||||
|
@ -226,10 +226,11 @@ def error_dialog(parent, title, msg, det_msg='', show=False,
|
||||
return d.exec_()
|
||||
return d
|
||||
|
||||
def question_dialog(parent, title, msg, det_msg='', show_copy_button=True):
|
||||
d = MessageBox(QMessageBox.Question, title, msg, QMessageBox.Yes|QMessageBox.No,
|
||||
def question_dialog(parent, title, msg, det_msg='', show_copy_button=True,
|
||||
buttons=QMessageBox.Yes|QMessageBox.No):
|
||||
d = MessageBox(QMessageBox.Question, title, msg, buttons,
|
||||
parent, det_msg)
|
||||
d.setIconPixmap(QPixmap(I('dialog_information.svg')))
|
||||
d.setIconPixmap(QPixmap(I('dialog_question.svg')))
|
||||
d.setEscapeButton(QMessageBox.No)
|
||||
if not show_copy_button:
|
||||
d.cb.setVisible(False)
|
||||
@ -592,6 +593,9 @@ def open_url(qurl):
|
||||
|
||||
|
||||
def open_local_file(path):
|
||||
if iswindows:
|
||||
os.startfile(os.path.normpath(path))
|
||||
else:
|
||||
url = QUrl.fromLocalFile(path)
|
||||
open_url(url)
|
||||
|
||||
|
@ -323,7 +323,6 @@ class AddAction(object): # {{{
|
||||
accept = True
|
||||
if accept:
|
||||
event.accept()
|
||||
self.cover_cache.refresh([cid])
|
||||
self.library_view.model().current_changed(current_idx, current_idx)
|
||||
|
||||
def __add_filesystem_book(self, paths, allow_device=True):
|
||||
|
@ -67,6 +67,13 @@ if pictureflow is not None:
|
||||
ans = ''
|
||||
return ans
|
||||
|
||||
def subtitle(self, index):
|
||||
try:
|
||||
return u'\u2605'*self.model.rating(index)
|
||||
except:
|
||||
pass
|
||||
return ''
|
||||
|
||||
def reset(self):
|
||||
self.dataChanged.emit()
|
||||
|
||||
@ -115,6 +122,7 @@ class CoverFlowMixin(object):
|
||||
self.sync_cf_to_listview)
|
||||
self.db_images = DatabaseImages(self.library_view.model())
|
||||
self.cover_flow.setImages(self.db_images)
|
||||
self.cover_flow.itemActivated.connect(self.view_specific_book)
|
||||
else:
|
||||
self.cover_flow = QLabel('<p>'+_('Cover browser could not be loaded')
|
||||
+'<br>'+pictureflowerror)
|
||||
|
@ -10,7 +10,7 @@ from functools import partial
|
||||
from binascii import unhexlify
|
||||
|
||||
from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, QPixmap, \
|
||||
Qt, pyqtSignal, QColor, QPainter, QDialog
|
||||
Qt, pyqtSignal, QColor, QPainter, QDialog, QMessageBox
|
||||
from PyQt4.QtSvg import QSvgRenderer
|
||||
|
||||
from calibre.customize.ui import available_input_formats, available_output_formats, \
|
||||
@ -758,10 +758,8 @@ class DeviceMixin(object): # {{{
|
||||
self.refresh_ondevice_info (device_connected = True, reset_only = True)
|
||||
else:
|
||||
self.device_connected = None
|
||||
self.status_bar.device_disconnected()
|
||||
self.location_view.model().update_devices()
|
||||
self.vanity.setText(self.vanity_template%\
|
||||
dict(version=self.latest_version, device=' '))
|
||||
self.device_info = ' '
|
||||
if self.current_view() != self.library_view:
|
||||
self.book_details.reset_info()
|
||||
self.location_view.setCurrentIndex(self.location_view.model().index(0))
|
||||
@ -775,10 +773,7 @@ class DeviceMixin(object): # {{{
|
||||
return self.device_job_exception(job)
|
||||
info, cp, fs = job.result
|
||||
self.location_view.model().update_devices(cp, fs)
|
||||
self.device_info = _('Connected ')+info[0]
|
||||
self.vanity.setText(self.vanity_template%\
|
||||
dict(version=self.latest_version, device=self.device_info))
|
||||
|
||||
self.status_bar.device_connected(info[0])
|
||||
self.device_manager.books(Dispatcher(self.metadata_downloaded))
|
||||
|
||||
def metadata_downloaded(self, job):
|
||||
@ -957,7 +952,8 @@ class DeviceMixin(object): # {{{
|
||||
autos = '\n'.join('%s'%i for i in autos)
|
||||
if question_dialog(self, _('No suitable formats'),
|
||||
_('Auto convert the following books before sending via '
|
||||
'email?'), det_msg=autos):
|
||||
'email?'), det_msg=autos,
|
||||
buttons=QMessageBox.Yes|QMessageBox.Cancel):
|
||||
self.auto_convert_mail(to, fmts, delete_from_library, auto, format)
|
||||
|
||||
if bad:
|
||||
@ -1056,7 +1052,8 @@ class DeviceMixin(object): # {{{
|
||||
autos = '\n'.join('%s'%i for i in autos)
|
||||
if question_dialog(self, _('No suitable formats'),
|
||||
_('Auto convert the following books before uploading to '
|
||||
'the device?'), det_msg=autos):
|
||||
'the device?'), det_msg=autos,
|
||||
buttons=QMessageBox.Yes|QMessageBox.Cancel):
|
||||
self.auto_convert_catalogs(auto, format)
|
||||
files = [f for f in files if f is not None]
|
||||
if not files:
|
||||
@ -1117,7 +1114,8 @@ class DeviceMixin(object): # {{{
|
||||
autos = '\n'.join('%s'%i for i in autos)
|
||||
if question_dialog(self, _('No suitable formats'),
|
||||
_('Auto convert the following books before uploading to '
|
||||
'the device?'), det_msg=autos):
|
||||
'the device?'), det_msg=autos,
|
||||
buttons=QMessageBox.Yes|QMessageBox.Cancel):
|
||||
self.auto_convert_news(auto, format)
|
||||
files = [f for f in files if f is not None]
|
||||
for f in files:
|
||||
@ -1235,7 +1233,8 @@ class DeviceMixin(object): # {{{
|
||||
autos = '\n'.join('%s'%i for i in autos)
|
||||
if question_dialog(self, _('No suitable formats'),
|
||||
_('Auto convert the following books before uploading to '
|
||||
'the device?'), det_msg=autos):
|
||||
'the device?'), det_msg=autos,
|
||||
buttons=QMessageBox.Yes|QMessageBox.Cancel):
|
||||
self.auto_convert(auto, on_card, format)
|
||||
|
||||
if bad:
|
||||
|
@ -495,6 +495,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
||||
li = i
|
||||
self.opt_gui_layout.setCurrentIndex(li)
|
||||
self.opt_disable_animations.setChecked(config['disable_animations'])
|
||||
self.opt_show_donate_button.setChecked(config['show_donate_button'])
|
||||
|
||||
def check_port_value(self, *args):
|
||||
port = self.port.value()
|
||||
@ -871,6 +872,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
||||
config['overwrite_author_title_metadata'] = self.opt_overwrite_author_title_metadata.isChecked()
|
||||
config['enforce_cpu_limit'] = bool(self.opt_enforce_cpu_limit.isChecked())
|
||||
config['disable_animations'] = bool(self.opt_disable_animations.isChecked())
|
||||
config['show_donate_button'] = bool(self.opt_show_donate_button.isChecked())
|
||||
gprefs['show_splash_screen'] = bool(self.show_splash_screen.isChecked())
|
||||
fmts = []
|
||||
for i in range(self.viewer.count()):
|
||||
|
@ -356,7 +356,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="show_splash_screen">
|
||||
<property name="text">
|
||||
<string>Show &splash screen at startup</string>
|
||||
@ -665,6 +665,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="opt_show_donate_button">
|
||||
<property name="text">
|
||||
<string>Show &donate button (restart)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_6">
|
||||
|
@ -25,6 +25,12 @@ class tableItem(QTableWidgetItem):
|
||||
def __lt__(self, other):
|
||||
return self.sort < other.sort
|
||||
|
||||
class centeredTableItem(tableItem):
|
||||
|
||||
def __init__(self, text):
|
||||
tableItem.__init__(self, text)
|
||||
self.setTextAlignment(Qt.AlignCenter)
|
||||
|
||||
class titleTableItem(tableItem):
|
||||
|
||||
def __init__(self, text):
|
||||
@ -60,14 +66,14 @@ class DeleteMatchingFromDeviceDialog(QDialog, Ui_DeleteMatchingFromDeviceDialog)
|
||||
|
||||
self.explanation.setText('<p>'+_('All checked books will be '
|
||||
'<b>permanently deleted</b> from your '
|
||||
'device. Please verify the list.'+'</p>'))
|
||||
'device. Please verify the list.')+'</p>')
|
||||
self.buttonBox.accepted.connect(self.accepted)
|
||||
self.table.cellClicked.connect(self.cell_clicked)
|
||||
self.table.setSelectionMode(QAbstractItemView.NoSelection)
|
||||
self.table.setColumnCount(5)
|
||||
self.table.setColumnCount(7)
|
||||
self.table.setHorizontalHeaderLabels(
|
||||
['', _('Location'), _('Title'),
|
||||
_('Author'), _('Date'), _('Format')])
|
||||
['', _('Location'), _('Title'), _('Author'),
|
||||
_('Date'), _('Format'), _('Path')])
|
||||
rows = 0
|
||||
for card in items:
|
||||
rows += len(items[card][1])
|
||||
@ -85,7 +91,8 @@ class DeleteMatchingFromDeviceDialog(QDialog, Ui_DeleteMatchingFromDeviceDialog)
|
||||
self.table.setItem(row, 2, titleTableItem(book.title))
|
||||
self.table.setItem(row, 3, authorTableItem(book))
|
||||
self.table.setItem(row, 4, dateTableItem(book.datetime))
|
||||
self.table.setItem(row, 5, tableItem(book.path.rpartition('.')[2]))
|
||||
self.table.setItem(row, 5, centeredTableItem(book.path.rpartition('.')[2]))
|
||||
self.table.setItem(row, 6, tableItem(book.path))
|
||||
row += 1
|
||||
self.table.setCurrentCell(0, 1)
|
||||
self.table.resizeColumnsToContents()
|
||||
|
@ -17,7 +17,7 @@ class ListWidgetItem(QListWidgetItem):
|
||||
def data(self, role):
|
||||
if role == Qt.DisplayRole:
|
||||
if self.old_value != self.cur_value:
|
||||
return _('%s (was %s)'%(self.cur_value, self.old_value))
|
||||
return _('%s (was %s)')%(self.cur_value, self.old_value)
|
||||
else:
|
||||
return self.cur_value
|
||||
elif role == Qt.EditRole:
|
||||
|
@ -5,22 +5,22 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import functools
|
||||
import functools, sys, os
|
||||
|
||||
from PyQt4.Qt import QMenu, Qt, pyqtSignal, QToolButton, QIcon, QStackedWidget, \
|
||||
QSize, QSizePolicy, QStatusBar
|
||||
from PyQt4.Qt import QMenu, Qt, pyqtSignal, QIcon, QStackedWidget, \
|
||||
QSize, QSizePolicy, QStatusBar, QUrl, QLabel, QFont
|
||||
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.constants import isosx, __appname__, preferred_encoding
|
||||
from calibre.gui2 import config, is_widescreen
|
||||
from calibre.constants import isosx, __appname__, preferred_encoding, \
|
||||
__version__
|
||||
from calibre.gui2 import config, is_widescreen, open_url
|
||||
from calibre.gui2.library.views import BooksView, DeviceBooksView
|
||||
from calibre.gui2.widgets import Splitter
|
||||
from calibre.gui2.tag_view import TagBrowserWidget
|
||||
from calibre.gui2.book_details import BookDetails
|
||||
from calibre.gui2.notify import get_notifier
|
||||
|
||||
|
||||
_keep_refs = []
|
||||
|
||||
def partial(*args, **kwargs):
|
||||
@ -48,6 +48,7 @@ class SaveMenu(QMenu): # {{{
|
||||
class ToolbarMixin(object): # {{{
|
||||
|
||||
def __init__(self):
|
||||
self.action_help.triggered.connect(self.show_help)
|
||||
md = QMenu()
|
||||
md.addAction(_('Edit metadata individually'),
|
||||
partial(self.edit_metadata, False, bulk=False))
|
||||
@ -172,18 +173,8 @@ class ToolbarMixin(object): # {{{
|
||||
for x in (self.preferences_action, self.action_preferences):
|
||||
x.triggered.connect(self.do_config)
|
||||
|
||||
for x in ('news', 'edit', 'sync', 'convert', 'save', 'add', 'view',
|
||||
'del', 'preferences'):
|
||||
w = self.tool_bar.widgetForAction(getattr(self, 'action_'+x))
|
||||
w.setPopupMode(w.MenuButtonPopup)
|
||||
|
||||
self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||
|
||||
for ch in self.tool_bar.children():
|
||||
if isinstance(ch, QToolButton):
|
||||
ch.setCursor(Qt.PointingHandCursor)
|
||||
|
||||
self.tool_bar.contextMenuEvent = self.no_op
|
||||
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'])
|
||||
@ -362,12 +353,50 @@ class Stack(QStackedWidget): # {{{
|
||||
|
||||
class StatusBar(QStatusBar): # {{{
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QStatusBar.__init__(self, parent)
|
||||
self.default_message = __appname__ + ' ' + _('version') + ' ' + \
|
||||
self.get_version() + ' ' + _('created by Kovid Goyal')
|
||||
self.device_string = ''
|
||||
self.update_label = QLabel('')
|
||||
self.update_label.setOpenExternalLinks(True)
|
||||
self.addPermanentWidget(self.update_label)
|
||||
self.update_label.setVisible(False)
|
||||
self._font = QFont()
|
||||
self._font.setBold(True)
|
||||
self.setFont(self._font)
|
||||
|
||||
def initialize(self, systray=None):
|
||||
self.systray = systray
|
||||
self.notifier = get_notifier(systray)
|
||||
self.messageChanged.connect(self.message_changed,
|
||||
type=Qt.QueuedConnection)
|
||||
self.message_changed('')
|
||||
|
||||
def device_connected(self, devname):
|
||||
self.device_string = _('Connected ') + devname
|
||||
self.clearMessage()
|
||||
|
||||
def device_disconnected(self):
|
||||
self.device_string = ''
|
||||
self.clearMessage()
|
||||
|
||||
def new_version_available(self, ver, url):
|
||||
msg = (u'<span style="color:red; font-weight: bold">%s: <a href="%s">%s<a></span>') % (
|
||||
_('Update found'), url, ver)
|
||||
self.update_label.setText(msg)
|
||||
self.update_label.setCursor(Qt.PointingHandCursor)
|
||||
self.update_label.setVisible(True)
|
||||
|
||||
def get_version(self):
|
||||
dv = os.environ.get('CALIBRE_DEVELOP_FROM', None)
|
||||
v = __version__
|
||||
if getattr(sys, 'frozen', False) and dv and os.path.abspath(dv) in sys.path:
|
||||
v += '*'
|
||||
return v
|
||||
|
||||
def show_message(self, msg, timeout=0):
|
||||
QStatusBar.showMessage(self, msg, timeout)
|
||||
self.showMessage(msg, timeout)
|
||||
if self.notifier is not None and not config['disable_tray_notification']:
|
||||
if isosx and isinstance(msg, unicode):
|
||||
try:
|
||||
@ -377,15 +406,21 @@ class StatusBar(QStatusBar): # {{{
|
||||
self.notifier(msg)
|
||||
|
||||
def clear_message(self):
|
||||
QStatusBar.clearMessage(self)
|
||||
self.clearMessage()
|
||||
|
||||
def message_changed(self, msg):
|
||||
if not msg or msg.isEmpty() or msg.isNull():
|
||||
extra = ''
|
||||
if self.device_string:
|
||||
extra = ' ..::.. ' + self.device_string
|
||||
self.showMessage(self.default_message + extra)
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
class LayoutMixin(object): # {{{
|
||||
|
||||
def __init__(self):
|
||||
self.setupUi(self)
|
||||
self.setWindowTitle(__appname__)
|
||||
|
||||
if config['gui_layout'] == 'narrow': # narrow {{{
|
||||
self.book_details = BookDetails(False, self)
|
||||
|
460
src/calibre/gui2/layout.py
Normal file
@ -0,0 +1,460 @@
|
||||
#!/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'
|
||||
|
||||
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
|
||||
|
||||
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 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
|
||||
|
||||
# }}}
|
||||
|
||||
# Location View {{{
|
||||
|
||||
class LocationModel(QAbstractListModel): # {{{
|
||||
|
||||
devicesChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, parent):
|
||||
QAbstractListModel.__init__(self, parent)
|
||||
self.icons = [QVariant(QIcon(I('library.png'))),
|
||||
QVariant(QIcon(I('reader.svg'))),
|
||||
QVariant(QIcon(I('sd.svg'))),
|
||||
QVariant(QIcon(I('sd.svg')))]
|
||||
self.text = [_('Library\n%d books'),
|
||||
_('Reader\n%s'),
|
||||
_('Card A\n%s'),
|
||||
_('Card B\n%s')]
|
||||
self.free = [-1, -1, -1]
|
||||
self.count = 0
|
||||
self.highlight_row = 0
|
||||
self.library_tooltip = _('Click to see the books available on your computer')
|
||||
self.tooltips = [
|
||||
self.library_tooltip,
|
||||
_('Click to see the books in the main memory of your reader'),
|
||||
_('Click to see the books on storage card A in your reader'),
|
||||
_('Click to see the books on storage card B in your reader')
|
||||
]
|
||||
|
||||
def database_changed(self, db):
|
||||
lp = db.library_path
|
||||
if not isinstance(lp, unicode):
|
||||
lp = lp.decode(filesystem_encoding, 'replace')
|
||||
self.tooltips[0] = self.library_tooltip + '\n\n' + \
|
||||
_('Books located at') + ' ' + lp
|
||||
self.dataChanged.emit(self.index(0), self.index(0))
|
||||
|
||||
def rowCount(self, *args):
|
||||
return 1 + len([i for i in self.free if i >= 0])
|
||||
|
||||
def get_device_row(self, row):
|
||||
if row == 2 and self.free[1] == -1 and self.free[2] > -1:
|
||||
row = 3
|
||||
return row
|
||||
|
||||
def get_tooltip(self, row, drow):
|
||||
ans = self.tooltips[row]
|
||||
if row > 0:
|
||||
fs = self.free[drow-1]
|
||||
if fs > -1:
|
||||
ans += '\n\n%s '%(human_readable(fs)) + _('free')
|
||||
return ans
|
||||
|
||||
def data(self, index, role):
|
||||
row = index.row()
|
||||
drow = self.get_device_row(row)
|
||||
data = NONE
|
||||
if role == Qt.DisplayRole:
|
||||
text = self.text[drow]%(human_readable(self.free[drow-1])) if row > 0 \
|
||||
else self.text[drow]%self.count
|
||||
data = QVariant(text)
|
||||
elif role == Qt.DecorationRole:
|
||||
data = self.icons[drow]
|
||||
elif role in (Qt.ToolTipRole, Qt.StatusTipRole):
|
||||
ans = self.get_tooltip(row, drow)
|
||||
data = QVariant(ans)
|
||||
elif role == Qt.SizeHintRole:
|
||||
data = QVariant(QSize(155, 90))
|
||||
elif role == Qt.FontRole:
|
||||
font = QFont('monospace')
|
||||
font.setBold(row == self.highlight_row)
|
||||
data = QVariant(font)
|
||||
elif role == Qt.ForegroundRole and row == self.highlight_row:
|
||||
return QVariant(QApplication.palette().brush(
|
||||
QPalette.HighlightedText))
|
||||
elif role == Qt.BackgroundRole and row == self.highlight_row:
|
||||
return QVariant(QApplication.palette().brush(
|
||||
QPalette.Highlight))
|
||||
|
||||
return data
|
||||
|
||||
def device_connected(self, dev):
|
||||
self.icons[1] = QIcon(dev.icon)
|
||||
self.dataChanged.emit(self.index(1), self.index(1))
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
return NONE
|
||||
|
||||
def update_devices(self, cp=(None, None), fs=[-1, -1, -1]):
|
||||
if cp is None:
|
||||
cp = (None, None)
|
||||
if isinstance(cp, (str, unicode)):
|
||||
cp = (cp, None)
|
||||
if len(fs) < 3:
|
||||
fs = list(fs) + [0]
|
||||
self.free[0] = fs[0]
|
||||
self.free[1] = fs[1]
|
||||
self.free[2] = fs[2]
|
||||
cpa, cpb = cp
|
||||
self.free[1] = fs[1] if fs[1] is not None and cpa is not None else -1
|
||||
self.free[2] = fs[2] if fs[2] is not None and cpb is not None else -1
|
||||
self.reset()
|
||||
self.devicesChanged.emit()
|
||||
|
||||
def location_changed(self, row):
|
||||
self.highlight_row = row
|
||||
self.dataChanged.emit(
|
||||
self.index(0), self.index(self.rowCount(QModelIndex())-1))
|
||||
|
||||
def location_for_row(self, row):
|
||||
if row == 0: return 'library'
|
||||
if row == 1: return 'main'
|
||||
if row == 3: return 'cardb'
|
||||
return 'carda' if self.free[1] > -1 else 'cardb'
|
||||
|
||||
# }}}
|
||||
|
||||
class LocationView(QListView):
|
||||
|
||||
unmount_device = pyqtSignal()
|
||||
location_selected = pyqtSignal(object)
|
||||
|
||||
def __init__(self, parent):
|
||||
QListView.__init__(self, parent)
|
||||
self.setModel(LocationModel(self))
|
||||
self.reset()
|
||||
self.currentChanged = self.current_changed
|
||||
|
||||
self.eject_button = EjectButton(self)
|
||||
self.eject_button.hide()
|
||||
|
||||
self.entered.connect(self.item_entered)
|
||||
self.viewportEntered.connect(self.viewport_entered)
|
||||
self.eject_button.clicked.connect(self.eject_clicked)
|
||||
self.model().devicesChanged.connect(self.eject_button.hide)
|
||||
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,
|
||||
QSizePolicy.Expanding))
|
||||
self.setMouseTracking(True)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.setEditTriggers(self.NoEditTriggers)
|
||||
self.setTabKeyNavigation(True)
|
||||
self.setProperty("showDropIndicator", True)
|
||||
self.setSelectionMode(self.SingleSelection)
|
||||
self.setIconSize(QSize(40, 40))
|
||||
self.setMovement(self.Static)
|
||||
self.setFlow(self.LeftToRight)
|
||||
self.setGridSize(QSize(175, 90))
|
||||
self.setViewMode(self.ListMode)
|
||||
self.setWordWrap(True)
|
||||
self.setObjectName("location_view")
|
||||
self.setMaximumHeight(74)
|
||||
|
||||
def eject_clicked(self, *args):
|
||||
self.unmount_device.emit()
|
||||
|
||||
def count_changed(self, new_count):
|
||||
self.model().count = new_count
|
||||
self.model().reset()
|
||||
|
||||
def current_changed(self, current, previous):
|
||||
if current.isValid():
|
||||
i = current.row()
|
||||
location = self.model().location_for_row(i)
|
||||
self.location_selected.emit(location)
|
||||
self.model().location_changed(i)
|
||||
|
||||
def location_changed(self, row):
|
||||
if 0 <= row and row <= 3:
|
||||
self.model().location_changed(row)
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.unsetCursor()
|
||||
self.eject_button.hide()
|
||||
|
||||
def item_entered(self, location):
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
self.eject_button.hide()
|
||||
|
||||
if location.row() == 1:
|
||||
rect = self.visualRect(location)
|
||||
|
||||
self.eject_button.resize(rect.height()/2, rect.height()/2)
|
||||
|
||||
x, y = rect.left(), rect.top()
|
||||
x = x + (rect.width() - self.eject_button.width() - 2)
|
||||
y += 6
|
||||
|
||||
self.eject_button.move(x, y)
|
||||
self.eject_button.show()
|
||||
|
||||
def viewport_entered(self):
|
||||
self.unsetCursor()
|
||||
self.eject_button.hide()
|
||||
|
||||
|
||||
class EjectButton(QAbstractButton):
|
||||
|
||||
def __init__(self, parent):
|
||||
QAbstractButton.__init__(self, parent)
|
||||
self.mouse_over = False
|
||||
|
||||
def enterEvent(self, event):
|
||||
self.mouse_over = True
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.mouse_over = False
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
painter.setClipRect(event.rect())
|
||||
image = QPixmap(I('eject')).scaledToHeight(event.rect().height(),
|
||||
Qt.SmoothTransformation)
|
||||
|
||||
if not self.mouse_over:
|
||||
alpha_mask = QPixmap(image.width(), image.height())
|
||||
color = QColor(128, 128, 128)
|
||||
alpha_mask.fill(color)
|
||||
image.setAlphaChannel(alpha_mask)
|
||||
|
||||
painter.drawPixmap(0, 0, image)
|
||||
|
||||
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
class SearchBar(QWidget): # {{{
|
||||
|
||||
def __init__(self, parent):
|
||||
QWidget.__init__(self, parent)
|
||||
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.setMaximumSize(QSize(150, 16777215))
|
||||
x.setObjectName("search_restriction")
|
||||
x.setToolTip(_("Books display will be restricted to those matching the selected saved search"))
|
||||
l.addWidget(x)
|
||||
parent.search_restriction = x
|
||||
|
||||
x = QLabel(self)
|
||||
x.setObjectName("search_count")
|
||||
l.addWidget(x)
|
||||
parent.search_count = x
|
||||
x.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
|
||||
|
||||
parent.advanced_search_button = x = QToolButton(self)
|
||||
x.setIcon(QIcon(I('search.svg')))
|
||||
l.addWidget(x)
|
||||
x.setToolTip(_("Advanced search"))
|
||||
|
||||
self.label = x = QLabel('&Search:')
|
||||
l.addWidget(self.label)
|
||||
x.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
|
||||
|
||||
x = parent.search = SearchBox2(self)
|
||||
x.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
|
||||
x.setObjectName("search")
|
||||
x.setToolTip(_("<p>Search the list of books by title, author, publisher, tags, comments, etc.<br><br>Words separated by spaces are ANDed"))
|
||||
l.addWidget(x)
|
||||
|
||||
x = parent.clear_button = QToolButton(self)
|
||||
x.setIcon(QIcon(I('clear_left.svg')))
|
||||
x.setObjectName("clear_button")
|
||||
l.addWidget(x)
|
||||
x.setToolTip(_("Reset Quick Search"))
|
||||
|
||||
x = parent.saved_search = SavedSearchBox(self)
|
||||
x.setMaximumSize(QSize(150, 16777215))
|
||||
x.setMinimumContentsLength(15)
|
||||
x.setObjectName("saved_search")
|
||||
l.addWidget(x)
|
||||
|
||||
x = parent.copy_search_button = QToolButton(self)
|
||||
x.setIcon(QIcon(I("search_copy_saved.svg")))
|
||||
x.setObjectName("copy_search_button")
|
||||
l.addWidget(x)
|
||||
x.setToolTip(_("Copy current search text (instead of search name)"))
|
||||
|
||||
x = parent.save_search_button = QToolButton(self)
|
||||
x.setIcon(QIcon(I("search_add_saved.svg")))
|
||||
x.setObjectName("save_search_button")
|
||||
l.addWidget(x)
|
||||
x.setToolTip(_("Save current search under the name shown in the box"))
|
||||
|
||||
x = parent.delete_search_button = QToolButton(self)
|
||||
x.setIcon(QIcon(I("search_delete_saved.svg")))
|
||||
x.setObjectName("delete_search_button")
|
||||
l.addWidget(x)
|
||||
x.setToolTip(_("Delete current saved search"))
|
||||
|
||||
self.label.setBuddy(parent.search)
|
||||
self.restriction_label.setBuddy(parent.search_restriction)
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
class LocationBar(ToolBar): # {{{
|
||||
|
||||
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))
|
||||
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())
|
||||
|
||||
return b
|
||||
# }}}
|
||||
|
||||
class MainWindowMixin(object):
|
||||
|
||||
def __init__(self):
|
||||
self.setObjectName('MainWindow')
|
||||
self.setWindowIcon(QIcon(I('library.png')))
|
||||
self.setWindowTitle(__appname__)
|
||||
|
||||
self.setContextMenuPolicy(Qt.NoContextMenu)
|
||||
self.centralwidget = QWidget(self)
|
||||
self.setCentralWidget(self.centralwidget)
|
||||
self._central_widget_layout = QVBoxLayout()
|
||||
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)
|
||||
|
||||
# Actions {{{
|
||||
|
||||
def ac(name, text, icon, shortcut=None, tooltip=None):
|
||||
action = QAction(QIcon(I(icon)), text, self)
|
||||
text = tooltip if tooltip else text
|
||||
action.setToolTip(text)
|
||||
action.setStatusTip(text)
|
||||
action.setWhatsThis(text)
|
||||
action.setAutoRepeat(False)
|
||||
action.setObjectName('action_'+name)
|
||||
if shortcut:
|
||||
action.setShortcut(shortcut)
|
||||
setattr(self, 'action_'+name, 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'),
|
||||
'document_open.svg')
|
||||
ac('show_book_details', _('Show book details'),
|
||||
'dialog_information.svg')
|
||||
ac('books_by_same_author', _('Books by same author'),
|
||||
'user_profile.svg')
|
||||
ac('books_in_this_series', _('Books in this series'),
|
||||
'books_in_series.svg')
|
||||
ac('books_by_this_publisher', _('Books by this publisher'),
|
||||
'publisher.png')
|
||||
ac('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)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
@ -492,6 +492,11 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
def title(self, row_number):
|
||||
return self.db.title(row_number)
|
||||
|
||||
def rating(self, row_number):
|
||||
ans = self.db.rating(row_number)
|
||||
ans = ans/2 if ans else 0
|
||||
return int(ans)
|
||||
|
||||
def cover(self, row_number):
|
||||
data = None
|
||||
try:
|
||||
@ -937,6 +942,7 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
cname = self.column_map[index.column()]
|
||||
if cname in ('title', 'authors') or \
|
||||
(cname == 'collections' and \
|
||||
callable(getattr(self.db, 'supports_collections', None)) and \
|
||||
self.db.supports_collections() and \
|
||||
prefs['preserve_user_collections']):
|
||||
flags |= Qt.ItemIsEditable
|
||||
|
@ -501,6 +501,7 @@ class DeviceBooksView(BooksView): # {{{
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
self.edit_collections_menu.setVisible(
|
||||
callable(getattr(self._model.db, 'supports_collections', None)) and \
|
||||
self._model.db.supports_collections() and \
|
||||
prefs['preserve_user_collections'])
|
||||
self.context_menu.popup(event.globalPos())
|
||||
|
@ -1,569 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<author>Kovid Goyal</author>
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1012</width>
|
||||
<height>822</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::NoContextMenu</enum>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>__appname__</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="LocationView" name="location_view">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>75</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="mouseTracking">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="tabKeyNavigation">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="showDropIndicator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="movement">
|
||||
<enum>QListView::Static</enum>
|
||||
</property>
|
||||
<property name="flow">
|
||||
<enum>QListView::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="gridSize">
|
||||
<size>
|
||||
<width>175</width>
|
||||
<height>90</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="viewMode">
|
||||
<enum>QListView::ListMode</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="donate_button">
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/donate.svg</normaloff>:/images/donate.svg</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="vanity">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>90</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="hl234">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="restriction_label">
|
||||
<property name="text">
|
||||
<string>&Restrict to:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>search_restriction</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="search_restriction">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Books display will be restricted to those matching the selected saved search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="search_count">
|
||||
<property name="text">
|
||||
<string>set in ui.py</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="advanced_search_button">
|
||||
<property name="toolTip">
|
||||
<string>Advanced search</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/search.svg</normaloff>:/images/search.svg</iconset>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Alt+S</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>&Search:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>search</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="SearchBox2" name="search">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>700</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><p>Search the list of books by title, author, publisher, tags, comments, etc.<br><br>Words separated by spaces are ANDed</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><p>Search the list of books by title, author, publisher, tags, comments, etc.<br><br>Words separated by spaces are ANDed</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="clear_button">
|
||||
<property name="toolTip">
|
||||
<string>Reset Quick Search</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/clear_left.svg</normaloff>:/images/clear_left.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="SavedSearchBox" name="saved_search">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Choose saved search or enter name for new saved search</string>
|
||||
</property>
|
||||
<property name="minimumContentsLength">
|
||||
<number>15</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="copy_search_button">
|
||||
<property name="toolTip">
|
||||
<string>Copy current search text (instead of search name)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/search_copy_saved.svg</normaloff>:/images/search_copy_saved.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="save_search_button">
|
||||
<property name="toolTip">
|
||||
<string>Save current search under the name shown in the box</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/search_add_saved.svg</normaloff>:/images/search_add_saved.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="delete_search_button">
|
||||
<property name="toolTip">
|
||||
<string>Delete current saved search</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/search_delete_saved.svg</normaloff>:/images/search_delete_saved.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="tool_bar">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::PreventContextMenu</enum>
|
||||
</property>
|
||||
<property name="movable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="action_add"/>
|
||||
<addaction name="action_edit"/>
|
||||
<addaction name="action_convert"/>
|
||||
<addaction name="action_view"/>
|
||||
<addaction name="action_news"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_sync"/>
|
||||
<addaction name="action_save"/>
|
||||
<addaction name="action_del"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_preferences"/>
|
||||
</widget>
|
||||
<action name="action_add">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/add_book.svg</normaloff>:/images/add_book.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add books</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>A</string>
|
||||
</property>
|
||||
<property name="autoRepeat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_del">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/trash.svg</normaloff>:/images/trash.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove books</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Remove books</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Del</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_edit">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/edit_input.svg</normaloff>:/images/edit_input.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Edit meta information</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>E</string>
|
||||
</property>
|
||||
<property name="autoRepeat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_merge">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/merge_books.svg</normaloff>:/images/merge_books.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Merge book records</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>M</string>
|
||||
</property>
|
||||
<property name="autoRepeat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_sync">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/sync.svg</normaloff>:/images/sync.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Send to device</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_save">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/save.svg</normaloff>:/images/save.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save to disk</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>S</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_news">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/news.svg</normaloff>:/images/news.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Fetch news</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_convert">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/convert.svg</normaloff>:/images/convert.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Convert E-books</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>C</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_view">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/view.svg</normaloff>:/images/view.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>View</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>V</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_open_containing_folder">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/document_open.svg</normaloff>:/images/document_open.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Open containing folder</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_show_book_details">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/dialog_information.svg</normaloff>:/images/dialog_information.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show book details</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_books_by_same_author">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/user_profile.svg</normaloff>:/images/user_profile.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Books by same author</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_books_in_this_series">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/books_in_series.svg</normaloff>:/images/books_in_series.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Books in this series</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_books_by_this_publisher">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/publisher.png</normaloff>:/images/publisher.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Books by this publisher</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_books_with_the_same_tags">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/tags.svg</normaloff>:/images/tags.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Books with the same tags</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_preferences">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../resources/images.qrc">
|
||||
<normaloff>:/images/config.svg</normaloff>:/images/config.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preferences</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Configure calibre</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+P</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LocationView</class>
|
||||
<extends>QListView</extends>
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>SearchBox2</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>calibre.gui2.search_box</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>SavedSearchBox</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>calibre.gui2.search_box</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../resources/images.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
@ -579,12 +579,10 @@ void PictureFlowPrivate::resetSlides()
|
||||
|
||||
static QImage prepareSurface(QImage img, int w, int h)
|
||||
{
|
||||
Qt::TransformationMode mode = Qt::SmoothTransformation;
|
||||
img = img.scaled(w, h, Qt::IgnoreAspectRatio, mode);
|
||||
img = img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
// slightly larger, to accommodate for the reflection
|
||||
int hs = int(h * REFLECTION_FACTOR);
|
||||
int hofs = 0;
|
||||
|
||||
// offscreen buffer: black is sweet
|
||||
QImage result(hs, w, QImage::Format_RGB16);
|
||||
@ -595,21 +593,20 @@ static QImage prepareSurface(QImage img, int w, int h)
|
||||
// (and much better and faster to work row-wise, i.e in one scanline)
|
||||
for(int x = 0; x < w; x++)
|
||||
for(int y = 0; y < h; y++)
|
||||
result.setPixel(hofs + y, x, img.pixel(x, y));
|
||||
result.setPixel(y, x, img.pixel(x, y));
|
||||
|
||||
// create the reflection
|
||||
int ht = hs - h - hofs;
|
||||
int hte = ht;
|
||||
int ht = hs - h;
|
||||
for(int x = 0; x < w; x++)
|
||||
for(int y = 0; y < ht; y++)
|
||||
{
|
||||
QRgb color = img.pixel(x, img.height()-y-1);
|
||||
//QRgb565 color = img.scanLine(img.height()-y-1) + x*sizeof(QRgb565); //img.pixel(x, img.height()-y-1);
|
||||
int a = qAlpha(color);
|
||||
int r = qRed(color) * a / 256 * (hte - y) / hte * 3/5;
|
||||
int g = qGreen(color) * a / 256 * (hte - y) / hte * 3/5;
|
||||
int b = qBlue(color) * a / 256 * (hte - y) / hte * 3/5;
|
||||
result.setPixel(h+hofs+y, x, qRgb(r, g, b));
|
||||
int r = qRed(color) * a / 256 * (ht - y) / ht * 3/5;
|
||||
int g = qGreen(color) * a / 256 * (ht - y) / ht * 3/5;
|
||||
int b = qBlue(color) * a / 256 * (ht - y) / ht * 3/5;
|
||||
result.setPixel(h+y, x, qRgb(r, g, b));
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -709,9 +706,12 @@ void PictureFlowPrivate::render()
|
||||
painter.setPen(Qt::white);
|
||||
//painter.setPen(QColor(255,255,255,127));
|
||||
|
||||
if (centerIndex < slideCount() && centerIndex > -1)
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2-fontSize*3),
|
||||
if (centerIndex < slideCount() && centerIndex > -1) {
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2-fontSize*4),
|
||||
Qt::AlignCenter, slideImages->caption(centerIndex));
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2-fontSize*2),
|
||||
Qt::AlignCenter, slideImages->subtitle(centerIndex));
|
||||
}
|
||||
|
||||
painter.end();
|
||||
|
||||
@ -762,15 +762,22 @@ void PictureFlowPrivate::render()
|
||||
int sc = slideCount();
|
||||
|
||||
painter.setPen(QColor(255,255,255, (255-fade) ));
|
||||
if (leftTextIndex < sc && leftTextIndex > -1)
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - fontSize*3),
|
||||
if (leftTextIndex < sc && leftTextIndex > -1) {
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - fontSize*4),
|
||||
Qt::AlignCenter, slideImages->caption(leftTextIndex));
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - fontSize*2),
|
||||
Qt::AlignCenter, slideImages->subtitle(leftTextIndex));
|
||||
|
||||
}
|
||||
|
||||
painter.setPen(QColor(255,255,255, fade));
|
||||
if (leftTextIndex+1 < sc && leftTextIndex > -2)
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - fontSize*3),
|
||||
if (leftTextIndex+1 < sc && leftTextIndex > -2) {
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - fontSize*4),
|
||||
Qt::AlignCenter, slideImages->caption(leftTextIndex+1));
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - fontSize*2),
|
||||
Qt::AlignCenter, slideImages->subtitle(leftTextIndex+1));
|
||||
|
||||
}
|
||||
|
||||
painter.end();
|
||||
}
|
||||
@ -798,12 +805,20 @@ QRect PictureFlowPrivate::renderCenterSlide(const SlideInfo &slide) {
|
||||
int sw = src->height();
|
||||
int sh = src->width();
|
||||
int h = buffer.height();
|
||||
QRect rect(buffer.width()/2 - sw/2, 0, sw, h-1);
|
||||
int left = rect.left();
|
||||
int srcoff = 0;
|
||||
int left = buffer.width()/2 - sw/2;
|
||||
if (left < 0) {
|
||||
srcoff = -left;
|
||||
sw += left;
|
||||
left = 0;
|
||||
}
|
||||
QRect rect(left, 0, sw, h-1);
|
||||
int xcon = MIN(h-1, sh-1);
|
||||
int ycon = MIN(sw, buffer.width() - left);
|
||||
|
||||
for(int x = 0; x < MIN(h-1, sh-1); x++)
|
||||
for(int y = 0; y < sw; y++)
|
||||
buffer.setPixel(left + y, 1+x, src->pixel(x, y));
|
||||
for(int x = 0; x < xcon; x++)
|
||||
for(int y = 0; y < ycon; y++)
|
||||
buffer.setPixel(left + y, 1+x, src->pixel(x, srcoff+y));
|
||||
|
||||
return rect;
|
||||
}
|
||||
@ -1367,5 +1382,6 @@ void PictureFlow::emitcurrentChanged(int index) { emit currentChanged(index); }
|
||||
int FlowImages::count() { return 0; }
|
||||
QImage FlowImages::image(int index) { index=0; return QImage(); }
|
||||
QString FlowImages::caption(int index) {index=0; return QString(); }
|
||||
QString FlowImages::subtitle(int index) {index=0; return QString(); }
|
||||
|
||||
// }}}
|
||||
|
@ -67,6 +67,7 @@ public:
|
||||
virtual int count();
|
||||
virtual QImage image(int index);
|
||||
virtual QString caption(int index);
|
||||
virtual QString subtitle(int index);
|
||||
|
||||
signals:
|
||||
void dataChanged();
|
||||
|
@ -16,6 +16,7 @@ public:
|
||||
virtual int count();
|
||||
virtual QImage image(int index);
|
||||
virtual QString caption(int index);
|
||||
virtual QString subtitle(int index);
|
||||
|
||||
signals:
|
||||
void dataChanged();
|
||||
|
@ -6,6 +6,8 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import re
|
||||
|
||||
from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, \
|
||||
pyqtSignal, SIGNAL, QObject, QDialog, QCompleter, \
|
||||
QAction, QKeySequence
|
||||
@ -368,6 +370,10 @@ class SearchBoxMixin(object):
|
||||
self.action_focus_search.triggered.connect(lambda x:
|
||||
self.search.setFocus(Qt.OtherFocusReason))
|
||||
self.addAction(self.action_focus_search)
|
||||
self.search.setStatusTip(re.sub(r'<\w+>', ' ',
|
||||
unicode(self.search.toolTip())))
|
||||
self.advanced_search_button.setStatusTip(self.advanced_search_button.toolTip())
|
||||
self.clear_button.setStatusTip(self.clear_button.toolTip())
|
||||
|
||||
def search_box_cleared(self):
|
||||
self.tags_view.clear()
|
||||
@ -396,6 +402,12 @@ class SavedSearchBoxMixin(object):
|
||||
self.saved_search.delete_search_button_clicked)
|
||||
self.connect(self.copy_search_button, SIGNAL('clicked()'),
|
||||
self.saved_search.copy_search_button_clicked)
|
||||
self.saved_search.setToolTip(
|
||||
_('Choose saved search or enter name for new saved search'))
|
||||
self.saved_search.setStatusTip(self.saved_search.toolTip())
|
||||
for x in ('copy', 'save', 'delete'):
|
||||
b = getattr(self, x+'_search_button')
|
||||
b.setStatusTip(b.toolTip())
|
||||
|
||||
|
||||
def saved_searches_changed(self):
|
||||
|
@ -11,6 +11,7 @@ class SearchRestrictionMixin(object):
|
||||
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())
|
||||
|
||||
'''
|
||||
Adding and deleting books while restricted creates a complexity. When added,
|
||||
|
@ -768,6 +768,9 @@ class TagBrowserWidget(QWidget): # {{{
|
||||
for x in (_('Sort by name'), _('Sort by popularity'),
|
||||
_('Sort by average rating')):
|
||||
parent.sort_by.addItem(x)
|
||||
parent.sort_by.setToolTip(
|
||||
_('Set the sort order for entries in the Tag Browser'))
|
||||
parent.sort_by.setStatusTip(parent.sort_by.toolTip())
|
||||
parent.sort_by.setCurrentIndex(0)
|
||||
self._layout.addWidget(parent.sort_by)
|
||||
|
||||
@ -776,9 +779,16 @@ class TagBrowserWidget(QWidget): # {{{
|
||||
parent.tag_match.addItem(x)
|
||||
parent.tag_match.setCurrentIndex(0)
|
||||
self._layout.addWidget(parent.tag_match)
|
||||
parent.tag_match.setToolTip(
|
||||
_('When selecting multiple entries in the Tag Browser '
|
||||
'match any or all of them'))
|
||||
parent.tag_match.setStatusTip(parent.tag_match.toolTip())
|
||||
|
||||
parent.edit_categories = QPushButton(_('Manage &user categories'), parent)
|
||||
self._layout.addWidget(parent.edit_categories)
|
||||
parent.edit_categories.setToolTip(
|
||||
_('Add your own categories to the Tag Browser'))
|
||||
parent.edit_categories.setStatusTip(parent.edit_categories.toolTip())
|
||||
|
||||
|
||||
# }}}
|
||||
|
70
src/calibre/gui2/throbber.py
Normal file
@ -0,0 +1,70 @@
|
||||
#!/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'
|
||||
|
||||
|
||||
from PyQt4.Qt import QToolButton, QSize, QPropertyAnimation, Qt, \
|
||||
QMetaObject
|
||||
|
||||
from calibre.gui2 import config
|
||||
|
||||
class ThrobbingButton(QToolButton):
|
||||
|
||||
def __init__(self, *args):
|
||||
QToolButton.__init__(self, *args)
|
||||
self.animation = QPropertyAnimation(self, 'iconSize', self)
|
||||
self.animation.setDuration(60/72.*1000)
|
||||
self.animation.setLoopCount(4)
|
||||
self.normal_icon_size = QSize(64, 64)
|
||||
self.animation.valueChanged.connect(self.value_changed)
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
self.animation.finished.connect(self.animation_finished)
|
||||
|
||||
def set_normal_icon_size(self, w, h):
|
||||
self.normal_icon_size = QSize(w, h)
|
||||
self.setIconSize(self.normal_icon_size)
|
||||
self.setMinimumSize(self.sizeHint())
|
||||
|
||||
def animation_finished(self):
|
||||
self.setIconSize(self.normal_icon_size)
|
||||
|
||||
def enterEvent(self, ev):
|
||||
self.start_animation()
|
||||
|
||||
def leaveEvent(self, ev):
|
||||
self.stop_animation()
|
||||
|
||||
def value_changed(self, val):
|
||||
self.update()
|
||||
|
||||
def start_animation(self):
|
||||
if config['disable_animations']: return
|
||||
if self.animation.state() != self.animation.Stopped or not self.isVisible():
|
||||
return
|
||||
size = self.normal_icon_size.width()
|
||||
smaller = int(0.7 * size)
|
||||
self.animation.setStartValue(QSize(smaller, smaller))
|
||||
self.animation.setEndValue(self.normal_icon_size)
|
||||
QMetaObject.invokeMethod(self.animation, 'start', Qt.QueuedConnection)
|
||||
|
||||
def stop_animation(self):
|
||||
self.animation.stop()
|
||||
self.animation_finished()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt4.Qt import QApplication, QWidget, QHBoxLayout, QIcon
|
||||
app = QApplication([])
|
||||
w = QWidget()
|
||||
w.setLayout(QHBoxLayout())
|
||||
b = ThrobbingButton()
|
||||
b.setIcon(QIcon(I('donate.svg')))
|
||||
w.layout().addWidget(b)
|
||||
w.show()
|
||||
b.set_normal_icon_size(64, 64)
|
||||
b.start_animation()
|
||||
|
||||
app.exec_()
|
@ -221,6 +221,8 @@ def fetch_scheduled_recipe(arg):
|
||||
if lf.get('base_font_size', 0.0) != 0.0:
|
||||
recs.append(('base_font_size', lf['base_font_size'],
|
||||
OptionRecommendation.HIGH))
|
||||
recs.append(('keep_ligatures', lf['keep_ligatures'],
|
||||
OptionRecommendation.HIGH))
|
||||
|
||||
lr = load_defaults('lrf_output')
|
||||
if lr.get('header', False):
|
||||
|
@ -19,7 +19,7 @@ from PyQt4.Qt import Qt, SIGNAL, QObject, QTimer, \
|
||||
QMessageBox, QHelpEvent
|
||||
|
||||
from calibre import prints, patheq
|
||||
from calibre.constants import __version__, __appname__, isosx
|
||||
from calibre.constants import __appname__, isosx
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.utils.config import prefs, dynamic
|
||||
from calibre.utils.ipc.server import Server
|
||||
@ -31,7 +31,7 @@ from calibre.gui2.wizard import move_library
|
||||
from calibre.gui2.dialogs.scheduler import Scheduler
|
||||
from calibre.gui2.update import UpdateMixin
|
||||
from calibre.gui2.main_window import MainWindow
|
||||
from calibre.gui2.main_ui import Ui_MainWindow
|
||||
from calibre.gui2.layout import MainWindowMixin
|
||||
from calibre.gui2.device import DeviceMixin
|
||||
from calibre.gui2.jobs import JobManager, JobsDialog, JobsButton
|
||||
from calibre.gui2.dialogs.config import ConfigDialog
|
||||
@ -91,7 +91,7 @@ class SystemTrayIcon(QSystemTrayIcon): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
|
||||
class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{
|
||||
TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin,
|
||||
SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin,
|
||||
AnnotationsAction, AddAction, DeleteAction,
|
||||
@ -120,7 +120,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
|
||||
self.another_instance_wants_to_talk)
|
||||
self.check_messages_timer.start(1000)
|
||||
|
||||
Ui_MainWindow.__init__(self)
|
||||
MainWindowMixin.__init__(self)
|
||||
|
||||
# Jobs Button {{{
|
||||
self.job_manager = JobManager()
|
||||
@ -163,6 +163,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
|
||||
self.donate_action = self.system_tray_menu.addAction(
|
||||
QIcon(I('donate.svg')), _('&Donate to support calibre'))
|
||||
self.donate_button.setDefaultAction(self.donate_action)
|
||||
self.donate_button.setStatusTip(self.donate_button.toolTip())
|
||||
self.eject_action = self.system_tray_menu.addAction(
|
||||
QIcon(I('eject.svg')), _('&Eject connected device'))
|
||||
self.eject_action.setEnabled(False)
|
||||
@ -202,18 +203,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
|
||||
self.device_manager.umount_device)
|
||||
self.eject_action.triggered.connect(self.device_manager.umount_device)
|
||||
|
||||
####################### Vanity ########################
|
||||
self.vanity_template = _('<p>For help see the: <a href="%s">User Manual</a>'
|
||||
'<br>')%'http://calibre-ebook.com/user_manual'
|
||||
dv = os.environ.get('CALIBRE_DEVELOP_FROM', None)
|
||||
v = __version__
|
||||
if getattr(sys, 'frozen', False) and dv and os.path.abspath(dv) in sys.path:
|
||||
v += '*'
|
||||
self.vanity_template += _('<b>%s</b>: %s by <b>Kovid Goyal '
|
||||
'%%(version)s</b><br>%%(device)s</p>')%(__appname__, v)
|
||||
self.latest_version = ' '
|
||||
self.vanity.setText(self.vanity_template%dict(version=' ', device=' '))
|
||||
self.device_info = ' '
|
||||
#################### Update notification ###################
|
||||
UpdateMixin.__init__(self, opts)
|
||||
|
||||
####################### Setup Toolbar #####################
|
||||
@ -291,6 +281,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
|
||||
|
||||
self.read_settings()
|
||||
self.finalize_layout()
|
||||
self.donate_button.start_animation()
|
||||
|
||||
def resizeEvent(self, ev):
|
||||
MainWindow.resizeEvent(self, ev)
|
||||
@ -554,7 +545,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
|
||||
'''
|
||||
MSG = _('is the result of the efforts of many volunteers from all '
|
||||
'over the world. If you find it useful, please consider '
|
||||
'donating to support its development.')
|
||||
'donating to support its development. Your donation helps '
|
||||
'keep calibre development going.')
|
||||
HTML = u'''
|
||||
<html>
|
||||
<head>
|
||||
|
@ -49,12 +49,8 @@ class UpdateMixin(object):
|
||||
def update_found(self, version):
|
||||
os = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
||||
url = 'http://calibre-ebook.com/download_%s'%os
|
||||
self.latest_version = '<br>' + _('<span style="color:red; font-weight:bold">'
|
||||
'Latest version: <a href="%s">%s</a></span>')%(url, version)
|
||||
self.vanity.setText(self.vanity_template%\
|
||||
(dict(version=self.latest_version,
|
||||
device=self.device_info)))
|
||||
self.vanity.update()
|
||||
self.status_bar.new_version_available(version, url)
|
||||
|
||||
if config.get('new_version_notification') and \
|
||||
dynamic.get('update to version %s'%version, True):
|
||||
if question_dialog(self, _('Update available'),
|
||||
|
@ -243,7 +243,7 @@
|
||||
<action name="action_copy">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/convert.svg</normaloff>:/images/convert.svg</iconset>
|
||||
<normaloff>:/images/edit_copy.svg</normaloff>:/images/edit_copy.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Copy to clipboard</string>
|
||||
|
@ -5,26 +5,25 @@ Miscellaneous widgets used in the GUI
|
||||
'''
|
||||
import re, os, traceback
|
||||
|
||||
from PyQt4.Qt import QListView, QIcon, QFont, QLabel, QListWidget, \
|
||||
from PyQt4.Qt import QIcon, QFont, QLabel, QListWidget, \
|
||||
QListWidgetItem, QTextCharFormat, QApplication, \
|
||||
QSyntaxHighlighter, QCursor, QColor, QWidget, \
|
||||
QPixmap, QPalette, QSplitterHandle, QToolButton, \
|
||||
QPixmap, QSplitterHandle, QToolButton, \
|
||||
QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, \
|
||||
QRegExp, QSettings, QSize, QModelIndex, QSplitter, \
|
||||
QAbstractButton, QPainter, QLineEdit, QComboBox, \
|
||||
QRegExp, QSettings, QSize, QSplitter, \
|
||||
QPainter, QLineEdit, QComboBox, \
|
||||
QMenu, QStringListModel, QCompleter, QStringList, \
|
||||
QTimer, QRect
|
||||
|
||||
from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs
|
||||
|
||||
from calibre.gui2.filename_pattern_ui import Ui_Form
|
||||
from calibre import fit_image, human_readable
|
||||
from calibre import fit_image
|
||||
from calibre.utils.fonts import fontconfig
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.ebooks.metadata.meta import metadata_from_filename
|
||||
from calibre.utils.config import prefs, XMLConfig
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator
|
||||
from calibre.constants import filesystem_encoding
|
||||
|
||||
history = XMLConfig('history')
|
||||
|
||||
@ -259,189 +258,6 @@ class ImageView(QWidget):
|
||||
# }}}
|
||||
|
||||
|
||||
class LocationModel(QAbstractListModel):
|
||||
|
||||
def __init__(self, parent):
|
||||
QAbstractListModel.__init__(self, parent)
|
||||
self.icons = [QVariant(QIcon(I('library.png'))),
|
||||
QVariant(QIcon(I('reader.svg'))),
|
||||
QVariant(QIcon(I('sd.svg'))),
|
||||
QVariant(QIcon(I('sd.svg')))]
|
||||
self.text = [_('Library\n%d\nbooks'),
|
||||
_('Reader\n%s\navailable'),
|
||||
_('Card A\n%s\navailable'),
|
||||
_('Card B\n%s\navailable')]
|
||||
self.free = [-1, -1, -1]
|
||||
self.count = 0
|
||||
self.highlight_row = 0
|
||||
self.library_tooltip = _('Click to see the books available on your computer')
|
||||
self.tooltips = [
|
||||
self.library_tooltip,
|
||||
_('Click to see the books in the main memory of your reader'),
|
||||
_('Click to see the books on storage card A in your reader'),
|
||||
_('Click to see the books on storage card B in your reader')
|
||||
]
|
||||
|
||||
def database_changed(self, db):
|
||||
lp = db.library_path
|
||||
if not isinstance(lp, unicode):
|
||||
lp = lp.decode(filesystem_encoding, 'replace')
|
||||
self.tooltips[0] = self.library_tooltip + '\n\n' + \
|
||||
_('Books located at') + ' ' + lp
|
||||
self.dataChanged.emit(self.index(0), self.index(0))
|
||||
|
||||
def rowCount(self, *args):
|
||||
return 1 + len([i for i in self.free if i >= 0])
|
||||
|
||||
def get_device_row(self, row):
|
||||
if row == 2 and self.free[1] == -1 and self.free[2] > -1:
|
||||
row = 3
|
||||
return row
|
||||
|
||||
def data(self, index, role):
|
||||
row = index.row()
|
||||
drow = self.get_device_row(row)
|
||||
data = NONE
|
||||
if role == Qt.DisplayRole:
|
||||
text = self.text[drow]%(human_readable(self.free[drow-1])) if row > 0 \
|
||||
else self.text[drow]%self.count
|
||||
data = QVariant(text)
|
||||
elif role == Qt.DecorationRole:
|
||||
data = self.icons[drow]
|
||||
elif role == Qt.ToolTipRole:
|
||||
data = QVariant(self.tooltips[drow])
|
||||
elif role == Qt.SizeHintRole:
|
||||
data = QVariant(QSize(155, 90))
|
||||
elif role == Qt.FontRole:
|
||||
font = QFont('monospace')
|
||||
font.setBold(row == self.highlight_row)
|
||||
data = QVariant(font)
|
||||
elif role == Qt.ForegroundRole and row == self.highlight_row:
|
||||
return QVariant(QApplication.palette().brush(
|
||||
QPalette.HighlightedText))
|
||||
elif role == Qt.BackgroundRole and row == self.highlight_row:
|
||||
return QVariant(QApplication.palette().brush(
|
||||
QPalette.Highlight))
|
||||
|
||||
return data
|
||||
|
||||
def device_connected(self, dev):
|
||||
self.icons[1] = QIcon(dev.icon)
|
||||
self.dataChanged.emit(self.index(1), self.index(1))
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
return NONE
|
||||
|
||||
def update_devices(self, cp=(None, None), fs=[-1, -1, -1]):
|
||||
if cp is None:
|
||||
cp = (None, None)
|
||||
if isinstance(cp, (str, unicode)):
|
||||
cp = (cp, None)
|
||||
if len(fs) < 3:
|
||||
fs = list(fs) + [0]
|
||||
self.free[0] = fs[0]
|
||||
self.free[1] = fs[1]
|
||||
self.free[2] = fs[2]
|
||||
cpa, cpb = cp
|
||||
self.free[1] = fs[1] if fs[1] is not None and cpa is not None else -1
|
||||
self.free[2] = fs[2] if fs[2] is not None and cpb is not None else -1
|
||||
self.reset()
|
||||
self.emit(SIGNAL('devicesChanged()'))
|
||||
|
||||
def location_changed(self, row):
|
||||
self.highlight_row = row
|
||||
self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'),
|
||||
self.index(0), self.index(self.rowCount(QModelIndex())-1))
|
||||
|
||||
def location_for_row(self, row):
|
||||
if row == 0: return 'library'
|
||||
if row == 1: return 'main'
|
||||
if row == 3: return 'cardb'
|
||||
return 'carda' if self.free[1] > -1 else 'cardb'
|
||||
|
||||
class LocationView(QListView):
|
||||
|
||||
def __init__(self, parent):
|
||||
QListView.__init__(self, parent)
|
||||
self.setModel(LocationModel(self))
|
||||
self.reset()
|
||||
self.currentChanged = self.current_changed
|
||||
|
||||
self.eject_button = EjectButton(self)
|
||||
self.eject_button.hide()
|
||||
|
||||
self.connect(self, SIGNAL('entered(QModelIndex)'), self.item_entered)
|
||||
self.connect(self, SIGNAL('viewportEntered()'), self.viewport_entered)
|
||||
self.connect(self.eject_button, SIGNAL('clicked()'), lambda: self.emit(SIGNAL('umount_device()')))
|
||||
self.connect(self.model(), SIGNAL('devicesChanged()'), self.eject_button.hide)
|
||||
|
||||
def count_changed(self, new_count):
|
||||
self.model().count = new_count
|
||||
self.model().reset()
|
||||
|
||||
def current_changed(self, current, previous):
|
||||
if current.isValid():
|
||||
i = current.row()
|
||||
location = self.model().location_for_row(i)
|
||||
self.emit(SIGNAL('location_selected(PyQt_PyObject)'), location)
|
||||
self.model().location_changed(i)
|
||||
|
||||
def location_changed(self, row):
|
||||
if 0 <= row and row <= 3:
|
||||
self.model().location_changed(row)
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.unsetCursor()
|
||||
self.eject_button.hide()
|
||||
|
||||
def item_entered(self, location):
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
self.eject_button.hide()
|
||||
|
||||
if location.row() == 1:
|
||||
rect = self.visualRect(location)
|
||||
|
||||
self.eject_button.resize(rect.height()/2, rect.height()/2)
|
||||
|
||||
x, y = rect.left(), rect.top()
|
||||
x = x + (rect.width() - self.eject_button.width() - 2)
|
||||
y += 6
|
||||
|
||||
self.eject_button.move(x, y)
|
||||
self.eject_button.show()
|
||||
|
||||
def viewport_entered(self):
|
||||
self.unsetCursor()
|
||||
self.eject_button.hide()
|
||||
|
||||
|
||||
class EjectButton(QAbstractButton):
|
||||
|
||||
def __init__(self, parent):
|
||||
QAbstractButton.__init__(self, parent)
|
||||
self.mouse_over = False
|
||||
|
||||
def enterEvent(self, event):
|
||||
self.mouse_over = True
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.mouse_over = False
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
painter.setClipRect(event.rect())
|
||||
image = QPixmap(I('eject')).scaledToHeight(event.rect().height(),
|
||||
Qt.SmoothTransformation)
|
||||
|
||||
if not self.mouse_over:
|
||||
alpha_mask = QPixmap(image.width(), image.height())
|
||||
color = QColor(128, 128, 128)
|
||||
alpha_mask.fill(color)
|
||||
image.setAlphaChannel(alpha_mask)
|
||||
|
||||
painter.drawPixmap(0, 0, image)
|
||||
|
||||
|
||||
|
||||
|
||||
class FontFamilyModel(QAbstractListModel):
|
||||
@ -1006,12 +822,14 @@ class LayoutButton(QToolButton):
|
||||
label =_('Show')
|
||||
self.setText(label + ' ' + self.label)
|
||||
self.setToolTip(self.text())
|
||||
self.setStatusTip(self.text())
|
||||
|
||||
def set_state_to_hide(self, *args):
|
||||
self.setChecked(True)
|
||||
label = _('Hide')
|
||||
self.setText(label + ' ' + self.label)
|
||||
self.setToolTip(self.text())
|
||||
self.setStatusTip(self.text())
|
||||
|
||||
def update_state(self, *args):
|
||||
if self.splitter.is_side_index_hidden:
|
||||
|
@ -29,6 +29,7 @@ entry_points = {
|
||||
'calibre-complete = calibre.utils.complete:main',
|
||||
'pdfmanipulate = calibre.ebooks.pdf.manipulate.cli:main',
|
||||
'fetch-ebook-metadata = calibre.ebooks.metadata.fetch:main',
|
||||
'epub-fix = calibre.ebooks.epub.fix.main:main',
|
||||
'calibre-smtp = calibre.utils.smtp:main',
|
||||
],
|
||||
'gui_scripts' : [
|
||||
@ -180,6 +181,7 @@ class PostInstall:
|
||||
from calibre.ebooks.metadata.fetch import option_parser as fem_op
|
||||
from calibre.gui2.main import option_parser as guiop
|
||||
from calibre.utils.smtp import option_parser as smtp_op
|
||||
from calibre.ebooks.epub.fix.main import option_parser as fix_op
|
||||
any_formats = ['epub', 'htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip',
|
||||
'txt', 'lit', 'rtf', 'pdf', 'prc', 'mobi', 'fb2', 'odt', 'lrf']
|
||||
bc = os.path.join(os.path.dirname(self.opts.staging_sharedir),
|
||||
@ -201,6 +203,7 @@ class PostInstall:
|
||||
f.write(opts_and_exts('ebook-viewer', viewer_op, any_formats))
|
||||
f.write(opts_and_words('fetch-ebook-metadata', fem_op, []))
|
||||
f.write(opts_and_words('calibre-smtp', smtp_op, []))
|
||||
f.write(opts_and_exts('epub-fix', fix_op, ['epub']))
|
||||
f.write(textwrap.dedent('''
|
||||
_ebook_device_ls()
|
||||
{
|
||||
|
@ -504,7 +504,7 @@ Meaning, it is very difficult to determine where one paragraph ends and another
|
||||
paragraphs using a configurable, :guilabel:`Line Un-Wrapping Factor`. This is a scale used to determine the length
|
||||
at which a line should be unwrapped. Valid values are a decimal
|
||||
between 0 and 1. The default is 0.5, this is the median line length. Lower this value to include more
|
||||
text in the unwrapping. Increase to include less.
|
||||
text in the unwrapping. Increase to include less. You can adjust this value in the conversion settings under PDF Input.
|
||||
|
||||
Also, they often have headers and footers as part of the document that will become included with the text.
|
||||
Use the options to remove headers and footers to mitigate this issue. If the headers and footers are not
|
||||
|