merged trunk

This commit is contained in:
Fabian Graßl 2010-10-14 18:19:15 +02:00
commit eadf98bfd0
128 changed files with 27845 additions and 16898 deletions

View File

@ -4,6 +4,85 @@
# for important features/bug fixes. # for important features/bug fixes.
# Also, each release can have new and improved recipes. # Also, each release can have new and improved recipes.
- version: 0.7.23
date: 2010-10-08
new features:
- title: "Drag and drop to Tag Browser. You can use this to conveniently add tags, set series/publisher etc for a group of books"
- title: "Allow switching of library even when a device is connected"
- title: "Support for the PD Novel running Kobo"
- title: "Move check library integrity from preferences to drop down menu accessed by clicking arrow next to calibre icon"
- title: "Nicer, non-blocking update available notification"
- title: "E-book viewer: If you choose to remeber last used window size, the state of the Table of Contents view is also remembered"
tickets: [7082]
- title: "Allow moving as well as copying of books to another library"
- title: "Apple devices: Add support for plugboards"
- title: "Allow DJVU to be sent to the DR1000"
bug fixes:
- title: "Searching: Fix search expression parser to allow use of escaped double quotes in the search expression"
- title: "When saving cover images don't re-encode the image data unless absolutely neccessary. This prevents information loss due to JPEG re-compression"
- title: "Fix regression that broke setting of metadata for some MOBI/AZW/PRC files"
- title: "Fix regression in last release that could cause download of metadata for multiple files to only download the metadata for a few of them"
tickets: [7071]
- title: "MOBI Output: More tweaking of the margin handling to yield results closer to the input document."
- title: "Device drivers: Fix regression that could cause geenration of invalid metadata.calibre cache files"
- title: "Fix saving to disk with ISBN in filename"
tickets: [7090]
- title: "Fix another regression in the ISBNdb.com metadata download plugin"
- title: "Fix dragging to not interfere with multi-selection. Also dont allow drag and drop from the library to itself"
- title: "CHM input: handle another class of broken CHM files"
tickets: [7058]
new recipes:
- title: "Communications of the Association for Computing Machinery"
author: jonmisurda
- title: "Anand Tech"
author: "Oliver Niesner"
- title: "gsp.ro"
author: "bucsie"
- title: "Il Fatto Quotidiano"
author: "egilh"
- title: "Serbian Literature blog and Rusia Hoy"
author: "Darko Miletic"
- title: "Medscape"
author: "Tony Stegall"
improved recipes:
- The Age
- Australian
- Wiki news
- Times Online
- New Yorker
- Guardian
- Sueddeutsche
- HNA
- Revista Muy Interesante
- version: 0.7.22 - version: 0.7.22
date: 2010-10-03 date: 2010-10-03

View File

@ -0,0 +1,262 @@
body {
background-color: #fffcf2;
font-family: sans-serif;
margin: 0 0 0 0;
padding: 0 0 0 0;
}
#container {
max-width: 1000px;
min-width: 400px;
min-height: 230px;
background-color: #F6F3E9;
margin-left: auto;
margin-right: auto;
-moz-box-shadow: 5px 5px 5px #ccc;
-webkit-box-shadow: 5px 5px 5px #ccc;
box-shadow: 5px 5px 5px #ccc;
}
#header {
height: 130px;
background: url('/static/logo.png') no-repeat scroll left top #39322b;
}
#content {
max-width: 1000px;
min-width: 400px;
min-height: 100px;
}
#main {
padding-left: 0.5em;
padding-right: 0.5em;
}
#footer {
font-size: small;
color: #a6a399;
text-align: right;
margin-right: 30px;
}
/* Header {{{ */
#header .area {
width: 300px;
height: 130px;
position: relative;
margin-left: 230px;
color: white;
font-size: xx-large;
font-family: monospace;
overflow: hidden;
z-index: 2;
}
#header .bubble {
position: absolute;
left: 93px;
top: 21px;
width: 135px;
height: 84px;
display: table;
}
#header .bubble p {
display: table-cell;
vertical-align: middle;
text-align: center;
}
#header a {
text-decoration: none;
color: white;
cursor: pointer;
white-space: nowrap;
text-shadow: #27211b 2px 2px 2px;
}
#header a:hover {
background-color: #39a9cf;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
text-shadow: #27211b 1px 1px 1px;
-moz-box-shadow: 5px 5px 5px #222;
-webkit-box-shadow: 5px 5px 5px #222;
box-shadow: 5px 5px 5px #222;
}
#nav-container {
position: relative;
height: 130px;
top: -130px;
left: 0%;
}
ul#primary-nav {
display: block;
margin-right: 60px;
text-align: right;
margin-top: 90px;
line-height: 20px;
cursor: default;
position: relative;
top: -2ex;
}
ul#primary-nav li {
display: inline;
padding: 0 4px;
}
ul#primary-nav li a {
padding: 6px;
font-variant: small-caps;
font-weight: bold;
white-space: nowrap;
}
#donate {
display: block;
width: 200px;
height: 38px;
overflow: hidden;
position: relative;
top: -260px;
left: 65%;
}
#calibre-home-link {
position: relative;
top: -298px;
left: 0%;
z-index: 2;
height: 130px;
width: 230px;
cursor: pointer;
}
h2.library_name {
font-family: monospace;
font-size: 50px;
font-weight: normal;
color: white;
margin-left: 250px;
padding-top: 40px;
}
/* }}} */
/* Sort select {{{ */
.sort_select {
float: left;
margin-left: 1em;
margin-top: 2ex;
max-height: 2.75em;
overflow: hidden;
}
.sort_select label {
font-size: medium;
}
/* }}} */
/* Search bar {{{ */
#search_box {
float: right;
margin-right: 1em;
margin-top: 2ex;
max-height: 2.75em;
overflow: hidden;
}
/* }}} */
/* Top level {{{ */
.toplevel ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.toplevel li {
margin: 0.75em;
padding: 0.75em;
text-align: center;
cursor: pointer;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
}
.toplevel li:hover {
background-color: #d6d3c9;
font-weight: bold;
-moz-box-shadow: 5px 5px 5px #ccc;
-webkit-box-shadow: 5px 5px 5px #ccc;
box-shadow: 5px 5px 5px #ccc;
}
.toplevel li span { display: none }
/* }}} */
/* Category {{{ */
.category > div.category-container {
width: 100%;
margin-top: 1ex;
margin-bottom: 1ex;
display: table;
}
.category div.category-item {
display: table-row;
}
.category div.category-item > div {
padding: 0.75em;
text-align: center;
cursor: pointer;
display: table-cell;
}
.category div.category-name { font-weight: bold }
.category div.category-item:hover > div {
background-color: #d6d3c9;
-moz-box-shadow: 5px 5px 5px #ccc;
-webkit-box-shadow: 5px 5px 5px #ccc;
box-shadow: 5px 5px 5px #ccc;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
}
.category div.category-item span.href { display: none }
#groups span.load_href { display: none }
#groups h3 {
font-weight: bold;
margin-top: 1ex;
padding-left: 30px;
padding-top: 0.5ex;
padding-bottom: 0.5ex;
}
#groups h3 span {
font-weight: normal
}
/* }}} */

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>..:: calibre library ::.. {title}</title>
<meta http-equiv="X-UA-Compatible" content="IE=100" />
<link rel="icon" type="image/x-icon" href="http://calibre-ebook.com/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/static/browse/browse.css" />
<link type="text/css" href="/static/jquery_ui/css/humanity-custom/jquery-ui-1.8.5.custom.css" rel="stylesheet" />
<link rel="stylesheet" type="text/css" href="/static/jquery.multiselect.css" />
<script type="text/javascript" src="/static/jquery.js"></script>
<script type="text/javascript" src="/static/jquery.corner.js"></script>
<script type="text/javascript"
src="/static/jquery_ui/js/jquery-ui-1.8.5.custom.min.js"></script>
<script type="text/javascript"
src="/static/jquery.multiselect.min.js"></script>
<script type="text/javascript" src="/static/browse/browse.js"></script>
<script type="text/javascript">
var sort_cookie_name = "{sort_cookie_name}";
var sort_select_label = "{sort_select_label}";
$(document).ready(function() {{
init();
{script}
}});
</script>
</head>
<body>
<div id="container">
<!-- Header -->
<div id="header">
<div class="area">
<div class="bubble">
<p><a href="/browse" title="Return to top level"
>&rarr;&nbsp;home&nbsp;&larr;</a></p>
</div>
</div>
<div id="nav-container">&nbsp;
<ul id="primary-nav">
<li><a id="nav-mobile" href="/mobile" title="A version of this website suited for mobile browsers">Mobile</a></li>
<li><a id="nav-demo" href="/old" title="The old version of this webiste">Old</a></li>
<li><a id="nav-download" href="/opds" title="An OPDS feed based version of this website, used in special purpose applications">Feed</a></li>
</ul>
</div>
<form id="donate" action="https://www.paypal.com/cgi-bin/webscr"
method="post" title="Donate to support the development of calibre">
<div>
<input type="hidden" name="cmd" value="_s-xclick"></input>
<input type="hidden" name="hosted_button_id" value="3028915"></input>
<input type="image"
src="http://calibre-ebook.com/site_media//img/button-donate.png"
name="submit"></input>
<img alt="" src="https://www.paypal.com/en_US/i/scr/pixel.gif"
width="1" height="1"></img>
</div>
</form>
<div id="calibre-home-link" title="Go to the calibre website"></div>
</div>
<!-- End header -->
<div id="content">
<div class="sort_select">
<label>{sort_select_label}</label>
<select id="sort_combobox">
{sort_select_options}
</select>
</div>
<div id="search_box">
<form name="search_form" action="/browse/search" method="get">
<input value="" type="text" title="Search"
class="search_input" />&nbsp;
<input type="submit" value="Search" title="Search" alt="Search" />
</form>
</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div id="main">
{main}
</div>
<div id="footer">
[{library_path}] Created by Kovid Goyal
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,135 @@
// Cookies {{{
function cookie(name, value, options) {
if (typeof value != 'undefined') { // name and value given, set cookie
options = options || {};
if (value === null) {
value = '';
options.expires = -1;
}
var expires = '';
if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
var date;
if (typeof options.expires == 'number') {
date = new Date();
date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
} else {
date = options.expires;
}
expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
}
// CAUTION: Needed to parenthesize options.path and options.domain
// in the following expressions, otherwise they evaluate to undefined
// in the packed version for some reason...
var path = options.path ? '; path=' + (options.path) : '';
var domain = options.domain ? '; domain=' + (options.domain) : '';
var secure = options.secure ? '; secure' : '';
document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
} else { // only name given, get cookie
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
};
// }}}
// Sort {{{
function init_sort_combobox() {
$("#sort_combobox").multiselect({
multiple: false,
header: sort_select_label,
noneSelectedText: sort_select_label,
selectedList: 1,
click: function(event, ui){
$(this).multiselect("close");
cookie(sort_cookie_name, ui.value, {expires: 365});
window.location.reload();
}
});
}
// }}}
function init() {
$("#container").corner("30px");
$("#header").corner("30px");
$("#calibre-home-link").click(function() { window.location = "http://calibre-ebook.com"; });
init_sort_combobox();
$("#search_box input:submit").button();
}
// Top-level feed {{{
function toplevel() {
$(".sort_select").hide();
$(".toplevel li").click(function() {
var href = $(this).children("span").html();
window.location = href;
});
}
// }}}
function render_error(msg) {
return '<div class="ui-widget"><div class="ui-state-error ui-corner-all" style="padding: 0pt 0.7em"><p><span class="ui-icon ui-icon-alert" style="float: left; margin-right: 0.3em">&nbsp;</span><strong>Error: </strong>'+msg+"</p></div></div>"
}
// Category feed {{{
function category() {
$(".category .category-item").click(function() {
var href = $(this).find("span.href").html();
window.location = href;
});
$(".category a.navlink").button();
$("#groups").accordion({
collapsible: true,
active: false,
autoHeight: false,
clearStyle: true,
header: "h3",
change: function(event, ui) {
if (ui.newContent) {
var href = ui.newContent.children("span.load_href").html();
ui.newContent.children(".loading").show();
if (href) {
$.ajax({
url:href,
success: function(data) {
this.children(".loaded").html(data);
this.children(".loaded").show();
this.children(".loading").hide();
},
context: ui.newContent,
dataType: "json",
timeout: 600000, //milliseconds (10 minutes)
error: function(xhr, stat, err) {
this.children(".loaded").html(render_error(stat));
this.children(".loaded").show();
this.children(".loading").hide();
}
});
}
}
}
});
}
// }}}

View File

@ -0,0 +1,247 @@
/*!
* jQuery corner plugin: simple corner rounding
* Examples and documentation at: http://jquery.malsup.com/corner/
* version 2.11 (15-JUN-2010)
* Requires jQuery v1.3.2 or later
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
* Authors: Dave Methvin and Mike Alsup
*/
/**
* corner() takes a single string argument: $('#myDiv').corner("effect corners width")
*
* effect: name of the effect to apply, such as round, bevel, notch, bite, etc (default is round).
* corners: one or more of: top, bottom, tr, tl, br, or bl. (default is all corners)
* width: width of the effect; in the case of rounded corners this is the radius.
* specify this value using the px suffix such as 10px (yes, it must be pixels).
*/
;(function($) {
var style = document.createElement('div').style,
moz = style['MozBorderRadius'] !== undefined,
webkit = style['WebkitBorderRadius'] !== undefined,
radius = style['borderRadius'] !== undefined || style['BorderRadius'] !== undefined,
mode = document.documentMode || 0,
noBottomFold = $.browser.msie && (($.browser.version < 8 && !mode) || mode < 8),
expr = $.browser.msie && (function() {
var div = document.createElement('div');
try { div.style.setExpression('width','0+0'); div.style.removeExpression('width'); }
catch(e) { return false; }
return true;
})();
$.support = $.support || {};
$.support.borderRadius = moz || webkit || radius; // so you can do: if (!$.support.borderRadius) $('#myDiv').corner();
function sz(el, p) {
return parseInt($.css(el,p))||0;
};
function hex2(s) {
var s = parseInt(s).toString(16);
return ( s.length < 2 ) ? '0'+s : s;
};
function gpc(node) {
while(node) {
var v = $.css(node,'backgroundColor'), rgb;
if (v && v != 'transparent' && v != 'rgba(0, 0, 0, 0)') {
if (v.indexOf('rgb') >= 0) {
rgb = v.match(/\d+/g);
return '#'+ hex2(rgb[0]) + hex2(rgb[1]) + hex2(rgb[2]);
}
return v;
}
if (node.nodeName.toLowerCase() == 'html')
break;
node = node.parentNode; // keep walking if transparent
}
return '#ffffff';
};
function getWidth(fx, i, width) {
switch(fx) {
case 'round': return Math.round(width*(1-Math.cos(Math.asin(i/width))));
case 'cool': return Math.round(width*(1+Math.cos(Math.asin(i/width))));
case 'sharp': return Math.round(width*(1-Math.cos(Math.acos(i/width))));
case 'bite': return Math.round(width*(Math.cos(Math.asin((width-i-1)/width))));
case 'slide': return Math.round(width*(Math.atan2(i,width/i)));
case 'jut': return Math.round(width*(Math.atan2(width,(width-i-1))));
case 'curl': return Math.round(width*(Math.atan(i)));
case 'tear': return Math.round(width*(Math.cos(i)));
case 'wicked': return Math.round(width*(Math.tan(i)));
case 'long': return Math.round(width*(Math.sqrt(i)));
case 'sculpt': return Math.round(width*(Math.log((width-i-1),width)));
case 'dogfold':
case 'dog': return (i&1) ? (i+1) : width;
case 'dog2': return (i&2) ? (i+1) : width;
case 'dog3': return (i&3) ? (i+1) : width;
case 'fray': return (i%2)*width;
case 'notch': return width;
case 'bevelfold':
case 'bevel': return i+1;
}
};
$.fn.corner = function(options) {
// in 1.3+ we can fix mistakes with the ready state
if (this.length == 0) {
if (!$.isReady && this.selector) {
var s = this.selector, c = this.context;
$(function() {
$(s,c).corner(options);
});
}
return this;
}
return this.each(function(index){
var $this = $(this),
// meta values override options
o = [$this.attr($.fn.corner.defaults.metaAttr) || '', options || ''].join(' ').toLowerCase(),
keep = /keep/.test(o), // keep borders?
cc = ((o.match(/cc:(#[0-9a-f]+)/)||[])[1]), // corner color
sc = ((o.match(/sc:(#[0-9a-f]+)/)||[])[1]), // strip color
width = parseInt((o.match(/(\d+)px/)||[])[1]) || 10, // corner width
re = /round|bevelfold|bevel|notch|bite|cool|sharp|slide|jut|curl|tear|fray|wicked|sculpt|long|dog3|dog2|dogfold|dog/,
fx = ((o.match(re)||['round'])[0]),
fold = /dogfold|bevelfold/.test(o),
edges = { T:0, B:1 },
opts = {
TL: /top|tl|left/.test(o), TR: /top|tr|right/.test(o),
BL: /bottom|bl|left/.test(o), BR: /bottom|br|right/.test(o)
},
// vars used in func later
strip, pad, cssHeight, j, bot, d, ds, bw, i, w, e, c, common, $horz;
if ( !opts.TL && !opts.TR && !opts.BL && !opts.BR )
opts = { TL:1, TR:1, BL:1, BR:1 };
// support native rounding
if ($.fn.corner.defaults.useNative && fx == 'round' && (radius || moz || webkit) && !cc && !sc) {
if (opts.TL)
$this.css(radius ? 'border-top-left-radius' : moz ? '-moz-border-radius-topleft' : '-webkit-border-top-left-radius', width + 'px');
if (opts.TR)
$this.css(radius ? 'border-top-right-radius' : moz ? '-moz-border-radius-topright' : '-webkit-border-top-right-radius', width + 'px');
if (opts.BL)
$this.css(radius ? 'border-bottom-left-radius' : moz ? '-moz-border-radius-bottomleft' : '-webkit-border-bottom-left-radius', width + 'px');
if (opts.BR)
$this.css(radius ? 'border-bottom-right-radius' : moz ? '-moz-border-radius-bottomright' : '-webkit-border-bottom-right-radius', width + 'px');
return;
}
strip = document.createElement('div');
$(strip).css({
overflow: 'hidden',
height: '1px',
minHeight: '1px',
fontSize: '1px',
backgroundColor: sc || 'transparent',
borderStyle: 'solid'
});
pad = {
T: parseInt($.css(this,'paddingTop'))||0, R: parseInt($.css(this,'paddingRight'))||0,
B: parseInt($.css(this,'paddingBottom'))||0, L: parseInt($.css(this,'paddingLeft'))||0
};
if (typeof this.style.zoom != undefined) this.style.zoom = 1; // force 'hasLayout' in IE
if (!keep) this.style.border = 'none';
strip.style.borderColor = cc || gpc(this.parentNode);
cssHeight = $(this).outerHeight();
for (j in edges) {
bot = edges[j];
// only add stips if needed
if ((bot && (opts.BL || opts.BR)) || (!bot && (opts.TL || opts.TR))) {
strip.style.borderStyle = 'none '+(opts[j+'R']?'solid':'none')+' none '+(opts[j+'L']?'solid':'none');
d = document.createElement('div');
$(d).addClass('jquery-corner');
ds = d.style;
bot ? this.appendChild(d) : this.insertBefore(d, this.firstChild);
if (bot && cssHeight != 'auto') {
if ($.css(this,'position') == 'static')
this.style.position = 'relative';
ds.position = 'absolute';
ds.bottom = ds.left = ds.padding = ds.margin = '0';
if (expr)
ds.setExpression('width', 'this.parentNode.offsetWidth');
else
ds.width = '100%';
}
else if (!bot && $.browser.msie) {
if ($.css(this,'position') == 'static')
this.style.position = 'relative';
ds.position = 'absolute';
ds.top = ds.left = ds.right = ds.padding = ds.margin = '0';
// fix ie6 problem when blocked element has a border width
if (expr) {
bw = sz(this,'borderLeftWidth') + sz(this,'borderRightWidth');
ds.setExpression('width', 'this.parentNode.offsetWidth - '+bw+'+ "px"');
}
else
ds.width = '100%';
}
else {
ds.position = 'relative';
ds.margin = !bot ? '-'+pad.T+'px -'+pad.R+'px '+(pad.T-width)+'px -'+pad.L+'px' :
(pad.B-width)+'px -'+pad.R+'px -'+pad.B+'px -'+pad.L+'px';
}
for (i=0; i < width; i++) {
w = Math.max(0,getWidth(fx,i, width));
e = strip.cloneNode(false);
e.style.borderWidth = '0 '+(opts[j+'R']?w:0)+'px 0 '+(opts[j+'L']?w:0)+'px';
bot ? d.appendChild(e) : d.insertBefore(e, d.firstChild);
}
if (fold && $.support.boxModel) {
if (bot && noBottomFold) continue;
for (c in opts) {
if (!opts[c]) continue;
if (bot && (c == 'TL' || c == 'TR')) continue;
if (!bot && (c == 'BL' || c == 'BR')) continue;
common = { position: 'absolute', border: 'none', margin: 0, padding: 0, overflow: 'hidden', backgroundColor: strip.style.borderColor };
$horz = $('<div/>').css(common).css({ width: width + 'px', height: '1px' });
switch(c) {
case 'TL': $horz.css({ bottom: 0, left: 0 }); break;
case 'TR': $horz.css({ bottom: 0, right: 0 }); break;
case 'BL': $horz.css({ top: 0, left: 0 }); break;
case 'BR': $horz.css({ top: 0, right: 0 }); break;
}
d.appendChild($horz[0]);
var $vert = $('<div/>').css(common).css({ top: 0, bottom: 0, width: '1px', height: width + 'px' });
switch(c) {
case 'TL': $vert.css({ left: width }); break;
case 'TR': $vert.css({ right: width }); break;
case 'BL': $vert.css({ left: width }); break;
case 'BR': $vert.css({ right: width }); break;
}
d.appendChild($vert[0]);
}
}
}
}
});
};
$.fn.uncorner = function() {
if (radius || moz || webkit)
this.css(radius ? 'border-radius' : moz ? '-moz-border-radius' : '-webkit-border-radius', 0);
$('div.jquery-corner', this).remove();
return this;
};
// expose options
$.fn.corner.defaults = {
useNative: true, // true if plugin should attempt to use native browser support for border radius rounding
metaAttr: 'data-corner' // name of meta attribute to use for options
};
})(jQuery);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
.ui-multiselect { padding:2px 0 2px 4px; text-align:left }
.ui-multiselect span.ui-icon { float:right }
.ui-multiselect-header { margin-bottom:3px; padding:3px 0 3px 4px }
.ui-multiselect-header ul { font-size:0.9em }
.ui-multiselect-header ul li { float:left; padding:0 10px 0 0 }
.ui-multiselect-header a { text-decoration:none }
.ui-multiselect-header a:hover { text-decoration:underline }
.ui-multiselect-header span.ui-icon { float:left }
.ui-multiselect-header li.ui-multiselect-close { float:right; text-align:right; padding-right:0 }
.ui-multiselect-menu { display:none; padding:3px; position:absolute; z-index:10000 }
.ui-multiselect-checkboxes { position:relative /* fixes bug in IE6/7 */; overflow-y:scroll }
.ui-multiselect-checkboxes label { cursor:default; display:block; border:1px solid transparent; padding:3px 1px }
.ui-multiselect-checkboxes label input { position:relative; top:1px }
.ui-multiselect-checkboxes li { clear:both; font-size:0.9em; padding-right:3px }
.ui-multiselect-checkboxes li.ui-multiselect-optgroup-label { text-align:center; font-weight:bold; border-bottom:1px solid }
.ui-multiselect-checkboxes li.ui-multiselect-optgroup-label a { display:block; padding:3px; margin:1px 0; text-decoration:none }
/* remove label borders in IE6 because IE6 does not support transparency */
* html .ui-multiselect-checkboxes label { border:none }

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,350 @@
/*
* jQuery UI CSS Framework @VERSION
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Theming/API
*/
/* Layout helpers
----------------------------------*/
.ui-helper-hidden { display: none; }
.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
.ui-helper-clearfix { display: inline-block; }
/* required comment for clearfix to work in Opera \*/
* html .ui-helper-clearfix { height:1%; }
.ui-helper-clearfix { display:block; }
/* end clearfix */
.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
/* Interaction Cues
----------------------------------*/
.ui-state-disabled { cursor: default !important; }
/* Icons
----------------------------------*/
/* states and images */
.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
/*
* jQuery UI CSS Framework @VERSION
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Theming/API
*
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1em&cornerRadius=6px&bgColorHeader=cb842e&bgTextureHeader=02_glass.png&bgImgOpacityHeader=25&borderColorHeader=d49768&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=f4f0ec&bgTextureContent=05_inset_soft.png&bgImgOpacityContent=100&borderColorContent=e0cfc2&fcContent=1e1b1d&iconColorContent=c47a23&bgColorDefault=ede4d4&bgTextureDefault=02_glass.png&bgImgOpacityDefault=70&borderColorDefault=cdc3b7&fcDefault=3f3731&iconColorDefault=f08000&bgColorHover=f5f0e5&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=f5ad66&fcHover=a46313&iconColorHover=f08000&bgColorActive=f4f0ec&bgTextureActive=04_highlight_hard.png&bgImgOpacityActive=100&borderColorActive=e0cfc2&fcActive=b85700&iconColorActive=f35f07&bgColorHighlight=f5f5b5&bgTextureHighlight=04_highlight_hard.png&bgImgOpacityHighlight=75&borderColorHighlight=d9bb73&fcHighlight=060200&iconColorHighlight=cb672b&bgColorError=fee4bd&bgTextureError=04_highlight_hard.png&bgImgOpacityError=65&borderColorError=f8893f&fcError=592003&iconColorError=ff7519&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=75&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=75&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
*/
/* Component containers
----------------------------------*/
.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
.ui-widget .ui-widget { font-size: 1em; }
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
.ui-widget-content { border: 1px solid #e0cfc2; background: #f4f0ec url(images/ui-bg_inset-soft_100_f4f0ec_1x100.png) 50% bottom repeat-x; color: #1e1b1d; }
.ui-widget-content a { color: #1e1b1d; }
.ui-widget-header { border: 1px solid #d49768; background: #cb842e url(images/ui-bg_glass_25_cb842e_1x400.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
.ui-widget-header a { color: #ffffff; }
/* Interaction states
----------------------------------*/
.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cdc3b7; background: #ede4d4 url(images/ui-bg_glass_70_ede4d4_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #3f3731; }
.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #3f3731; text-decoration: none; }
.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #f5ad66; background: #f5f0e5 url(images/ui-bg_glass_100_f5f0e5_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #a46313; }
.ui-state-hover a, .ui-state-hover a:hover { color: #a46313; text-decoration: none; }
.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #e0cfc2; background: #f4f0ec url(images/ui-bg_highlight-hard_100_f4f0ec_1x100.png) 50% 50% repeat-x; font-weight: normal; color: #b85700; }
.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #b85700; text-decoration: none; }
.ui-widget :active { outline: none; }
/* Interaction Cues
----------------------------------*/
.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #d9bb73; background: #f5f5b5 url(images/ui-bg_highlight-hard_75_f5f5b5_1x100.png) 50% top repeat-x; color: #060200; }
.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #060200; }
.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #f8893f; background: #fee4bd url(images/ui-bg_highlight-hard_65_fee4bd_1x100.png) 50% top repeat-x; color: #592003; }
.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #592003; }
.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #592003; }
.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
/* Icons
----------------------------------*/
/* states and images */
.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_c47a23_256x240.png); }
.ui-widget-content .ui-icon {background-image: url(images/ui-icons_c47a23_256x240.png); }
.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
.ui-state-default .ui-icon { background-image: url(images/ui-icons_f08000_256x240.png); }
.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_f08000_256x240.png); }
.ui-state-active .ui-icon {background-image: url(images/ui-icons_f35f07_256x240.png); }
.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_cb672b_256x240.png); }
.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ff7519_256x240.png); }
/* positioning */
.ui-icon-carat-1-n { background-position: 0 0; }
.ui-icon-carat-1-ne { background-position: -16px 0; }
.ui-icon-carat-1-e { background-position: -32px 0; }
.ui-icon-carat-1-se { background-position: -48px 0; }
.ui-icon-carat-1-s { background-position: -64px 0; }
.ui-icon-carat-1-sw { background-position: -80px 0; }
.ui-icon-carat-1-w { background-position: -96px 0; }
.ui-icon-carat-1-nw { background-position: -112px 0; }
.ui-icon-carat-2-n-s { background-position: -128px 0; }
.ui-icon-carat-2-e-w { background-position: -144px 0; }
.ui-icon-triangle-1-n { background-position: 0 -16px; }
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
.ui-icon-triangle-1-e { background-position: -32px -16px; }
.ui-icon-triangle-1-se { background-position: -48px -16px; }
.ui-icon-triangle-1-s { background-position: -64px -16px; }
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
.ui-icon-triangle-1-w { background-position: -96px -16px; }
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
.ui-icon-arrow-1-n { background-position: 0 -32px; }
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
.ui-icon-arrow-1-e { background-position: -32px -32px; }
.ui-icon-arrow-1-se { background-position: -48px -32px; }
.ui-icon-arrow-1-s { background-position: -64px -32px; }
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
.ui-icon-arrow-1-w { background-position: -96px -32px; }
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
.ui-icon-arrow-4 { background-position: 0 -80px; }
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
.ui-icon-extlink { background-position: -32px -80px; }
.ui-icon-newwin { background-position: -48px -80px; }
.ui-icon-refresh { background-position: -64px -80px; }
.ui-icon-shuffle { background-position: -80px -80px; }
.ui-icon-transfer-e-w { background-position: -96px -80px; }
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
.ui-icon-folder-collapsed { background-position: 0 -96px; }
.ui-icon-folder-open { background-position: -16px -96px; }
.ui-icon-document { background-position: -32px -96px; }
.ui-icon-document-b { background-position: -48px -96px; }
.ui-icon-note { background-position: -64px -96px; }
.ui-icon-mail-closed { background-position: -80px -96px; }
.ui-icon-mail-open { background-position: -96px -96px; }
.ui-icon-suitcase { background-position: -112px -96px; }
.ui-icon-comment { background-position: -128px -96px; }
.ui-icon-person { background-position: -144px -96px; }
.ui-icon-print { background-position: -160px -96px; }
.ui-icon-trash { background-position: -176px -96px; }
.ui-icon-locked { background-position: -192px -96px; }
.ui-icon-unlocked { background-position: -208px -96px; }
.ui-icon-bookmark { background-position: -224px -96px; }
.ui-icon-tag { background-position: -240px -96px; }
.ui-icon-home { background-position: 0 -112px; }
.ui-icon-flag { background-position: -16px -112px; }
.ui-icon-calendar { background-position: -32px -112px; }
.ui-icon-cart { background-position: -48px -112px; }
.ui-icon-pencil { background-position: -64px -112px; }
.ui-icon-clock { background-position: -80px -112px; }
.ui-icon-disk { background-position: -96px -112px; }
.ui-icon-calculator { background-position: -112px -112px; }
.ui-icon-zoomin { background-position: -128px -112px; }
.ui-icon-zoomout { background-position: -144px -112px; }
.ui-icon-search { background-position: -160px -112px; }
.ui-icon-wrench { background-position: -176px -112px; }
.ui-icon-gear { background-position: -192px -112px; }
.ui-icon-heart { background-position: -208px -112px; }
.ui-icon-star { background-position: -224px -112px; }
.ui-icon-link { background-position: -240px -112px; }
.ui-icon-cancel { background-position: 0 -128px; }
.ui-icon-plus { background-position: -16px -128px; }
.ui-icon-plusthick { background-position: -32px -128px; }
.ui-icon-minus { background-position: -48px -128px; }
.ui-icon-minusthick { background-position: -64px -128px; }
.ui-icon-close { background-position: -80px -128px; }
.ui-icon-closethick { background-position: -96px -128px; }
.ui-icon-key { background-position: -112px -128px; }
.ui-icon-lightbulb { background-position: -128px -128px; }
.ui-icon-scissors { background-position: -144px -128px; }
.ui-icon-clipboard { background-position: -160px -128px; }
.ui-icon-copy { background-position: -176px -128px; }
.ui-icon-contact { background-position: -192px -128px; }
.ui-icon-image { background-position: -208px -128px; }
.ui-icon-video { background-position: -224px -128px; }
.ui-icon-script { background-position: -240px -128px; }
.ui-icon-alert { background-position: 0 -144px; }
.ui-icon-info { background-position: -16px -144px; }
.ui-icon-notice { background-position: -32px -144px; }
.ui-icon-help { background-position: -48px -144px; }
.ui-icon-check { background-position: -64px -144px; }
.ui-icon-bullet { background-position: -80px -144px; }
.ui-icon-radio-off { background-position: -96px -144px; }
.ui-icon-radio-on { background-position: -112px -144px; }
.ui-icon-pin-w { background-position: -128px -144px; }
.ui-icon-pin-s { background-position: -144px -144px; }
.ui-icon-play { background-position: 0 -160px; }
.ui-icon-pause { background-position: -16px -160px; }
.ui-icon-seek-next { background-position: -32px -160px; }
.ui-icon-seek-prev { background-position: -48px -160px; }
.ui-icon-seek-end { background-position: -64px -160px; }
.ui-icon-seek-start { background-position: -80px -160px; }
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
.ui-icon-seek-first { background-position: -80px -160px; }
.ui-icon-stop { background-position: -96px -160px; }
.ui-icon-eject { background-position: -112px -160px; }
.ui-icon-volume-off { background-position: -128px -160px; }
.ui-icon-volume-on { background-position: -144px -160px; }
.ui-icon-power { background-position: 0 -176px; }
.ui-icon-signal-diag { background-position: -16px -176px; }
.ui-icon-signal { background-position: -32px -176px; }
.ui-icon-battery-0 { background-position: -48px -176px; }
.ui-icon-battery-1 { background-position: -64px -176px; }
.ui-icon-battery-2 { background-position: -80px -176px; }
.ui-icon-battery-3 { background-position: -96px -176px; }
.ui-icon-circle-plus { background-position: 0 -192px; }
.ui-icon-circle-minus { background-position: -16px -192px; }
.ui-icon-circle-close { background-position: -32px -192px; }
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
.ui-icon-circle-zoomin { background-position: -176px -192px; }
.ui-icon-circle-zoomout { background-position: -192px -192px; }
.ui-icon-circle-check { background-position: -208px -192px; }
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
.ui-icon-circlesmall-close { background-position: -32px -208px; }
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
.ui-icon-squaresmall-close { background-position: -80px -208px; }
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
/* Misc visuals
----------------------------------*/
/* Corner radius */
.ui-corner-tl { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; }
.ui-corner-tr { -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; }
.ui-corner-bl { -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; }
.ui-corner-br { -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; }
.ui-corner-top { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; }
.ui-corner-bottom { -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; }
.ui-corner-right { -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; }
.ui-corner-left { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; }
.ui-corner-all { -moz-border-radius: 6px; -webkit-border-radius: 6px; border-radius: 6px; }
/* Overlays */
.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_75_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_75_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/*
* jQuery UI Accordion @VERSION
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Accordion#theming
*/
/* IE/Win - Fix animation bug - #4615 */
.ui-accordion { width: 100%; }
.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
.ui-accordion .ui-accordion-li-fix { display: inline; }
.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
.ui-accordion .ui-accordion-content-active { display: block; }/*
* jQuery UI Button @VERSION
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Button#theming
*/
.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
.ui-button-icons-only { width: 3.4em; }
button.ui-button-icons-only { width: 3.7em; }
/*button text element */
.ui-button .ui-button-text { display: block; line-height: 1.4; }
.ui-button-text-only .ui-button-text { padding: .4em 1em; }
.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
/* no icon support for input elements, provide padding by default */
input.ui-button { padding: .4em 1em; }
/*button icon element(s) */
.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
/*button sets*/
.ui-buttonset { margin-right: 7px; }
.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
/* workarounds */
button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */

View File

@ -0,0 +1,340 @@
/*!
* jQuery UI 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI
*/
(function(c,j){function k(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.5",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,
NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,
"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");
if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"));if(!isNaN(b)&&b!=0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind("mousedown.ui-disableSelection selectstart.ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,l,m){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(l)g-=parseFloat(c.curCSS(f,
"border"+this+"Width",true))||0;if(m)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c.style(this,h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c.style(this,
h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){var b=a.nodeName.toLowerCase(),d=c.attr(a,"tabindex");if("area"===b){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&k(a)}return(/input|select|textarea|button|object/.test(b)?!a.disabled:"a"==b?a.href||!isNaN(d):!isNaN(d))&&k(a)},tabbable:function(a){var b=c.attr(a,"tabindex");return(isNaN(b)||b>=0)&&c(a).is(":focusable")}});
c(function(){var a=document.createElement("div"),b=document.body;c.extend(a.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.appendChild(a).offsetHeight===100;b.removeChild(a).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e<b.length;e++)a.options[b[e][0]]&&b[e][1].apply(a.element,
d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&&c.ui.isOverAxis(b,e,i)}})}})(jQuery);
;/*!
* jQuery UI Widget 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Widget
*/
(function(b,j){if(b.cleanData){var k=b.cleanData;b.cleanData=function(a){for(var c=0,d;(d=a[c])!=null;c++)b(d).triggerHandler("remove");k(a)}}else{var l=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add([this]).each(function(){b(this).triggerHandler("remove")});return l.call(b(this),a,c)})}}b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=function(h){return!!b.data(h,
a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend(true,{},c.options);b[e][a].prototype=b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):d;if(e&&d.substring(0,1)===
"_")return h;e?this.each(function(){var g=b.data(this,a);if(!g)throw"cannot call methods on "+a+" prior to initialization; attempted to call method '"+d+"'";if(!b.isFunction(g[d]))throw"no such method '"+d+"' for "+a+" widget instance";var i=g[d].apply(g,f);if(i!==g&&i!==j){h=i;return false}}):this.each(function(){var g=b.data(this,a);g?g.option(d||{})._init():b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",
widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){b.data(c,this.widgetName,this);this.element=b(c);this.options=b.extend(true,{},this.options,b.metadata&&b.metadata.get(c)[this.widgetName],a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._init()},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+
"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(a,c){var d=a,e=this;if(arguments.length===0)return b.extend({},e.options);if(typeof a==="string"){if(c===j)return this.options[a];d={};d[a]=c}b.each(d,function(f,h){e._setOption(f,h)});return e},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",c);return this},enable:function(){return this._setOption("disabled",
false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery);
;/*!
* jQuery UI Mouse 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Mouse
*
* Depends:
* jquery.ui.widget.js
*/
(function(c){c.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(b){return a._mouseDown(b)}).bind("click."+this.widgetName,function(b){if(a._preventClickEvent){a._preventClickEvent=false;b.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName)},_mouseDown:function(a){a.originalEvent=a.originalEvent||{};if(!a.originalEvent.mouseHandled){this._mouseStarted&&
this._mouseUp(a);this._mouseDownEvent=a;var b=this,e=a.which==1,f=typeof this.options.cancel=="string"?c(a.target).parents().add(a.target).filter(this.options.cancel).length:false;if(!e||f||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){b.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted=this._mouseStart(a)!==false;if(!this._mouseStarted){a.preventDefault();
return true}}this._mouseMoveDelegate=function(d){return b._mouseMove(d)};this._mouseUpDelegate=function(d){return b._mouseUp(d)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);c.browser.safari||a.preventDefault();return a.originalEvent.mouseHandled=true}},_mouseMove:function(a){if(c.browser.msie&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&
this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=a.target==this._mouseDownEvent.target;this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-
a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery);
;/*
* jQuery UI Position 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Position
*/
(function(c){c.ui=c.ui||{};var n=/left|center|right/,o=/top|center|bottom/,t=c.fn.position,u=c.fn.offset;c.fn.position=function(b){if(!b||!b.of)return t.apply(this,arguments);b=c.extend({},b);var a=c(b.of),d=a[0],g=(b.collision||"flip").split(" "),e=b.offset?b.offset.split(" "):[0,0],h,k,j;if(d.nodeType===9){h=a.width();k=a.height();j={top:0,left:0}}else if(d.scrollTo&&d.document){h=a.width();k=a.height();j={top:a.scrollTop(),left:a.scrollLeft()}}else if(d.preventDefault){b.at="left top";h=k=0;j=
{top:b.of.pageY,left:b.of.pageX}}else{h=a.outerWidth();k=a.outerHeight();j=a.offset()}c.each(["my","at"],function(){var f=(b[this]||"").split(" ");if(f.length===1)f=n.test(f[0])?f.concat(["center"]):o.test(f[0])?["center"].concat(f):["center","center"];f[0]=n.test(f[0])?f[0]:"center";f[1]=o.test(f[1])?f[1]:"center";b[this]=f});if(g.length===1)g[1]=g[0];e[0]=parseInt(e[0],10)||0;if(e.length===1)e[1]=e[0];e[1]=parseInt(e[1],10)||0;if(b.at[0]==="right")j.left+=h;else if(b.at[0]==="center")j.left+=h/
2;if(b.at[1]==="bottom")j.top+=k;else if(b.at[1]==="center")j.top+=k/2;j.left+=e[0];j.top+=e[1];return this.each(function(){var f=c(this),l=f.outerWidth(),m=f.outerHeight(),p=parseInt(c.curCSS(this,"marginLeft",true))||0,q=parseInt(c.curCSS(this,"marginTop",true))||0,v=l+p+parseInt(c.curCSS(this,"marginRight",true))||0,w=m+q+parseInt(c.curCSS(this,"marginBottom",true))||0,i=c.extend({},j),r;if(b.my[0]==="right")i.left-=l;else if(b.my[0]==="center")i.left-=l/2;if(b.my[1]==="bottom")i.top-=m;else if(b.my[1]===
"center")i.top-=m/2;i.left=parseInt(i.left);i.top=parseInt(i.top);r={left:i.left-p,top:i.top-q};c.each(["left","top"],function(s,x){c.ui.position[g[s]]&&c.ui.position[g[s]][x](i,{targetWidth:h,targetHeight:k,elemWidth:l,elemHeight:m,collisionPosition:r,collisionWidth:v,collisionHeight:w,offset:e,my:b.my,at:b.at})});c.fn.bgiframe&&f.bgiframe();f.offset(c.extend(i,{using:b.using}))})};c.ui.position={fit:{left:function(b,a){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();
b.left=d>0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];
b.left+=a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=
c(b),g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery);
;/*
* jQuery UI Accordion 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Accordion
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
*/
(function(c){c.widget("ui.accordion",{options:{active:0,animated:"slide",autoHeight:true,clearStyle:false,collapsible:false,event:"click",fillSpace:false,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var a=this,b=a.options;a.running=0;a.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix");
a.headers=a.element.find(b.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){b.disabled||c(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){b.disabled||c(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){b.disabled||c(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){b.disabled||c(this).removeClass("ui-state-focus")});a.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");
if(b.navigation){var d=a.element.find("a").filter(b.navigationFilter).eq(0);if(d.length){var f=d.closest(".ui-accordion-header");a.active=f.length?f:d.closest(".ui-accordion-content").prev()}}a.active=a._findActive(a.active||b.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all ui-corner-top");a.active.next().addClass("ui-accordion-content-active");a._createIcons();a.resize();a.element.attr("role","tablist");a.headers.attr("role","tab").bind("keydown.accordion",function(g){return a._keydown(g)}).next().attr("role",
"tabpanel");a.headers.not(a.active||"").attr({"aria-expanded":"false",tabIndex:-1}).next().hide();a.active.length?a.active.attr({"aria-expanded":"true",tabIndex:0}):a.headers.eq(0).attr("tabIndex",0);c.browser.safari||a.headers.find("a").attr("tabIndex",-1);b.event&&a.headers.bind(b.event.split(" ").join(".accordion ")+".accordion",function(g){a._clickHandler.call(a,g,this);g.preventDefault()})},_createIcons:function(){var a=this.options;if(a.icons){c("<span></span>").addClass("ui-icon "+a.icons.header).prependTo(this.headers);
this.active.children(".ui-icon").toggleClass(a.icons.header).toggleClass(a.icons.headerSelected);this.element.addClass("ui-accordion-icons")}},_destroyIcons:function(){this.headers.children(".ui-icon").remove();this.element.removeClass("ui-accordion-icons")},destroy:function(){var a=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("tabIndex");
this.headers.find("a").removeAttr("tabIndex");this._destroyIcons();var b=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");if(a.autoHeight||a.fillHeight)b.css("height","");return c.Widget.prototype.destroy.call(this)},_setOption:function(a,b){c.Widget.prototype._setOption.apply(this,arguments);a=="active"&&this.activate(b);if(a=="icons"){this._destroyIcons();
b&&this._createIcons()}if(a=="disabled")this.headers.add(this.headers.next())[b?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(a){if(!(this.options.disabled||a.altKey||a.ctrlKey)){var b=c.ui.keyCode,d=this.headers.length,f=this.headers.index(a.target),g=false;switch(a.keyCode){case b.RIGHT:case b.DOWN:g=this.headers[(f+1)%d];break;case b.LEFT:case b.UP:g=this.headers[(f-1+d)%d];break;case b.SPACE:case b.ENTER:this._clickHandler({target:a.target},a.target);
a.preventDefault()}if(g){c(a.target).attr("tabIndex",-1);c(g).attr("tabIndex",0);g.focus();return false}return true}},resize:function(){var a=this.options,b;if(a.fillSpace){if(c.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}b=this.element.parent().height();c.browser.msie&&this.element.parent().css("overflow",d);this.headers.each(function(){b-=c(this).outerHeight(true)});this.headers.next().each(function(){c(this).height(Math.max(0,b-c(this).innerHeight()+
c(this).height()))}).css("overflow","auto")}else if(a.autoHeight){b=0;this.headers.next().each(function(){b=Math.max(b,c(this).height("").height())}).height(b)}return this},activate:function(a){this.options.active=a;a=this._findActive(a)[0];this._clickHandler({target:a},a);return this},_findActive:function(a){return a?typeof a==="number"?this.headers.filter(":eq("+a+")"):this.headers.not(this.headers.not(a)):a===false?c([]):this.headers.filter(":eq(0)")},_clickHandler:function(a,b){var d=this.options;
if(!d.disabled)if(a.target){a=c(a.currentTarget||b);b=a[0]===this.active[0];d.active=d.collapsible&&b?false:this.headers.index(a);if(!(this.running||!d.collapsible&&b)){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);if(!b){a.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected);
a.next().addClass("ui-accordion-content-active")}h=a.next();f=this.active.next();g={options:d,newHeader:b&&d.collapsible?c([]):a,oldHeader:this.active,newContent:b&&d.collapsible?c([]):h,oldContent:f};d=this.headers.index(this.active[0])>this.headers.index(a[0]);this.active=b?c([]):a;this._toggle(h,f,g,b,d)}}else if(d.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);
this.active.next().addClass("ui-accordion-content-active");var f=this.active.next(),g={options:d,newHeader:c([]),oldHeader:d.active,newContent:c([]),oldContent:f},h=this.active=c([]);this._toggle(h,f,g)}},_toggle:function(a,b,d,f,g){var h=this,e=h.options;h.toShow=a;h.toHide=b;h.data=d;var j=function(){if(h)return h._completed.apply(h,arguments)};h._trigger("changestart",null,h.data);h.running=b.size()===0?a.size():b.size();if(e.animated){d={};d=e.collapsible&&f?{toShow:c([]),toHide:b,complete:j,
down:g,autoHeight:e.autoHeight||e.fillSpace}:{toShow:a,toHide:b,complete:j,down:g,autoHeight:e.autoHeight||e.fillSpace};if(!e.proxied)e.proxied=e.animated;if(!e.proxiedDuration)e.proxiedDuration=e.duration;e.animated=c.isFunction(e.proxied)?e.proxied(d):e.proxied;e.duration=c.isFunction(e.proxiedDuration)?e.proxiedDuration(d):e.proxiedDuration;f=c.ui.accordion.animations;var i=e.duration,k=e.animated;if(k&&!f[k]&&!c.easing[k])k="slide";f[k]||(f[k]=function(l){this.slide(l,{easing:k,duration:i||700})});
f[k](d)}else{if(e.collapsible&&f)a.toggle();else{b.hide();a.show()}j(true)}b.prev().attr({"aria-expanded":"false",tabIndex:-1}).blur();a.prev().attr({"aria-expanded":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;if(!this.running){this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""});this.toHide.removeClass("ui-accordion-content-active");this._trigger("change",null,this.data)}}});c.extend(c.ui.accordion,{version:"1.8.5",animations:{slide:function(a,
b){a=c.extend({easing:"swing",duration:300},a,b);if(a.toHide.size())if(a.toShow.size()){var d=a.toShow.css("overflow"),f=0,g={},h={},e;b=a.toShow;e=b[0].style.width;b.width(parseInt(b.parent().width(),10)-parseInt(b.css("paddingLeft"),10)-parseInt(b.css("paddingRight"),10)-(parseInt(b.css("borderLeftWidth"),10)||0)-(parseInt(b.css("borderRightWidth"),10)||0));c.each(["height","paddingTop","paddingBottom"],function(j,i){h[i]="hide";j=(""+c.css(a.toShow[0],i)).match(/^([\d+-.]+)(.*)$/);g[i]={value:j[1],
unit:j[2]||"px"}});a.toShow.css({height:0,overflow:"hidden"}).show();a.toHide.filter(":hidden").each(a.complete).end().filter(":visible").animate(h,{step:function(j,i){if(i.prop=="height")f=i.end-i.start===0?0:(i.now-i.start)/(i.end-i.start);a.toShow[0].style[i.prop]=f*g[i.prop].value+g[i.prop].unit},duration:a.duration,easing:a.easing,complete:function(){a.autoHeight||a.toShow.css("height","");a.toShow.css({width:e,overflow:d});a.complete()}})}else a.toHide.animate({height:"hide",paddingTop:"hide",
paddingBottom:"hide"},a);else a.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},a)},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1E3:200})}}})})(jQuery);
;/*
* jQuery UI Button 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Button
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
*/
(function(a){var g,i=function(b){a(":ui-button",b.target.form).each(function(){var c=a(this).data("button");setTimeout(function(){c.refresh()},1)})},h=function(b){var c=b.name,d=b.form,e=a([]);if(c)e=d?a(d).find("[name='"+c+"']"):a("[name='"+c+"']",b.ownerDocument).filter(function(){return!this.form});return e};a.widget("ui.button",{options:{disabled:null,text:true,label:null,icons:{primary:null,secondary:null}},_create:function(){this.element.closest("form").unbind("reset.button").bind("reset.button",
i);if(typeof this.options.disabled!=="boolean")this.options.disabled=this.element.attr("disabled");this._determineButtonType();this.hasTitle=!!this.buttonElement.attr("title");var b=this,c=this.options,d=this.type==="checkbox"||this.type==="radio",e="ui-state-hover"+(!d?" ui-state-active":"");if(c.label===null)c.label=this.buttonElement.html();if(this.element.is(":disabled"))c.disabled=true;this.buttonElement.addClass("ui-button ui-widget ui-state-default ui-corner-all").attr("role","button").bind("mouseenter.button",
function(){if(!c.disabled){a(this).addClass("ui-state-hover");this===g&&a(this).addClass("ui-state-active")}}).bind("mouseleave.button",function(){c.disabled||a(this).removeClass(e)}).bind("focus.button",function(){a(this).addClass("ui-state-focus")}).bind("blur.button",function(){a(this).removeClass("ui-state-focus")});d&&this.element.bind("change.button",function(){b.refresh()});if(this.type==="checkbox")this.buttonElement.bind("click.button",function(){if(c.disabled)return false;a(this).toggleClass("ui-state-active");
b.buttonElement.attr("aria-pressed",b.element[0].checked)});else if(this.type==="radio")this.buttonElement.bind("click.button",function(){if(c.disabled)return false;a(this).addClass("ui-state-active");b.buttonElement.attr("aria-pressed",true);var f=b.element[0];h(f).not(f).map(function(){return a(this).button("widget")[0]}).removeClass("ui-state-active").attr("aria-pressed",false)});else{this.buttonElement.bind("mousedown.button",function(){if(c.disabled)return false;a(this).addClass("ui-state-active");
g=this;a(document).one("mouseup",function(){g=null})}).bind("mouseup.button",function(){if(c.disabled)return false;a(this).removeClass("ui-state-active")}).bind("keydown.button",function(f){if(c.disabled)return false;if(f.keyCode==a.ui.keyCode.SPACE||f.keyCode==a.ui.keyCode.ENTER)a(this).addClass("ui-state-active")}).bind("keyup.button",function(){a(this).removeClass("ui-state-active")});this.buttonElement.is("a")&&this.buttonElement.keyup(function(f){f.keyCode===a.ui.keyCode.SPACE&&a(this).click()})}this._setOption("disabled",
c.disabled)},_determineButtonType:function(){this.type=this.element.is(":checkbox")?"checkbox":this.element.is(":radio")?"radio":this.element.is("input")?"input":"button";if(this.type==="checkbox"||this.type==="radio"){this.buttonElement=this.element.parents().last().find("label[for="+this.element.attr("id")+"]");this.element.addClass("ui-helper-hidden-accessible");var b=this.element.is(":checked");b&&this.buttonElement.addClass("ui-state-active");this.buttonElement.attr("aria-pressed",b)}else this.buttonElement=
this.element},widget:function(){return this.buttonElement},destroy:function(){this.element.removeClass("ui-helper-hidden-accessible");this.buttonElement.removeClass("ui-button ui-widget ui-state-default ui-corner-all ui-state-hover ui-state-active ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only").removeAttr("role").removeAttr("aria-pressed").html(this.buttonElement.find(".ui-button-text").html());this.hasTitle||
this.buttonElement.removeAttr("title");a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments);if(b==="disabled")c?this.element.attr("disabled",true):this.element.removeAttr("disabled");this._resetButton()},refresh:function(){var b=this.element.is(":disabled");b!==this.options.disabled&&this._setOption("disabled",b);if(this.type==="radio")h(this.element[0]).each(function(){a(this).is(":checked")?a(this).button("widget").addClass("ui-state-active").attr("aria-pressed",
true):a(this).button("widget").removeClass("ui-state-active").attr("aria-pressed",false)});else if(this.type==="checkbox")this.element.is(":checked")?this.buttonElement.addClass("ui-state-active").attr("aria-pressed",true):this.buttonElement.removeClass("ui-state-active").attr("aria-pressed",false)},_resetButton:function(){if(this.type==="input")this.options.label&&this.element.val(this.options.label);else{var b=this.buttonElement.removeClass("ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only"),
c=a("<span></span>").addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),d=this.options.icons,e=d.primary&&d.secondary;if(d.primary||d.secondary){b.addClass("ui-button-text-icon"+(e?"s":d.primary?"-primary":"-secondary"));d.primary&&b.prepend("<span class='ui-button-icon-primary ui-icon "+d.primary+"'></span>");d.secondary&&b.append("<span class='ui-button-icon-secondary ui-icon "+d.secondary+"'></span>");if(!this.options.text){b.addClass(e?"ui-button-icons-only":"ui-button-icon-only").removeClass("ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary");
this.hasTitle||b.attr("title",c)}}else b.addClass("ui-button-text-only")}}});a.widget("ui.buttonset",{_create:function(){this.element.addClass("ui-buttonset");this._init()},_init:function(){this.refresh()},_setOption:function(b,c){b==="disabled"&&this.buttons.button("option",b,c);a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){this.buttons=this.element.find(":button, :submit, :reset, :checkbox, :radio, a, :data(button)").filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":visible").filter(":first").addClass("ui-corner-left").end().filter(":last").addClass("ui-corner-right").end().end().end()},
destroy:function(){this.element.removeClass("ui-buttonset");this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy");a.Widget.prototype.destroy.call(this)}})})(jQuery);
;/*
* jQuery UI Effects 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/
*/
jQuery.effects||function(f,j){function l(c){var a;if(c&&c.constructor==Array&&c.length==3)return c;if(a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c))return[parseInt(a[1],10),parseInt(a[2],10),parseInt(a[3],10)];if(a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c))return[parseInt(a[1],
16),parseInt(a[2],16),parseInt(a[3],16)];if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];if(/rgba\(0, 0, 0, 0\)/.exec(c))return m.transparent;return m[f.trim(c).toLowerCase()]}function r(c,a){var b;do{b=f.curCSS(c,a);if(b!=""&&b!="transparent"||f.nodeName(c,"body"))break;a="backgroundColor"}while(c=c.parentNode);return l(b)}function n(){var c=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle,
a={},b,d;if(c&&c.length&&c[0]&&c[c[0]])for(var e=c.length;e--;){b=c[e];if(typeof c[b]=="string"){d=b.replace(/\-(\w)/g,function(g,h){return h.toUpperCase()});a[d]=c[b]}}else for(b in c)if(typeof c[b]==="string")a[b]=c[b];return a}function o(c){var a,b;for(a in c){b=c[a];if(b==null||f.isFunction(b)||a in s||/scrollbar/.test(a)||!/color/i.test(a)&&isNaN(parseFloat(b)))delete c[a]}return c}function t(c,a){var b={_:0},d;for(d in a)if(c[d]!=a[d])b[d]=a[d];return b}function k(c,a,b,d){if(typeof c=="object"){d=
a;b=null;a=c;c=a.effect}if(f.isFunction(a)){d=a;b=null;a={}}if(typeof a=="number"||f.fx.speeds[a]){d=b;b=a;a={}}if(f.isFunction(b)){d=b;b=null}a=a||{};b=b||a.duration;b=f.fx.off?0:typeof b=="number"?b:f.fx.speeds[b]||f.fx.speeds._default;d=d||a.complete;return[c,a,b,d]}f.effects={};f.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","color","outlineColor"],function(c,a){f.fx.step[a]=function(b){if(!b.colorInit){b.start=r(b.elem,a);b.end=l(b.end);b.colorInit=
true}b.elem.style[a]="rgb("+Math.max(Math.min(parseInt(b.pos*(b.end[0]-b.start[0])+b.start[0],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[1]-b.start[1])+b.start[1],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[2]-b.start[2])+b.start[2],10),255),0)+")"}});var m={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,
183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,
165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},p=["add","remove","toggle"],s={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};f.effects.animateClass=function(c,a,b,d){if(f.isFunction(b)){d=b;b=null}return this.each(function(){var e=f(this),g=e.attr("style")||" ",h=o(n.call(this)),q,u=e.attr("className");f.each(p,function(v,
i){c[i]&&e[i+"Class"](c[i])});q=o(n.call(this));e.attr("className",u);e.animate(t(h,q),a,b,function(){f.each(p,function(v,i){c[i]&&e[i+"Class"](c[i])});if(typeof e.attr("style")=="object"){e.attr("style").cssText="";e.attr("style").cssText=g}else e.attr("style",g);d&&d.apply(this,arguments)})})};f.fn.extend({_addClass:f.fn.addClass,addClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{add:c},a,b,d]):this._addClass(c)},_removeClass:f.fn.removeClass,removeClass:function(c,a,b,d){return a?
f.effects.animateClass.apply(this,[{remove:c},a,b,d]):this._removeClass(c)},_toggleClass:f.fn.toggleClass,toggleClass:function(c,a,b,d,e){return typeof a=="boolean"||a===j?b?f.effects.animateClass.apply(this,[a?{add:c}:{remove:c},b,d,e]):this._toggleClass(c,a):f.effects.animateClass.apply(this,[{toggle:c},a,b,d])},switchClass:function(c,a,b,d,e){return f.effects.animateClass.apply(this,[{add:a,remove:c},b,d,e])}});f.extend(f.effects,{version:"1.8.5",save:function(c,a){for(var b=0;b<a.length;b++)a[b]!==
null&&c.data("ec.storage."+a[b],c[0].style[a[b]])},restore:function(c,a){for(var b=0;b<a.length;b++)a[b]!==null&&c.css(a[b],c.data("ec.storage."+a[b]))},setMode:function(c,a){if(a=="toggle")a=c.is(":hidden")?"show":"hide";return a},getBaseline:function(c,a){var b;switch(c[0]){case "top":b=0;break;case "middle":b=0.5;break;case "bottom":b=1;break;default:b=c[0]/a.height}switch(c[1]){case "left":c=0;break;case "center":c=0.5;break;case "right":c=1;break;default:c=c[1]/a.width}return{x:c,y:b}},createWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent();
var a={width:c.outerWidth(true),height:c.outerHeight(true),"float":c.css("float")},b=f("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0});c.wrap(b);b=c.parent();if(c.css("position")=="static"){b.css({position:"relative"});c.css({position:"relative"})}else{f.extend(a,{position:c.css("position"),zIndex:c.css("z-index")});f.each(["top","left","bottom","right"],function(d,e){a[e]=c.css(e);if(isNaN(parseInt(a[e],10)))a[e]="auto"});
c.css({position:"relative",top:0,left:0})}return b.css(a).show()},removeWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent().replaceWith(c);return c},setTransition:function(c,a,b,d){d=d||{};f.each(a,function(e,g){unit=c.cssUnit(g);if(unit[0]>0)d[g]=unit[0]*b+unit[1]});return d}});f.fn.extend({effect:function(c){var a=k.apply(this,arguments);a={options:a[1],duration:a[2],callback:a[3]};var b=f.effects[c];return b&&!f.fx.off?b.call(this,a):this},_show:f.fn.show,show:function(c){if(!c||
typeof c=="number"||f.fx.speeds[c]||!f.effects[c])return this._show.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="show";return this.effect.apply(this,a)}},_hide:f.fn.hide,hide:function(c){if(!c||typeof c=="number"||f.fx.speeds[c]||!f.effects[c])return this._hide.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="hide";return this.effect.apply(this,a)}},__toggle:f.fn.toggle,toggle:function(c){if(!c||typeof c=="number"||f.fx.speeds[c]||!f.effects[c]||typeof c==
"boolean"||f.isFunction(c))return this.__toggle.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="toggle";return this.effect.apply(this,a)}},cssUnit:function(c){var a=this.css(c),b=[];f.each(["em","px","%","pt"],function(d,e){if(a.indexOf(e)>0)b=[parseFloat(a),e]});return b}});f.easing.jswing=f.easing.swing;f.extend(f.easing,{def:"easeOutQuad",swing:function(c,a,b,d,e){return f.easing[f.easing.def](c,a,b,d,e)},easeInQuad:function(c,a,b,d,e){return d*(a/=e)*a+b},easeOutQuad:function(c,
a,b,d,e){return-d*(a/=e)*(a-2)+b},easeInOutQuad:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a+b;return-d/2*(--a*(a-2)-1)+b},easeInCubic:function(c,a,b,d,e){return d*(a/=e)*a*a+b},easeOutCubic:function(c,a,b,d,e){return d*((a=a/e-1)*a*a+1)+b},easeInOutCubic:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a+b;return d/2*((a-=2)*a*a+2)+b},easeInQuart:function(c,a,b,d,e){return d*(a/=e)*a*a*a+b},easeOutQuart:function(c,a,b,d,e){return-d*((a=a/e-1)*a*a*a-1)+b},easeInOutQuart:function(c,a,b,d,e){if((a/=
e/2)<1)return d/2*a*a*a*a+b;return-d/2*((a-=2)*a*a*a-2)+b},easeInQuint:function(c,a,b,d,e){return d*(a/=e)*a*a*a*a+b},easeOutQuint:function(c,a,b,d,e){return d*((a=a/e-1)*a*a*a*a+1)+b},easeInOutQuint:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a*a+b;return d/2*((a-=2)*a*a*a*a+2)+b},easeInSine:function(c,a,b,d,e){return-d*Math.cos(a/e*(Math.PI/2))+d+b},easeOutSine:function(c,a,b,d,e){return d*Math.sin(a/e*(Math.PI/2))+b},easeInOutSine:function(c,a,b,d,e){return-d/2*(Math.cos(Math.PI*a/e)-1)+
b},easeInExpo:function(c,a,b,d,e){return a==0?b:d*Math.pow(2,10*(a/e-1))+b},easeOutExpo:function(c,a,b,d,e){return a==e?b+d:d*(-Math.pow(2,-10*a/e)+1)+b},easeInOutExpo:function(c,a,b,d,e){if(a==0)return b;if(a==e)return b+d;if((a/=e/2)<1)return d/2*Math.pow(2,10*(a-1))+b;return d/2*(-Math.pow(2,-10*--a)+2)+b},easeInCirc:function(c,a,b,d,e){return-d*(Math.sqrt(1-(a/=e)*a)-1)+b},easeOutCirc:function(c,a,b,d,e){return d*Math.sqrt(1-(a=a/e-1)*a)+b},easeInOutCirc:function(c,a,b,d,e){if((a/=e/2)<1)return-d/
2*(Math.sqrt(1-a*a)-1)+b;return d/2*(Math.sqrt(1-(a-=2)*a)+1)+b},easeInElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h<Math.abs(d)){h=d;c=g/4}else c=g/(2*Math.PI)*Math.asin(d/h);return-(h*Math.pow(2,10*(a-=1))*Math.sin((a*e-c)*2*Math.PI/g))+b},easeOutElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h<Math.abs(d)){h=d;c=g/4}else c=g/(2*Math.PI)*Math.asin(d/h);return h*Math.pow(2,-10*
a)*Math.sin((a*e-c)*2*Math.PI/g)+d+b},easeInOutElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e/2)==2)return b+d;g||(g=e*0.3*1.5);if(h<Math.abs(d)){h=d;c=g/4}else c=g/(2*Math.PI)*Math.asin(d/h);if(a<1)return-0.5*h*Math.pow(2,10*(a-=1))*Math.sin((a*e-c)*2*Math.PI/g)+b;return h*Math.pow(2,-10*(a-=1))*Math.sin((a*e-c)*2*Math.PI/g)*0.5+d+b},easeInBack:function(c,a,b,d,e,g){if(g==j)g=1.70158;return d*(a/=e)*a*((g+1)*a-g)+b},easeOutBack:function(c,a,b,d,e,g){if(g==j)g=1.70158;
return d*((a=a/e-1)*a*((g+1)*a+g)+1)+b},easeInOutBack:function(c,a,b,d,e,g){if(g==j)g=1.70158;if((a/=e/2)<1)return d/2*a*a*(((g*=1.525)+1)*a-g)+b;return d/2*((a-=2)*a*(((g*=1.525)+1)*a+g)+2)+b},easeInBounce:function(c,a,b,d,e){return d-f.easing.easeOutBounce(c,e-a,0,d,e)+b},easeOutBounce:function(c,a,b,d,e){return(a/=e)<1/2.75?d*7.5625*a*a+b:a<2/2.75?d*(7.5625*(a-=1.5/2.75)*a+0.75)+b:a<2.5/2.75?d*(7.5625*(a-=2.25/2.75)*a+0.9375)+b:d*(7.5625*(a-=2.625/2.75)*a+0.984375)+b},easeInOutBounce:function(c,
a,b,d,e){if(a<e/2)return f.easing.easeInBounce(c,a*2,0,d,e)*0.5+b;return f.easing.easeOutBounce(c,a*2-e,0,d,e)*0.5+d*0.5+b}})}(jQuery);
;/*
* jQuery UI Effects Blind 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Blind
*
* Depends:
* jquery.effects.core.js
*/
(function(b){b.effects.blind=function(c){return this.queue(function(){var a=b(this),g=["position","top","left"],f=b.effects.setMode(a,c.options.mode||"hide"),d=c.options.direction||"vertical";b.effects.save(a,g);a.show();var e=b.effects.createWrapper(a).css({overflow:"hidden"}),h=d=="vertical"?"height":"width";d=d=="vertical"?e.height():e.width();f=="show"&&e.css(h,0);var i={};i[h]=f=="show"?d:0;e.animate(i,c.duration,c.options.easing,function(){f=="hide"&&a.hide();b.effects.restore(a,g);b.effects.removeWrapper(a);
c.callback&&c.callback.apply(a[0],arguments);a.dequeue()})})}})(jQuery);
;/*
* jQuery UI Effects Bounce 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Bounce
*
* Depends:
* jquery.effects.core.js
*/
(function(e){e.effects.bounce=function(b){return this.queue(function(){var a=e(this),l=["position","top","left"],h=e.effects.setMode(a,b.options.mode||"effect"),d=b.options.direction||"up",c=b.options.distance||20,m=b.options.times||5,i=b.duration||250;/show|hide/.test(h)&&l.push("opacity");e.effects.save(a,l);a.show();e.effects.createWrapper(a);var f=d=="up"||d=="down"?"top":"left";d=d=="up"||d=="left"?"pos":"neg";c=b.options.distance||(f=="top"?a.outerHeight({margin:true})/3:a.outerWidth({margin:true})/
3);if(h=="show")a.css("opacity",0).css(f,d=="pos"?-c:c);if(h=="hide")c/=m*2;h!="hide"&&m--;if(h=="show"){var g={opacity:1};g[f]=(d=="pos"?"+=":"-=")+c;a.animate(g,i/2,b.options.easing);c/=2;m--}for(g=0;g<m;g++){var j={},k={};j[f]=(d=="pos"?"-=":"+=")+c;k[f]=(d=="pos"?"+=":"-=")+c;a.animate(j,i/2,b.options.easing).animate(k,i/2,b.options.easing);c=h=="hide"?c*2:c/2}if(h=="hide"){g={opacity:0};g[f]=(d=="pos"?"-=":"+=")+c;a.animate(g,i/2,b.options.easing,function(){a.hide();e.effects.restore(a,l);e.effects.removeWrapper(a);
b.callback&&b.callback.apply(this,arguments)})}else{j={};k={};j[f]=(d=="pos"?"-=":"+=")+c;k[f]=(d=="pos"?"+=":"-=")+c;a.animate(j,i/2,b.options.easing).animate(k,i/2,b.options.easing,function(){e.effects.restore(a,l);e.effects.removeWrapper(a);b.callback&&b.callback.apply(this,arguments)})}a.queue("fx",function(){a.dequeue()});a.dequeue()})}})(jQuery);
;/*
* jQuery UI Effects Clip 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Clip
*
* Depends:
* jquery.effects.core.js
*/
(function(b){b.effects.clip=function(e){return this.queue(function(){var a=b(this),i=["position","top","left","height","width"],f=b.effects.setMode(a,e.options.mode||"hide"),c=e.options.direction||"vertical";b.effects.save(a,i);a.show();var d=b.effects.createWrapper(a).css({overflow:"hidden"});d=a[0].tagName=="IMG"?d:a;var g={size:c=="vertical"?"height":"width",position:c=="vertical"?"top":"left"};c=c=="vertical"?d.height():d.width();if(f=="show"){d.css(g.size,0);d.css(g.position,c/2)}var h={};h[g.size]=
f=="show"?c:0;h[g.position]=f=="show"?0:c/2;d.animate(h,{queue:false,duration:e.duration,easing:e.options.easing,complete:function(){f=="hide"&&a.hide();b.effects.restore(a,i);b.effects.removeWrapper(a);e.callback&&e.callback.apply(a[0],arguments);a.dequeue()}})})}})(jQuery);
;/*
* jQuery UI Effects Drop 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Drop
*
* Depends:
* jquery.effects.core.js
*/
(function(c){c.effects.drop=function(d){return this.queue(function(){var a=c(this),h=["position","top","left","opacity"],e=c.effects.setMode(a,d.options.mode||"hide"),b=d.options.direction||"left";c.effects.save(a,h);a.show();c.effects.createWrapper(a);var f=b=="up"||b=="down"?"top":"left";b=b=="up"||b=="left"?"pos":"neg";var g=d.options.distance||(f=="top"?a.outerHeight({margin:true})/2:a.outerWidth({margin:true})/2);if(e=="show")a.css("opacity",0).css(f,b=="pos"?-g:g);var i={opacity:e=="show"?1:
0};i[f]=(e=="show"?b=="pos"?"+=":"-=":b=="pos"?"-=":"+=")+g;a.animate(i,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){e=="hide"&&a.hide();c.effects.restore(a,h);c.effects.removeWrapper(a);d.callback&&d.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery);
;/*
* jQuery UI Effects Explode 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Explode
*
* Depends:
* jquery.effects.core.js
*/
(function(j){j.effects.explode=function(a){return this.queue(function(){var c=a.options.pieces?Math.round(Math.sqrt(a.options.pieces)):3,d=a.options.pieces?Math.round(Math.sqrt(a.options.pieces)):3;a.options.mode=a.options.mode=="toggle"?j(this).is(":visible")?"hide":"show":a.options.mode;var b=j(this).show().css("visibility","hidden"),g=b.offset();g.top-=parseInt(b.css("marginTop"),10)||0;g.left-=parseInt(b.css("marginLeft"),10)||0;for(var h=b.outerWidth(true),i=b.outerHeight(true),e=0;e<c;e++)for(var f=
0;f<d;f++)b.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-f*(h/d),top:-e*(i/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:h/d,height:i/c,left:g.left+f*(h/d)+(a.options.mode=="show"?(f-Math.floor(d/2))*(h/d):0),top:g.top+e*(i/c)+(a.options.mode=="show"?(e-Math.floor(c/2))*(i/c):0),opacity:a.options.mode=="show"?0:1}).animate({left:g.left+f*(h/d)+(a.options.mode=="show"?0:(f-Math.floor(d/2))*(h/d)),top:g.top+
e*(i/c)+(a.options.mode=="show"?0:(e-Math.floor(c/2))*(i/c)),opacity:a.options.mode=="show"?1:0},a.duration||500);setTimeout(function(){a.options.mode=="show"?b.css({visibility:"visible"}):b.css({visibility:"visible"}).hide();a.callback&&a.callback.apply(b[0]);b.dequeue();j("div.ui-effects-explode").remove()},a.duration||500)})}})(jQuery);
;/*
* jQuery UI Effects Fade 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Fade
*
* Depends:
* jquery.effects.core.js
*/
(function(b){b.effects.fade=function(a){return this.queue(function(){var c=b(this),d=b.effects.setMode(c,a.options.mode||"hide");c.animate({opacity:d},{queue:false,duration:a.duration,easing:a.options.easing,complete:function(){a.callback&&a.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery);
;/*
* jQuery UI Effects Fold 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Fold
*
* Depends:
* jquery.effects.core.js
*/
(function(c){c.effects.fold=function(a){return this.queue(function(){var b=c(this),j=["position","top","left"],d=c.effects.setMode(b,a.options.mode||"hide"),g=a.options.size||15,h=!!a.options.horizFirst,k=a.duration?a.duration/2:c.fx.speeds._default/2;c.effects.save(b,j);b.show();var e=c.effects.createWrapper(b).css({overflow:"hidden"}),f=d=="show"!=h,l=f?["width","height"]:["height","width"];f=f?[e.width(),e.height()]:[e.height(),e.width()];var i=/([0-9]+)%/.exec(g);if(i)g=parseInt(i[1],10)/100*
f[d=="hide"?0:1];if(d=="show")e.css(h?{height:0,width:g}:{height:g,width:0});h={};i={};h[l[0]]=d=="show"?f[0]:g;i[l[1]]=d=="show"?f[1]:0;e.animate(h,k,a.options.easing).animate(i,k,a.options.easing,function(){d=="hide"&&b.hide();c.effects.restore(b,j);c.effects.removeWrapper(b);a.callback&&a.callback.apply(b[0],arguments);b.dequeue()})})}})(jQuery);
;/*
* jQuery UI Effects Highlight 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Highlight
*
* Depends:
* jquery.effects.core.js
*/
(function(b){b.effects.highlight=function(c){return this.queue(function(){var a=b(this),e=["backgroundImage","backgroundColor","opacity"],d=b.effects.setMode(a,c.options.mode||"show"),f={backgroundColor:a.css("backgroundColor")};if(d=="hide")f.opacity=0;b.effects.save(a,e);a.show().css({backgroundImage:"none",backgroundColor:c.options.color||"#ffff99"}).animate(f,{queue:false,duration:c.duration,easing:c.options.easing,complete:function(){d=="hide"&&a.hide();b.effects.restore(a,e);d=="show"&&!b.support.opacity&&
this.style.removeAttribute("filter");c.callback&&c.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery);
;/*
* jQuery UI Effects Pulsate 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Pulsate
*
* Depends:
* jquery.effects.core.js
*/
(function(d){d.effects.pulsate=function(a){return this.queue(function(){var b=d(this),c=d.effects.setMode(b,a.options.mode||"show");times=(a.options.times||5)*2-1;duration=a.duration?a.duration/2:d.fx.speeds._default/2;isVisible=b.is(":visible");animateTo=0;if(!isVisible){b.css("opacity",0).show();animateTo=1}if(c=="hide"&&isVisible||c=="show"&&!isVisible)times--;for(c=0;c<times;c++){b.animate({opacity:animateTo},duration,a.options.easing);animateTo=(animateTo+1)%2}b.animate({opacity:animateTo},duration,
a.options.easing,function(){animateTo==0&&b.hide();a.callback&&a.callback.apply(this,arguments)});b.queue("fx",function(){b.dequeue()}).dequeue()})}})(jQuery);
;/*
* jQuery UI Effects Scale 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Scale
*
* Depends:
* jquery.effects.core.js
*/
(function(c){c.effects.puff=function(b){return this.queue(function(){var a=c(this),e=c.effects.setMode(a,b.options.mode||"hide"),g=parseInt(b.options.percent,10)||150,h=g/100,i={height:a.height(),width:a.width()};c.extend(b.options,{fade:true,mode:e,percent:e=="hide"?g:100,from:e=="hide"?i:{height:i.height*h,width:i.width*h}});a.effect("scale",b.options,b.duration,b.callback);a.dequeue()})};c.effects.scale=function(b){return this.queue(function(){var a=c(this),e=c.extend(true,{},b.options),g=c.effects.setMode(a,
b.options.mode||"effect"),h=parseInt(b.options.percent,10)||(parseInt(b.options.percent,10)==0?0:g=="hide"?0:100),i=b.options.direction||"both",f=b.options.origin;if(g!="effect"){e.origin=f||["middle","center"];e.restore=true}f={height:a.height(),width:a.width()};a.from=b.options.from||(g=="show"?{height:0,width:0}:f);h={y:i!="horizontal"?h/100:1,x:i!="vertical"?h/100:1};a.to={height:f.height*h.y,width:f.width*h.x};if(b.options.fade){if(g=="show"){a.from.opacity=0;a.to.opacity=1}if(g=="hide"){a.from.opacity=
1;a.to.opacity=0}}e.from=a.from;e.to=a.to;e.mode=g;a.effect("size",e,b.duration,b.callback);a.dequeue()})};c.effects.size=function(b){return this.queue(function(){var a=c(this),e=["position","top","left","width","height","overflow","opacity"],g=["position","top","left","overflow","opacity"],h=["width","height","overflow"],i=["fontSize"],f=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],k=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=c.effects.setMode(a,
b.options.mode||"effect"),n=b.options.restore||false,m=b.options.scale||"both",l=b.options.origin,j={height:a.height(),width:a.width()};a.from=b.options.from||j;a.to=b.options.to||j;if(l){l=c.effects.getBaseline(l,j);a.from.top=(j.height-a.from.height)*l.y;a.from.left=(j.width-a.from.width)*l.x;a.to.top=(j.height-a.to.height)*l.y;a.to.left=(j.width-a.to.width)*l.x}var d={from:{y:a.from.height/j.height,x:a.from.width/j.width},to:{y:a.to.height/j.height,x:a.to.width/j.width}};if(m=="box"||m=="both"){if(d.from.y!=
d.to.y){e=e.concat(f);a.from=c.effects.setTransition(a,f,d.from.y,a.from);a.to=c.effects.setTransition(a,f,d.to.y,a.to)}if(d.from.x!=d.to.x){e=e.concat(k);a.from=c.effects.setTransition(a,k,d.from.x,a.from);a.to=c.effects.setTransition(a,k,d.to.x,a.to)}}if(m=="content"||m=="both")if(d.from.y!=d.to.y){e=e.concat(i);a.from=c.effects.setTransition(a,i,d.from.y,a.from);a.to=c.effects.setTransition(a,i,d.to.y,a.to)}c.effects.save(a,n?e:g);a.show();c.effects.createWrapper(a);a.css("overflow","hidden").css(a.from);
if(m=="content"||m=="both"){f=f.concat(["marginTop","marginBottom"]).concat(i);k=k.concat(["marginLeft","marginRight"]);h=e.concat(f).concat(k);a.find("*[width]").each(function(){child=c(this);n&&c.effects.save(child,h);var o={height:child.height(),width:child.width()};child.from={height:o.height*d.from.y,width:o.width*d.from.x};child.to={height:o.height*d.to.y,width:o.width*d.to.x};if(d.from.y!=d.to.y){child.from=c.effects.setTransition(child,f,d.from.y,child.from);child.to=c.effects.setTransition(child,
f,d.to.y,child.to)}if(d.from.x!=d.to.x){child.from=c.effects.setTransition(child,k,d.from.x,child.from);child.to=c.effects.setTransition(child,k,d.to.x,child.to)}child.css(child.from);child.animate(child.to,b.duration,b.options.easing,function(){n&&c.effects.restore(child,h)})})}a.animate(a.to,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){a.to.opacity===0&&a.css("opacity",a.from.opacity);p=="hide"&&a.hide();c.effects.restore(a,n?e:g);c.effects.removeWrapper(a);b.callback&&
b.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery);
;/*
* jQuery UI Effects Shake 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Shake
*
* Depends:
* jquery.effects.core.js
*/
(function(d){d.effects.shake=function(a){return this.queue(function(){var b=d(this),j=["position","top","left"];d.effects.setMode(b,a.options.mode||"effect");var c=a.options.direction||"left",e=a.options.distance||20,l=a.options.times||3,f=a.duration||a.options.duration||140;d.effects.save(b,j);b.show();d.effects.createWrapper(b);var g=c=="up"||c=="down"?"top":"left",h=c=="up"||c=="left"?"pos":"neg";c={};var i={},k={};c[g]=(h=="pos"?"-=":"+=")+e;i[g]=(h=="pos"?"+=":"-=")+e*2;k[g]=(h=="pos"?"-=":"+=")+
e*2;b.animate(c,f,a.options.easing);for(e=1;e<l;e++)b.animate(i,f,a.options.easing).animate(k,f,a.options.easing);b.animate(i,f,a.options.easing).animate(c,f/2,a.options.easing,function(){d.effects.restore(b,j);d.effects.removeWrapper(b);a.callback&&a.callback.apply(this,arguments)});b.queue("fx",function(){b.dequeue()});b.dequeue()})}})(jQuery);
;/*
* jQuery UI Effects Slide 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Slide
*
* Depends:
* jquery.effects.core.js
*/
(function(c){c.effects.slide=function(d){return this.queue(function(){var a=c(this),h=["position","top","left"],e=c.effects.setMode(a,d.options.mode||"show"),b=d.options.direction||"left";c.effects.save(a,h);a.show();c.effects.createWrapper(a).css({overflow:"hidden"});var f=b=="up"||b=="down"?"top":"left";b=b=="up"||b=="left"?"pos":"neg";var g=d.options.distance||(f=="top"?a.outerHeight({margin:true}):a.outerWidth({margin:true}));if(e=="show")a.css(f,b=="pos"?-g:g);var i={};i[f]=(e=="show"?b=="pos"?
"+=":"-=":b=="pos"?"-=":"+=")+g;a.animate(i,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){e=="hide"&&a.hide();c.effects.restore(a,h);c.effects.removeWrapper(a);d.callback&&d.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery);
;/*
* jQuery UI Effects Transfer 1.8.5
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Transfer
*
* Depends:
* jquery.effects.core.js
*/
(function(e){e.effects.transfer=function(a){return this.queue(function(){var b=e(this),c=e(a.options.to),d=c.offset();c={top:d.top,left:d.left,height:c.innerHeight(),width:c.innerWidth()};d=b.offset();var f=e('<div class="ui-effects-transfer"></div>').appendTo(document.body).addClass(a.options.className).css({top:d.top,left:d.left,height:b.innerHeight(),width:b.innerWidth(),position:"absolute"}).animate(c,a.duration,a.options.easing,function(){f.remove();a.callback&&a.callback.apply(b[0],arguments);
b.dequeue()})})}})(jQuery);
;

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -13,6 +13,8 @@
font-size: 1.25em; font-size: 1.25em;
border: 1px solid black; border: 1px solid black;
text-color: black; text-color: black;
text-decoration: none;
margin-right: 0.5em;
background-color: #ddd; background-color: #ddd;
border-top: 1px solid ThreeDLightShadow; border-top: 1px solid ThreeDLightShadow;
border-right: 1px solid ButtonShadow; border-right: 1px solid ButtonShadow;
@ -70,6 +72,7 @@ div.navigation {
padding-right: 0em; padding-right: 0em;
overflow: hidden; overflow: hidden;
text-align: center; text-align: center;
text-decoration: none;
} }
#logo { #logo {

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
''' '''
www.business-standard.com www.business-standard.com
''' '''
@ -28,30 +26,22 @@ class BusinessStandard(BasicNewsRecipe):
,'publisher' : publisher ,'publisher' : publisher
,'linearize_tables': True ,'linearize_tables': True
} }
keep_only_tags=[dict(attrs={'class':'TableClas'})]
remove_attributes=['style'] remove_tags = [
remove_tags = [dict(name=['object','link','script','iframe'])] dict(name=['object','link','script','iframe','base','meta'])
,dict(attrs={'class':'rightDiv2'})
,dict(name='table',attrs={'width':'450px'})
]
remove_attributes=['width','height']
feeds = [ feeds = [
(u'News Now' , u'http://feeds.business-standard.com/News-Now.xml' ) (u'News Now' , u'http://feeds.business-standard.com/rss/online.xml')
,(u'Banking & finance' , u'http://feeds.business-standard.com/Banking-Finance-All.xml' ) ,(u'Banking & finance' , u'http://feeds.business-standard.com/rss/3_0.xml' )
,(u'Companies & Industry', u'http://feeds.business-standard.com/Companies-Industry-All.xml') ,(u'Companies & Industry', u'http://feeds.business-standard.com/rss/2_0.xml' )
,(u'Economy & Policy' , u'http://feeds.business-standard.com/Economy-Policy-All.xml' ) ,(u'Economy & Policy' , u'http://feeds.business-standard.com/rss/4_0.xml' )
,(u'Tech World' , u'http://feeds.business-standard.com/Tech-World-All.xml' ) ,(u'Tech World' , u'http://feeds.business-standard.com/rss/8_0.xml' )
,(u'Life & Leisure' , u'http://feeds.business-standard.com/Life-Leisure-All.xml' ) ,(u'Life & Leisure' , u'http://feeds.business-standard.com/rss/6_0.xml' )
,(u'Markets & Investing' , u'http://feeds.business-standard.com/Markets-Investing-All.xml' ) ,(u'Markets & Investing' , u'http://feeds.business-standard.com/rss/1_0.xml' )
,(u'Management & Mktg' , u'http://feeds.business-standard.com/Management-Mktg-All.xml' ) ,(u'Management & Mktg' , u'http://feeds.business-standard.com/rss/7_0.xml' )
,(u'Automobiles' , u'http://feeds.business-standard.com/Automobiles.xml' ) ,(u'Opinion' , u'http://feeds.business-standard.com/rss/5_0.xml' )
,(u'Aviation' , u'http://feeds.business-standard.com/Aviation.xml' )
] ]
def print_version(self, url):
autono = url.rpartition('autono=')[2]
tp = 'on'
hk = url.rpartition('bKeyFlag=')[1]
if hk == '':
tp = ''
return 'http://www.business-standard.com/india/printpage.php?autono=' + autono + '&tp=' + tp
def get_article_url(self, article):
return article.get('guid', None)

View File

@ -0,0 +1,86 @@
from calibre.web.feeds.recipes import BasicNewsRecipe
class RevistaElCultural(BasicNewsRecipe):
title = 'Revista El Cultural'
__author__ = 'Jefferson Frantz'
description = 'Revista de cultura'
timefmt = ' [%d %b, %Y]'
language = 'es'
no_stylesheets = True
remove_javascript = True
extra_css = 'h1{ font-family: sans-serif; font-size: large; font-weight: bolder; text-align: justify } h2{ font-family: sans-serif; font-size: small; font-weight: 500; text-align: justify } h3{ font-family: sans-serif; font-size: small; font-weight: 500; text-align: justify } h4{ font-family: sans-serif; font-weight: lighter; font-size: medium; font-style: italic; text-align: justify } .rtsArticuloFirma{ font-family: sans-serif; font-size: small; text-align: justify } .column span-13 last{ font-family: sans-serif; font-size: medium; text-align: justify } .rtsImgArticulo{font-family: serif; font-size: small; color: #000000; text-align: justify}'
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return soup
keep_only_tags = [dict(name='div', attrs={'class':['column span-13 last']}),dict(name='div', attrs={'class':['rtsImgArticulo']})]
remove_tags = [
dict(name=['object','link','script','ul'])
,dict(name='div', attrs={'class':['rtsRating']})
]
#TO GET ARTICLES IN SECTION
def ec_parse_section(self, url, titleSection):
print 'Section: '+ titleSection
soup = self.index_to_soup(url)
div = soup.find(attrs={'id':'gallery'})
current_articles = []
for a in div.findAllNext('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 not url.startswith('/version_papel/'+titleSection+'/'):
if len(current_articles) > 0 and not url.startswith('/secciones/'):
break
continue
if url.startswith('/version_papel/'+titleSection+'/'):
url = 'http://www.elcultural.es'+url
self.log('\t\tFound article:', title[0:title.find("|")-1])
self.log('\t\t\t', url)
current_articles.append({'title': title[0:title.find("|")-1], 'url':url,
'description':'', 'date':''})
return current_articles
# To GET SECTIONS
def parse_index(self):
feeds = []
for title, url in [
('LETRAS',
'http://www.elcultural.es/pdf_sumario/cultural/Sumario_El_Cultural_en_PDF'),
('ARTE',
'http://www.elcultural.es/pdf_sumario/cultural/Sumario_El_Cultural_en_PDF'),
('CINE',
'http://www.elcultural.es/pdf_sumario/cultural/Sumario_El_Cultural_en_PDF'),
('CIENCIA',
'http://www.elcultural.es/pdf_sumario/cultural/Sumario_El_Cultural_en_PDF'),
## ('OPINION',
## 'http://www.elcultural.es/pdf_sumario/cultural/Sumario_El_Cultural_en_PDF'),
('ESCENARIOS',
'http://www.elcultural.es/pdf_sumario/cultural/Sumario_El_Cultural_en_PDF'),
]:
articles = self.ec_parse_section(url,title)
if articles:
feeds.append((title, articles))
return feeds

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
''' '''
emol.com emol.com
''' '''
@ -19,43 +17,34 @@ class ElMercurio(BasicNewsRecipe):
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False
encoding = 'cp1252' encoding = 'cp1252'
cover_url = 'http://www.emol.com/especiales/logo_emol/logo_emol.gif' masthead_url = 'http://www.emol.com/especiales/logo_emol/logo_emol.gif'
remove_javascript = True remove_javascript = True
use_embedded_content = False use_embedded_content = False
language = 'es'
html2lrf_options = [
'--comment', description
, '--category', category
, '--publisher', publisher
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
keep_only_tags = [ keep_only_tags = [dict(name='div', attrs={'id':['cont_iz_titulobajada','cont_iz_creditos_1_a','cont_iz_cuerpo']})]
dict(name='div', attrs={'class':'despliegue-txt_750px'}) remove_tags = [dict(name='div', attrs={'id':'cont_iz_cuerpo_relacionados'})]
,dict(name='div', attrs={'id':'div_cuerpo_participa'}) remove_attributes = ['height','width']
]
remove_tags = [
dict(name='div', attrs={'class':'contenedor_despliegue-col-left300'})
,dict(name='div', attrs={'id':['div_centro_dn_opc','div_cabezera','div_secciones','div_contenidos','div_pie','nav']})
]
feeds = [ feeds = [
(u'Noticias de ultima hora', u'http://www.emol.com/rss20/rss.asp?canal=0') (u'Noticias de ultima hora', u'http://rss.emol.com/rss.asp?canal=0')
,(u'Nacional', u'http://www.emol.com/rss20/rss.asp?canal=1') ,(u'Nacional', u'http://rss.emol.com/rss.asp?canal=1')
,(u'Mundo', u'http://www.emol.com/rss20/rss.asp?canal=2') ,(u'Mundo', u'http://rss.emol.com/rss.asp?canal=2')
,(u'Deportes', u'http://www.emol.com/rss20/rss.asp?canal=4') ,(u'Deportes', u'http://rss.emol.com/rss.asp?canal=4')
,(u'Magazine', u'http://www.emol.com/rss20/rss.asp?canal=6') ,(u'Magazine', u'http://rss.emol.com/rss.asp?canal=6')
,(u'Tecnologia', u'http://www.emol.com/rss20/rss.asp?canal=5') ,(u'Tecnologia', u'http://rss.emol.com/rss.asp?canal=5')
,(u'La Musica', u'http://www.emol.com/rss20/rss.asp?canal=7')
] ]
def preprocess_html(self, soup): def preprocess_html(self, soup):
mtag = '<meta http-equiv="Content-Language" content="es-CL"/>'
soup.head.insert(0,mtag)
for item in soup.findAll(style=True): for item in soup.findAll(style=True):
del item['style'] del item['style']
return soup return soup
language = 'es'

View File

@ -0,0 +1,74 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
'''
ft.com
'''
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class FinancialTimes(BasicNewsRecipe):
title = u'Financial Times - UK printed edition'
__author__ = 'Darko Miletic'
description = 'Financial world news'
oldest_article = 2
language = 'en_GB'
max_articles_per_feed = 250
no_stylesheets = True
use_embedded_content = False
needs_subscription = True
encoding = 'utf8'
simultaneous_downloads= 1
delay = 1
LOGIN = 'https://registration.ft.com/registration/barrier/login'
INDEX = 'http://www.ft.com/uk-edition'
PREFIX = 'http://www.ft.com'
def get_browser(self):
br = BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None:
br.open(self.LOGIN)
br.select_form(name='loginForm')
br['username'] = self.username
br['password'] = self.password
br.submit()
return br
keep_only_tags = [ dict(name='div', attrs={'id':'cont'}) ]
remove_tags_after = dict(name='p', attrs={'class':'copyright'})
remove_tags = [
dict(name='div', attrs={'id':'floating-con'})
,dict(name=['meta','iframe','base','object','embed','link'])
]
remove_attributes = ['width','height','lang']
extra_css = """
body{font-family:Arial,Helvetica,sans-serif;}
h2{font-size:large;}
.ft-story-header{font-size:xx-small;}
.ft-story-body{font-size:small;}
a{color:#003399;}
.container{font-size:x-small;}
h3{font-size:x-small;color:#003399;}
.copyright{font-size: x-small}
"""
def parse_index(self):
articles = []
soup = self.index_to_soup(self.INDEX)
wide = soup.find('div',attrs={'class':'wide'})
if wide:
for item in wide.findAll('a',href=True):
url = self.PREFIX + item['href']
title = self.tag_to_string(item)
date = strftime(self.timefmt)
articles.append({
'title' :title
,'date' :date
,'url' :url
,'description':''
})
return [('FT UK edition',articles)]
def preprocess_html(self, soup):
return self.adeify_images(soup)

View File

@ -0,0 +1,35 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = u'2010, Tomasz Dlugosz <tomek3d@gmail.com>'
'''
frazpc.pl
'''
from calibre.web.feeds.news import BasicNewsRecipe
import re
class FrazPC(BasicNewsRecipe):
title = u'frazpc.pl'
publisher = u'frazpc.pl'
description = u'Tw\xf3j Vortal Technologiczny'
language = 'pl'
__author__ = u'Tomasz D\u0142ugosz'
oldest_article = 7
max_articles_per_feed = 100
use_embedded_content = False
no_stylesheets = True
feeds = [(u'Aktualno\u015bci', u'http://www.frazpc.pl/feed'), (u'Recenzje', u'http://www.frazpc.pl/kat/recenzje-2/feed') ]
keep_only_tags = [dict(name='div', attrs={'id':'FRAZ_CONTENT'})]
remove_tags = [dict(name='p', attrs={'class':'gray tagsP fs11'})]
preprocess_regexps = [
(re.compile(i[0], re.IGNORECASE | re.DOTALL), i[1]) for i in
[(r'<div id="post-[0-9]*"', lambda match: '<div id="FRAZ_CONTENT"'),
(r'href="/f/news/', lambda match: 'href="http://www.frazpc.pl/f/news/'),
(r' &nbsp; <a href="http://www.frazpc.pl/[^>]*?">(Skomentuj|Komentarz(e)?\([0-9]*\))</a>&nbsp; \|', lambda match: '')]
]
remove_attributes = [ 'width', 'height' ]

View File

@ -11,8 +11,8 @@ import mechanize
class GoComics(BasicNewsRecipe): class GoComics(BasicNewsRecipe):
title = 'GoComics' title = 'GoComics'
__author__ = 'Starson17' __author__ = 'Starson17'
__version__ = '1.02' __version__ = '1.03'
__date__ = '14 August 2010' __date__ = '09 October 2010'
description = u'200+ Comics - Customize for more days/comics: Defaults to 7 days, 25 comics - 20 general, 5 editorial.' description = u'200+ Comics - Customize for more days/comics: Defaults to 7 days, 25 comics - 20 general, 5 editorial.'
category = 'news, comics' category = 'news, comics'
language = 'en' language = 'en'
@ -273,6 +273,7 @@ class GoComics(BasicNewsRecipe):
# ("Wit of the World","http://www.gocomics.com/witoftheworld"), # ("Wit of the World","http://www.gocomics.com/witoftheworld"),
# ("Don Wright","http://www.gocomics.com/donwright"), # ("Don Wright","http://www.gocomics.com/donwright"),
]: ]:
print 'Working on: ', title
articles = self.make_links(url) articles = self.make_links(url)
if articles: if articles:
feeds.append((title, articles)) feeds.append((title, articles))
@ -286,28 +287,30 @@ class GoComics(BasicNewsRecipe):
page_soup = self.index_to_soup(url) page_soup = self.index_to_soup(url)
if page_soup: if page_soup:
try: try:
strip_title = page_soup.h1.a.string strip_title = page_soup.find(name='div', attrs={'class':'top'}).h1.a.string
except: except:
strip_title = 'Error - no page_soup.h1.a.string' strip_title = 'Error - no Title found'
try: try:
date_title = page_soup.find('ul', attrs={'class': 'feature-nav'}).li.string date_title = page_soup.find('ul', attrs={'class': 'feature-nav'}).li.string
if not date_title:
date_title = page_soup.find('ul', attrs={'class': 'feature-nav'}).li.string
except: except:
date_title = 'Error - no page_soup.h1.li.string' date_title = 'Error - no Date found'
title = strip_title + ' - ' + date_title title = strip_title + ' - ' + date_title
for i in range(2): for i in range(2):
try: try:
strip_url_date = page_soup.h1.a['href'] strip_url_date = page_soup.find(name='div', attrs={'class':'top'}).h1.a['href']
break #success - this is normal exit break #success - this is normal exit
except: except:
strip_url_date = None
continue #try to get strip_url_date again continue #try to get strip_url_date again
continue # give up on this strip date
for i in range(2): for i in range(2):
try: try:
prev_strip_url_date = page_soup.find('a', attrs={'class': 'prev'})['href'] prev_strip_url_date = page_soup.find('a', attrs={'class': 'prev'})['href']
break #success - this is normal exit break #success - this is normal exit
except: except:
prev_strip_url_date = None
continue #try to get prev_strip_url_date again continue #try to get prev_strip_url_date again
continue # give up on this prev strip date
if strip_url_date: if strip_url_date:
page_url = 'http://www.gocomics.com' + strip_url_date page_url = 'http://www.gocomics.com' + strip_url_date
else: else:

View File

@ -38,7 +38,7 @@ class Guardian(BasicNewsRecipe):
dict(name='div', attrs={'id':["article-toolbox","subscribe-feeds",]}), dict(name='div', attrs={'id':["article-toolbox","subscribe-feeds",]}),
dict(name='ul', attrs={'class':["pagination"]}), dict(name='ul', attrs={'class':["pagination"]}),
dict(name='ul', attrs={'id':["content-actions"]}), dict(name='ul', attrs={'id':["content-actions"]}),
dict(name='img'), #dict(name='img'),
] ]
use_embedded_content = False use_embedded_content = False

View File

@ -0,0 +1,64 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__author__ = 'Tony Stegall'
__copyright__ = '2010, Tony Stegall or Tonythebookworm on mobileread.com'
__version__ = '1'
__date__ = '01, October 2010'
__docformat__ = 'English'
from calibre.web.feeds.recipes import BasicNewsRecipe
class MedScrape(BasicNewsRecipe):
title = 'MedScape'
__author__ = 'Tony Stegall'
description = 'Nursing News'
language = 'en'
timefmt = ' [%a, %d %b, %Y]'
needs_subscription = True
masthead_url = 'http://images.medscape.com/pi/global/header/sp/bg-sp-medscape.gif'
no_stylesheets = True
remove_javascript = True
conversion_options = {'linearize_tables' : True}
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
p.authors{text-align:right; font-size:small;margin-top:0px;margin-bottom: 0px;}
p.postingdate{text-align:right; font-size:small;margin-top:0px;margin-bottom: 0px;}
h2{text-align:right; font-size:small;margin-top:0px;margin-bottom: 0px;}
p{font-family:Helvetica,Arial,sans-serif;font-size:small;}
'''
remove_tags = [dict(name='div', attrs={'class':['closewindow2']}),
dict(name='div', attrs={'id': ['basicheaderlinks']})
]
def get_browser(self):
br = BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None:
br.open('https://profreg.medscape.com/px/getlogin.do')
br.select_form(name='LoginForm')
br['userId'] = self.username
br['password'] = self.password
br.submit()
return br
feeds = [
('MedInfo', 'http://www.medscape.com/cx/rssfeeds/2685.xml'),
]
def print_version(self,url):
#the original url is: http://www.medscape.com/viewarticle/728955?src=rss
#the print url is: http://www.medscape.com/viewarticle/728955_print
print_url = url.partition('?')[0] +'_print'
#print 'the printable version is: ',print_url
return print_url
def preprocess_html(self, soup):
for item in soup.findAll(attrs={'style':True}):
del item['style']
return soup

View File

@ -1,6 +1,6 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
''' '''
miamiherald.com miamiherald.com
''' '''
@ -16,12 +16,10 @@ class TheMiamiHerald(BasicNewsRecipe):
publisher = u'The Miami Herald' publisher = u'The Miami Herald'
category = u'miami herald, weather, dolphins, news, miami news, local news, miamiherald, miami newspaper, miamiherald.com, miami, the miami herald, broward, miami-dade' category = u'miami herald, weather, dolphins, news, miami news, local news, miamiherald, miami newspaper, miamiherald.com, miami, the miami herald, broward, miami-dade'
language = 'en' language = 'en'
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False
encoding = 'cp1252' encoding = 'cp1252'
remove_javascript = True remove_javascript = True
extra_css = ''' extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-size:large; color:#1A272F; } h1{font-family:Arial,Helvetica,sans-serif; font-size:large; color:#1A272F; }
.subheadline{font-family:Arial,Helvetica,sans-serif; font-size:30%; color: #666666;} .subheadline{font-family:Arial,Helvetica,sans-serif; font-size:30%; color: #666666;}
@ -33,50 +31,35 @@ class TheMiamiHerald(BasicNewsRecipe):
.imageCaption{font-family:Arial,Helvetica,sans-serif; font-size:30%; color:#666666; } .imageCaption{font-family:Arial,Helvetica,sans-serif; font-size:30%; color:#666666; }
''' '''
keep_only_tags = [dict(name='div', attrs={'id':['storyBody','storyPhotoContentArea']}), conversion_options = {
] 'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
remove_tags = [dict(name=['object','link','embed']), keep_only_tags = [dict(name='div', attrs={'id':'wide'}),]
dict(name='div', attrs={'class':["imageBuyButton","shareLinksArea","storyTools","spill_navigation pagination","circPromoArea","storyTools_footer","storyYahooContentMatch"]}) ,
dict(name='div', attrs={'id':["pluck","mlt","storyAssets"]}) ] remove_tags = [dict(name=['object','link','embed','iframe','meta'])]
feeds = [ feeds = [
(u'Breaking News' , u'http://www.miamiherald.com/416/index.xml' ) (u'Breaking News' , u'http://www.miamiherald.com/news/breaking-news/index.xml' )
,(u'Miami-Dade' , u'http://www.miamiherald.com/460/index.xml' ) ,(u'Miami-Dade' , u'http://www.miamiherald.com/news/miami-dade/index.xml' )
,(u'Broward' , u'http://www.miamiherald.com/467/index.xml' ) ,(u'Broward' , u'http://www.miamiherald.com/news/broward/index.xml' )
,(u'Florida Keys' , u'http://www.miamiherald.com/505/index.xml' ) ,(u'Florida Keys' , u'http://www.miamiherald.com/news/florida-keys/index.xml' )
,(u'Florida' , u'http://www.miamiherald.com/569/index.xml' ) ,(u'Florida' , u'http://www.miamiherald.com/news/florida/index.xml' )
,(u'Nation' , u'http://www.miamiherald.com/509/index.xml' ) ,(u'Nation' , u'http://www.miamiherald.com/news/nation/index.xml' )
,(u'World' , u'http://www.miamiherald.com/578/index.xml' ) ,(u'World' , u'http://www.miamiherald.com/news/world/index.xml' )
,(u'Americas' , u'http://www.miamiherald.com/579/index.xml' ) ,(u'Americas' , u'http://www.miamiherald.com/news/americas/index.xml' )
,(u'Cuba' , u'http://www.miamiherald.com/581/index.xml' ) ,(u'Cuba' , u'http://www.miamiherald.com/news/americas/cuba/index.xml' )
,(u'Haiti' , u'http://www.miamiherald.com/582/index.xml' ) ,(u'Haiti' , u'http://www.miamiherald.com/news/americas/haiti/index.xml' )
,(u'Politics' , u'http://www.miamiherald.com/515/index.xml' ) ,(u'Politics' , u'http://www.miamiherald.com/news/politics/index.xml' )
,(u'Education' , u'http://www.miamiherald.com/295/index.xml' ) ,(u'Education' , u'http://www.miamiherald.com/news/education/index.xml' )
,(u'Environment' , u'http://www.miamiherald.com/573/index.xml' ) ,(u'Environment' , u'http://www.miamiherald.com/news/environment/index.xml' )
] ]
def print_version(self, url):
art, sep, rest = url.rpartition('/')
art2, sep2, rest2 = art.rpartition('/')
return art2 + '/v-print/' + rest2 + '/' + rest
def get_article_url(self, article):
ans = article.get('guid', None)
print ans
try:
self.log('Looking for full story link in', ans)
soup = self.index_to_soup(ans)
x = soup.find(text="Full Story")
if x is not None:
a = x.parent
if a and a.has_key('href'):
ans = 'http://www.miamiherald.com'+a['href']
self.log('Found full story link', ans)
except:
pass
return ans

View File

@ -33,10 +33,14 @@ class NewYorker(BasicNewsRecipe):
, 'language' : language , 'language' : language
} }
keep_only_tags = [dict(name='div', attrs={'id':['articleheads','articleRail','articletext','photocredits']})] keep_only_tags = [
dict(name='div', attrs={'class':'headers'})
,dict(name='div', attrs={'id':['articleheads','items-container','articleRail','articletext','photocredits']})
]
remove_tags = [ remove_tags = [
dict(name=['meta','iframe','base','link','embed','object']) dict(name=['meta','iframe','base','link','embed','object'])
,dict(name='div', attrs={'class':['utils','articleRailLinks','icons'] }) ,dict(attrs={'class':['utils','articleRailLinks','icons'] })
,dict(attrs={'id':['show-header','show-footer'] })
] ]
remove_attributes = ['lang'] remove_attributes = ['lang']
feeds = [(u'The New Yorker', u'http://feeds.newyorker.com/services/rss/feeds/everything.xml')] feeds = [(u'The New Yorker', u'http://feeds.newyorker.com/services/rss/feeds/everything.xml')]

View File

@ -0,0 +1,46 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
'''
nightfliersbookspace.blogspot.com
'''
import re
from calibre.web.feeds.news import BasicNewsRecipe
class NightfliersBookspace(BasicNewsRecipe):
title = "Nightflier's Bookspace"
__author__ = 'Darko Miletic'
description = 'SF, Fantasy, Books, Knjige'
oldest_article = 35
max_articles_per_feed = 100
language = 'sr'
encoding = 'utf-8'
no_stylesheets = True
use_embedded_content = True
publication_type = 'blog'
cover_url = ''
extra_css = """
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
body{font-family: "Trebuchet MS",Trebuchet,Verdana,sans1,sans-serif}
.article_description{font-family: sans1, sans-serif}
img{margin-bottom: 0.8em; border: 1px solid #333333; padding: 4px }
"""
conversion_options = {
'comment' : description
, 'tags' : 'SF, fantasy, prevod, blog, Srbija'
, 'publisher': 'Ivan Jovanovic'
, 'language' : language
}
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
feeds = [(u'Posts', u'http://nightfliersbookspace.blogspot.com/feeds/posts/default')]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return self.adeify_images(soup)

View File

@ -0,0 +1,18 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1286819935(BasicNewsRecipe):
title = u'Novaya Gazeta'
__author__ = 'muwa'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
conversion_options = {'linearize_tables' : True}
remove_attributes = ['style']
language = 'ru'
feeds = [(u'Articles', u'http://www.novayagazeta.ru/rss_number.xml')]
def print_version(self, url):
return url + '?print=true'

View File

@ -0,0 +1,82 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__author__ = 'Tony Stegall'
__copyright__ = '2010, Tony Stegall or Tonythebookworm on mobileread.com'
__version__ = 'v1.01'
__date__ = '07, October 2010'
__description__ = 'Rolling Stones Mag'
'''
http://www.rollingstone.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class RollingStones(BasicNewsRecipe):
__author__ = 'Tony Stegall'
description = 'Rolling Stones Mag'
cover_url = 'http://gallery.celebritypro.com/data/media/648/kid-rock-rolling-stone-cover.jpg'
masthead_url = 'http://origin.myfonts.com/s/ec/cc-200804/Rolling_Stone-logo.gif'
title = 'Rolling Stones Mag'
category = 'Music Reviews, Movie Reviews, entertainment news'
language = 'en'
timefmt = '[%a, %d %b, %Y]'
oldest_article = 15
max_articles_per_feed = 25
use_embedded_content = False
no_stylesheets = True
remove_javascript = True
#####################################################################################
# cleanup section #
#####################################################################################
keep_only_tags = [
dict(name='div', attrs={'class':['c65l']}),
dict(name='div', attrs={'id':['col1']}),
]
remove_tags = [
dict(name='div', attrs={'class': ['storyActions upper','storyActions lowerArticleNav']}),
dict(name='div', attrs={'id': ['comments','related']}),
]
feeds = [
(u'News', u'http://www.rollingstone.com/siteServices/rss/allNews'),
(u'Blogs', u'http://www.rollingstone.com/siteServices/rss/allBlogs'),
(u'Movie Reviews', u'http://www.rollingstone.com/siteServices/rss/movieReviews'),
(u'Album Reviews', u'http://www.rollingstone.com/siteServices/rss/albumReviews'),
(u'Song Reviews', u'http://www.rollingstone.com/siteServices/rss/songReviews'),
]
def get_article_url(self, article):
return article.get('guid', None)
def append_page(self, soup, appendtag, position):
'''
Some are the articles are multipage so the below function
will get the articles that have <next>
'''
pager = soup.find('li',attrs={'class':'next'})
if pager:
nexturl = pager.a['href']
soup2 = self.index_to_soup(nexturl)
texttag = soup2.find('div', attrs={'id':'storyTextContainer'})
for it in texttag.findAll(style=True):
del it['style']
newpos = len(texttag.contents)
self.append_page(soup2,texttag,newpos)
texttag.extract()
appendtag.insert(position,texttag)

View File

@ -9,15 +9,19 @@ theage.com.au
from calibre import strftime from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.BeautifulSoup import BeautifulSoup
import re
class TheAge(BasicNewsRecipe): class TheAge(BasicNewsRecipe):
title = 'The Age' title = 'The Age'
description = 'Business News, World News and Breaking News in Melbourne, Australia' description = 'Business News, World News and Breaking News in Melbourne, Australia'
publication_type = 'newspaper'
__author__ = 'Matthew Briggs' __author__ = 'Matthew Briggs'
language = 'en_AU' language = 'en_AU'
max_articles_per_feed = 1000
recursions = 0
remove_tags = [dict(name=['table', 'script', 'noscript', 'style']), dict(name='a', attrs={'href':'/'}), dict(name='a', attrs={'href':'/text/'})]
def get_browser(self): def get_browser(self):
br = BasicNewsRecipe.get_browser() br = BasicNewsRecipe.get_browser()
@ -28,22 +32,22 @@ class TheAge(BasicNewsRecipe):
soup = BeautifulSoup(self.browser.open('http://www.theage.com.au/text/').read()) soup = BeautifulSoup(self.browser.open('http://www.theage.com.au/text/').read())
feeds, articles = [], [] section = None
feed = None sections = {}
for tag in soup.findAll(['h3', 'a']): for tag in soup.findAll(['h3', 'a']):
if tag.name == 'h3': if tag.name == 'h3':
if articles: section = self.tag_to_string(tag)
feeds.append((feed, articles)) sections[section] = []
articles = []
feed = self.tag_to_string(tag) # Make sure to skip: <a href="/">TheAge</a>
elif feed is not None and tag.has_key('href') and tag['href'].strip():
elif section and tag.has_key('href') and len(tag['href'].strip())>1:
url = tag['href'].strip() url = tag['href'].strip()
if url.startswith('/'): if url.startswith('/'):
url = 'http://www.theage.com.au' + url url = 'http://www.theage.com.au' + url
title = self.tag_to_string(tag) title = self.tag_to_string(tag)
articles.append({ sections[section].append({
'title': title, 'title': title,
'url' : url, 'url' : url,
'date' : strftime('%a, %d %b'), 'date' : strftime('%a, %d %b'),
@ -51,7 +55,58 @@ class TheAge(BasicNewsRecipe):
'content' : '', 'content' : '',
}) })
feeds = []
# Insert feeds in specified order, if available
feedSort = [ 'National', 'World', 'Opinion', 'Columns', 'Business', 'Sport', 'Entertainment' ]
for i in feedSort:
if i in sections:
feeds.append((i,sections[i]))
# Done with the sorted feeds
for i in feedSort:
del sections[i]
# Append what is left over...
for i in sections:
feeds.append((i,sections[i]))
return feeds return feeds
def get_cover_url(self):
soup = BeautifulSoup(self.browser.open('http://www.theage.com.au/todays-paper').read())
for i in soup.findAll('a'):
href = i['href']
if href and re.match('http://www.theage.com.au/frontpage/[0-9]+/[0-9]+/[0-9]+/frontpage.pdf',href):
return href
return None
def preprocess_html(self,soup):
for p in soup.findAll('p'):
# Collapse the paragraph by joining the non-tag contents
contents = [i for i in p.contents if isinstance(i,unicode)]
if len(contents):
contents = ''.join(contents)
# Filter out what's left of the text-mode navigation stuff
if re.match('((\s)|(\&nbsp\;))*\[[\|\s*]*\]((\s)|(\&nbsp\;))*$',contents):
p.extract()
continue
# Shrink the fine print font
if contents=='This material is subject to copyright and any unauthorised use, copying or mirroring is prohibited.':
p['style'] = 'font-size:small'
continue
return soup

View File

@ -16,7 +16,7 @@ class DailyTelegraph(BasicNewsRecipe):
language = 'en_AU' language = 'en_AU'
oldest_article = 2 oldest_article = 2
max_articles_per_feed = 20 max_articles_per_feed = 30
remove_javascript = True remove_javascript = True
no_stylesheets = True no_stylesheets = True
encoding = 'utf8' encoding = 'utf8'
@ -50,20 +50,22 @@ class DailyTelegraph(BasicNewsRecipe):
feeds = [ (u'News', u'http://feeds.news.com.au/public/rss/2.0/aus_news_807.xml'), feeds = [ (u'News', u'http://feeds.news.com.au/public/rss/2.0/aus_news_807.xml'),
(u'Opinion', u'http://feeds.news.com.au/public/rss/2.0/aus_opinion_58.xml'), (u'Opinion', u'http://feeds.news.com.au/public/rss/2.0/aus_opinion_58.xml'),
(u'Business', u'http://feeds.news.com.au/public/rss/2.0/aus_business_811.xml'),
(u'Media', u'http://feeds.news.com.au/public/rss/2.0/aus_media_57.xml'),
(u'Higher Education', u'http://feeds.news.com.au/public/rss/2.0/aus_higher_education_56.xml'),
(u'The Arts', u'http://feeds.news.com.au/public/rss/2.0/aus_arts_51.xml'),
(u'Commercial Property', u'http://feeds.news.com.au/public/rss/2.0/aus_business_commercial_property_708.xml'),
(u'The Nation', u'http://feeds.news.com.au/public/rss/2.0/aus_the_nation_62.xml'), (u'The Nation', u'http://feeds.news.com.au/public/rss/2.0/aus_the_nation_62.xml'),
(u'Sport', u'http://feeds.news.com.au/public/rss/2.0/aus_sport_61.xml'), (u'World News', u'http://feeds.news.com.au/public/rss/2.0/aus_world_808.xml'),
(u'Travel', u'http://feeds.news.com.au/public/rss/2.0/aus_travel_and_indulgence_63.xml'), (u'US Election', u'http://feeds.news.com.au/public/rss/2.0/aus_uselection_687.xml'),
(u'Defence', u'http://feeds.news.com.au/public/rss/2.0/aus_defence_54.xml'),
(u'Aviation', u'http://feeds.news.com.au/public/rss/2.0/aus_business_aviation_706.xml'),
(u'Mining', u'http://feeds.news.com.au/public/rss/2.0/aus_business_mining_704.xml'),
(u'Climate', u'http://feeds.news.com.au/public/rss/2.0/aus_climate_809.xml'), (u'Climate', u'http://feeds.news.com.au/public/rss/2.0/aus_climate_809.xml'),
(u'Media', u'http://feeds.news.com.au/public/rss/2.0/aus_media_57.xml'),
(u'IT', u'http://feeds.news.com.au/public/rss/2.0/ausit_itnews_topstories_367.xml'),
(u'Exec Tech', u'http://feeds.news.com.au/public/rss/2.0/ausit_exec_topstories_385.xml'),
(u'Higher Education', u'http://feeds.news.com.au/public/rss/2.0/aus_higher_education_56.xml'),
(u'Arts', u'http://feeds.news.com.au/public/rss/2.0/aus_arts_51.xml'),
(u'Travel', u'http://feeds.news.com.au/public/rss/2.0/aus_travel_and_indulgence_63.xml'),
(u'Property', u'http://feeds.news.com.au/public/rss/2.0/aus_property_59.xml'), (u'Property', u'http://feeds.news.com.au/public/rss/2.0/aus_property_59.xml'),
(u'US Election', u'http://feeds.news.com.au/public/rss/2.0/aus_uselection_687.xml')] (u'Sport', u'http://feeds.news.com.au/public/rss/2.0/aus_sport_61.xml'),
(u'Business', u'http://feeds.news.com.au/public/rss/2.0/aus_business_811.xml'),
(u'Aviation', u'http://feeds.news.com.au/public/rss/2.0/aus_business_aviation_706.xml'),
(u'Commercial Property', u'http://feeds.news.com.au/public/rss/2.0/aus_business_commercial_property_708.xml'),
(u'Mining', u'http://feeds.news.com.au/public/rss/2.0/aus_business_mining_704.xml')]
def get_article_url(self, article): def get_article_url(self, article):
return article.id return article.id

View File

@ -6,7 +6,18 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
'''
Modified by Tony Stegall
on 10/10/10 to include function to grab print version of articles
'''
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
'''
added by Tony Stegall
'''
#######################################################
from calibre.ptempfile import PersistentTemporaryFile
#######################################################
class AdvancedUserRecipe1249039563(BasicNewsRecipe): class AdvancedUserRecipe1249039563(BasicNewsRecipe):
title = u'De Volkskrant' title = u'De Volkskrant'
@ -16,20 +27,54 @@ class AdvancedUserRecipe1249039563(BasicNewsRecipe):
no_stylesheets = True no_stylesheets = True
language = 'nl' language = 'nl'
keep_only_tags = [dict(name='div', attrs={'id':'leftColumnArticle'}) ]
remove_tags = [
dict(name='div',attrs={'class':'article_tools'}),
dict(name='div',attrs={'id':'article_tools'}),
dict(name='div',attrs={'class':'articletools'}),
dict(name='div',attrs={'id':'articletools'}),
dict(name='div',attrs={'id':'myOverlay'}),
dict(name='div',attrs={'id':'trackback'}),
dict(name='div',attrs={'id':'googleBanner'}),
dict(name='div',attrs={'id':'article_headlines'}),
]
extra_css = ''' extra_css = '''
body{font-family:Arial,Helvetica,sans-serif; font-size:small;} body{font-family:Arial,Helvetica,sans-serif; font-size:small;}
h1{font-size:large;} h1{font-size:large;}
''' '''
'''
Change Log:
Date: 10/10/10 - Modified code to include obfuscated to get the print version
Author: Tony Stegall
'''
#######################################################################################################
temp_files = []
articles_are_obfuscated = True
feeds = [(u'Laatste Nieuws', u'http://volkskrant.nl/rss/laatstenieuws.rss'), (u'Binnenlands nieuws', u'http://volkskrant.nl/rss/nederland.rss'), (u'Buitenlands nieuws', u'http://volkskrant.nl/rss/internationaal.rss'), (u'Economisch nieuws', u'http://volkskrant.nl/rss/economie.rss'), (u'Sportnieuws', u'http://volkskrant.nl/rss/sport.rss'), (u'Kunstnieuws', u'http://volkskrant.nl/rss/kunst.rss'), (u'Wetenschapsnieuws', u'http://feeds.feedburner.com/DeVolkskrantWetenschap'), (u'Technologienieuws', u'http://feeds.feedburner.com/vkmedia')] def get_obfuscated_article(self, url):
br = self.get_browser()
br.open(url)
try:
response = br.follow_link(url_regex='.*?(2010)(\\/)(article)(\\/)(print)(\\/)', nr = 0)
html = response.read()
except:
response = br.open(url)
html = response.read()
self.temp_files.append(PersistentTemporaryFile('_fa.html'))
self.temp_files[-1].write(html)
self.temp_files[-1].close()
return self.temp_files[-1].name
###############################################################################################################
feeds = [
(u'Laatste Nieuws', u'http://volkskrant.nl/rss/laatstenieuws.rss'),
(u'Binnenlands nieuws', u'http://volkskrant.nl/rss/nederland.rss'),
(u'Buitenlands nieuws', u'http://volkskrant.nl/rss/internationaal.rss'),
(u'Economisch nieuws', u'http://volkskrant.nl/rss/economie.rss'),
(u'Sportnieuws', u'http://volkskrant.nl/rss/sport.rss'),
(u'Kunstnieuws', u'http://volkskrant.nl/rss/kunst.rss'),
#both of these rss feeds link back to the main volksrant.nl url a.k.a Broken
#If someone happens to know the correct paths then they can put them in here
#(u'Wetenschapsnieuws', u'http://feeds.feedburner.com/DeVolkskrantWetenschap'),
#(u'Technologienieuws', u'http://feeds.feedburner.com/vkmedia')
]
'''
example for formating
'''
# original url: http://www.volkskrant.nl/vk/nl/2668/Buitenland/article/detail/1031493/2010/10/10/Noord-Korea-ziet-nieuwe-leider.dhtml
# print url : http://www.volkskrant.nl/vk/nl/2668/2010/article/print/detail/1031493/Noord-Korea-ziet-nieuwe-leider.dhtml

View File

@ -55,6 +55,9 @@ class WikiNews(BasicNewsRecipe):
rest, sep, article_id = url.rpartition('/') rest, sep, article_id = url.rpartition('/')
return 'http://en.wikinews.org/w/index.php?title=' + article_id + '&printable=yes' return 'http://en.wikinews.org/w/index.php?title=' + article_id + '&printable=yes'
def get_cover_url(self):
return 'http://upload.wikimedia.org/wikipedia/commons/b/bd/Wikinews-logo-en.png'
def preprocess_html(self, soup): def preprocess_html(self, soup):
mtag = '<meta http-equiv="Content-Language" content="en"/><meta http-equiv="Content-Type" content="text/html; charset=utf-8">' mtag = '<meta http-equiv="Content-Language" content="en"/><meta http-equiv="Content-Type" content="text/html; charset=utf-8">'
soup.head.insert(0,mtag) soup.head.insert(0,mtag)

View File

@ -17,6 +17,7 @@ function selector(elem) {
sel = selector_in_parent(obj) + sel; sel = selector_in_parent(obj) + sel;
obj = obj.parent(); obj = obj.parent();
} }
if (sel.length > 2 && sel.charAt(1) == ">") sel = sel.substring(2);
return sel; return sel;
} }
@ -26,7 +27,8 @@ function calculate_bookmark(y, node) {
var ratio = (y - elem.offset().top)/elem.height(); var ratio = (y - elem.offset().top)/elem.height();
if (ratio > 1) { ratio = 1; } if (ratio > 1) { ratio = 1; }
if (ratio < 0) { ratio = 0; } if (ratio < 0) { ratio = 0; }
return sel + "|" + ratio; sel = sel + "|" + ratio;
return sel;
} }
function animated_scrolling_done() { function animated_scrolling_done() {
@ -37,6 +39,10 @@ function scroll_to_bookmark(bookmark) {
bm = bookmark.split("|"); bm = bookmark.split("|");
var ratio = 0.7 * parseFloat(bm[1]); var ratio = 0.7 * parseFloat(bm[1]);
$.scrollTo($(bm[0]), 1000, $.scrollTo($(bm[0]), 1000,
{over:ratio, onAfter:function(){window.py_bridge.animated_scroll_done()}}); {
over:ratio,
onAfter:function(){window.py_bridge.animated_scroll_done()}
}
);
} }

View File

@ -1,15 +1,15 @@
/** /**
* jQuery.ScrollTo * jQuery.ScrollTo
* Copyright (c) 2007-2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
* Dual licensed under MIT and GPL. * Dual licensed under MIT and GPL.
* Date: 9/11/2008 * Date: 5/25/2009
* *
* @projectDescription Easy element scrolling using jQuery. * @projectDescription Easy element scrolling using jQuery.
* http://flesler.blogspot.com/2007/10/jqueryscrollto.html * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
* Tested with jQuery 1.2.6. On FF 2/3, IE 6/7, Opera 9.2/5 and Safari 3. on Windows. * Works with jQuery +1.2.6. Tested on FF 2/3, IE 6/7/8, Opera 9.5/6, Safari 3, Chrome 1 on WinXP.
* *
* @author Ariel Flesler * @author Ariel Flesler
* @version 1.4 * @version 1.4.2
* *
* @id jQuery.scrollTo * @id jQuery.scrollTo
* @id jQuery.fn.scrollTo * @id jQuery.fn.scrollTo
@ -20,6 +20,8 @@
* - A jQuery/DOM element ( logically, child of the element to scroll ) * - A jQuery/DOM element ( logically, child of the element to scroll )
* - A string selector, that will be relative to the element to scroll ( 'li:eq(2)', etc ) * - A string selector, that will be relative to the element to scroll ( 'li:eq(2)', etc )
* - A hash { top:x, left:y }, x and y can be any kind of number/string like above. * - A hash { top:x, left:y }, x and y can be any kind of number/string like above.
* - A percentage of the container's dimension/s, for example: 50% to go to the middle.
* - The string 'max' for go-to-end.
* @param {Number} duration The OVERALL length of the animation, this argument can be the settings object instead. * @param {Number} duration The OVERALL length of the animation, this argument can be the settings object instead.
* @param {Object,Function} settings Optional set of settings or the onAfter callback. * @param {Object,Function} settings Optional set of settings or the onAfter callback.
* @option {String} axis Which axis must be scrolled, use 'x', 'y', 'xy' or 'yx'. * @option {String} axis Which axis must be scrolled, use 'x', 'y', 'xy' or 'yx'.
@ -58,31 +60,31 @@
}; };
$scrollTo.defaults = { $scrollTo.defaults = {
axis:'y', axis:'xy',
duration:1 duration: parseFloat($.fn.jquery) >= 1.3 ? 0 : 1
}; };
// Returns the element that needs to be animated to scroll the window. // Returns the element that needs to be animated to scroll the window.
// Kept for backwards compatibility (specially for localScroll & serialScroll) // Kept for backwards compatibility (specially for localScroll & serialScroll)
$scrollTo.window = function( scope ){ $scrollTo.window = function( scope ){
return $(window).scrollable(); return $(window)._scrollable();
}; };
// Hack, hack, hack... stay away! // Hack, hack, hack :)
// Returns the real elements to scroll (supports window/iframes, documents and regular nodes) // Returns the real elements to scroll (supports window/iframes, documents and regular nodes)
$.fn.scrollable = function(){ $.fn._scrollable = function(){
return this.map(function(){ return this.map(function(){
// Just store it, we might need it var elem = this,
var win = this.parentWindow || this.defaultView, isWin = !elem.nodeName || $.inArray( elem.nodeName.toLowerCase(), ['iframe','#document','html','body'] ) != -1;
// If it's a document, get its iframe or the window if it's THE document
elem = this.nodeName == '#document' ? win.frameElement || win : this,
// Get the corresponding document
doc = elem.contentDocument || (elem.contentWindow || elem).document,
isWin = elem.setInterval;
return elem.nodeName == 'IFRAME' || isWin && $.browser.safari ? doc.body if( !isWin )
: isWin ? doc.documentElement return elem;
: this;
var doc = (elem.contentWindow || elem).document || elem.ownerDocument || elem;
return $.browser.safari || doc.compatMode == 'BackCompat' ?
doc.body :
doc.documentElement;
}); });
}; };
@ -94,6 +96,9 @@
if( typeof settings == 'function' ) if( typeof settings == 'function' )
settings = { onAfter:settings }; settings = { onAfter:settings };
if( target == 'max' )
target = 9e9;
settings = $.extend( {}, $scrollTo.defaults, settings ); settings = $.extend( {}, $scrollTo.defaults, settings );
// Speed is still recognized for backwards compatibility // Speed is still recognized for backwards compatibility
duration = duration || settings.speed || settings.duration; duration = duration || settings.speed || settings.duration;
@ -106,7 +111,7 @@
settings.offset = both( settings.offset ); settings.offset = both( settings.offset );
settings.over = both( settings.over ); settings.over = both( settings.over );
return this.scrollable().each(function(){ return this._scrollable().each(function(){
var elem = this, var elem = this,
$elem = $(elem), $elem = $(elem),
targ = target, toff, attr = {}, targ = target, toff, attr = {},
@ -116,7 +121,7 @@
// A number will pass the regex // A number will pass the regex
case 'number': case 'number':
case 'string': case 'string':
if( /^([+-]=)?\d+(px)?$/.test(targ) ){ if( /^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(targ) ){
targ = both( targ ); targ = both( targ );
// We are done // We are done
break; break;
@ -134,8 +139,7 @@
pos = Pos.toLowerCase(), pos = Pos.toLowerCase(),
key = 'scroll' + Pos, key = 'scroll' + Pos,
old = elem[key], old = elem[key],
Dim = axis == 'x' ? 'Width' : 'Height', max = $scrollTo.max(elem, axis);
dim = Dim.toLowerCase();
if( toff ){// jQuery / DOMElement if( toff ){// jQuery / DOMElement
attr[key] = toff[pos] + ( win ? 0 : old - $elem.offset()[pos] ); attr[key] = toff[pos] + ( win ? 0 : old - $elem.offset()[pos] );
@ -150,14 +154,19 @@
if( settings.over[pos] ) if( settings.over[pos] )
// Scroll to a fraction of its width/height // Scroll to a fraction of its width/height
attr[key] += targ[dim]() * settings.over[pos]; attr[key] += targ[axis=='x'?'width':'height']() * settings.over[pos];
}else }else{
attr[key] = targ[pos]; var val = targ[pos];
// Handle percentage values
attr[key] = val.slice && val.slice(-1) == '%' ?
parseFloat(val) / 100 * max
: val;
}
// Number or 'number' // Number or 'number'
if( /^\d+$/.test(attr[key]) ) if( /^\d+$/.test(attr[key]) )
// Check the limits // Check the limits
attr[key] = attr[key] <= 0 ? 0 : Math.min( attr[key], max(Dim) ); attr[key] = attr[key] <= 0 ? 0 : Math.min( attr[key], max );
// Queueing axes // Queueing axes
if( !i && settings.queue ){ if( !i && settings.queue ){
@ -169,6 +178,7 @@
delete attr[key]; delete attr[key];
} }
}); });
animate( settings.onAfter ); animate( settings.onAfter );
function animate( callback ){ function animate( callback ){
@ -176,17 +186,28 @@
callback.call(this, target, settings); callback.call(this, target, settings);
}); });
}; };
function max( Dim ){
var attr ='scroll'+Dim,
doc = elem.ownerDocument;
return win
? Math.max( doc.documentElement[attr], doc.body[attr] )
: elem[attr];
};
}).end(); }).end();
}; };
// Max scrolling position, works on quirks mode
// It only fails (not too badly) on IE, quirks mode.
$scrollTo.max = function( elem, axis ){
var Dim = axis == 'x' ? 'Width' : 'Height',
scroll = 'scroll'+Dim;
if( !$(elem).is('html,body') )
return elem[scroll] - $(elem)[Dim.toLowerCase()]();
var size = 'client' + Dim,
html = elem.ownerDocument.documentElement,
body = elem.ownerDocument.body;
return Math.max( html[scroll], body[scroll] )
- Math.min( html[size] , body[size] );
};
function both( val ){ function both( val ){
return typeof val == 'object' ? val : { top:val, left:val }; return typeof val == 'object' ? val : { top:val, left:val };
}; };

View File

@ -19,7 +19,7 @@ __all__ = [
'upload_user_manual', 'upload_to_mobileread', 'upload_demo', 'upload_user_manual', 'upload_to_mobileread', 'upload_demo',
'upload_to_sourceforge', 'upload_to_google_code', 'upload_to_sourceforge', 'upload_to_google_code',
'linux32', 'linux64', 'linux', 'linux_freeze', 'linux_freeze2', 'linux32', 'linux64', 'linux', 'linux_freeze', 'linux_freeze2',
'osx32_freeze', 'osx32', 'osx', 'rsync', 'osx32_freeze', 'osx32', 'osx', 'rsync', 'push',
'win32_freeze', 'win32', 'win', 'win32_freeze', 'win32', 'win',
'stage1', 'stage2', 'stage3', 'stage4', 'publish' 'stage1', 'stage2', 'stage3', 'stage4', 'publish'
] ]
@ -68,8 +68,9 @@ upload_to_server = UploadToServer()
upload_to_sourceforge = UploadToSourceForge() upload_to_sourceforge = UploadToSourceForge()
upload_to_google_code = UploadToGoogleCode() upload_to_google_code = UploadToGoogleCode()
from setup.installer import Rsync from setup.installer import Rsync, Push
rsync = Rsync() rsync = Rsync()
push = Push()
from setup.installer.linux import Linux, Linux32, Linux64 from setup.installer.linux import Linux, Linux32, Linux64
linux = Linux() linux = Linux()

View File

@ -11,16 +11,21 @@ import subprocess, tempfile, os, time
from setup import Command, installer_name from setup import Command, installer_name
from setup.build_environment import HOST, PROJECT from setup.build_environment import HOST, PROJECT
BASE_RSYNC = 'rsync -avz --delete'.split()
EXCLUDES = []
for x in [
'src/calibre/plugins', 'src/calibre/manual', 'src/calibre/trac',
'.bzr', '.build', '.svn', 'build', 'dist', 'imgsrc', '*.pyc', '*.pyo', '*.swp',
'*.swo']:
EXCLUDES.extend(['--exclude', x])
SAFE_EXCLUDES = ['"%s"'%x if '*' in x else x for x in EXCLUDES]
class Rsync(Command): class Rsync(Command):
description = 'Sync source tree from development machine' description = 'Sync source tree from development machine'
SYNC_CMD = ('rsync -avz --delete --exclude src/calibre/plugins ' SYNC_CMD = ' '.join(BASE_RSYNC+SAFE_EXCLUDES+
'--exclude src/calibre/manual --exclude src/calibre/trac ' ['rsync://{host}/work/{project}', '..'])
'--exclude .bzr --exclude .build --exclude .svn --exclude build --exclude dist '
'--exclude imgsrc '
'--exclude "*.pyc" --exclude "*.pyo" --exclude "*.swp" --exclude "*.swo" '
'rsync://{host}/work/{project} ..')
def run(self, opts): def run(self, opts):
cmd = self.SYNC_CMD.format(host=HOST, project=PROJECT) cmd = self.SYNC_CMD.format(host=HOST, project=PROJECT)
@ -28,6 +33,21 @@ class Rsync(Command):
subprocess.check_call(cmd, shell=True) subprocess.check_call(cmd, shell=True)
class Push(Command):
description = 'Push code to another host'
def run(self, opts):
for host in (
r'Owner@winxp:/cygdrive/c/Documents\ and\ Settings/Owner/calibre',
'kovid@ox:calibre'
):
rcmd = BASE_RSYNC + EXCLUDES + ['.', host]
print '\n\nPushing to:', host, '\n'
subprocess.check_call(rcmd)
class VMInstaller(Command): class VMInstaller(Command):
EXTRA_SLEEP = 5 EXTRA_SLEEP = 5

View File

@ -6,9 +6,9 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os, re, cStringIO, base64, httplib, subprocess import os, re, cStringIO, base64, httplib, subprocess, hashlib, shutil
from subprocess import check_call from subprocess import check_call
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile, mkdtemp
from setup import Command, __version__, installer_name, __appname__ from setup import Command, __version__, installer_name, __appname__
@ -331,5 +331,19 @@ class UploadToServer(Command):
%(__version__, DOWNLOADS), shell=True) %(__version__, DOWNLOADS), shell=True)
check_call('ssh divok /etc/init.d/apache2 graceful', check_call('ssh divok /etc/init.d/apache2 graceful',
shell=True) shell=True)
tdir = mkdtemp()
for installer in installers():
if not os.path.exists(installer):
continue
with open(installer, 'rb') as f:
raw = f.read()
fingerprint = hashlib.sha512(raw).hexdigest()
fname = os.path.basename(installer+'.sha512')
with open(os.path.join(tdir, fname), 'wb') as f:
f.write(fingerprint)
check_call('scp %s/*.sha512 divok:%s/signatures/' % (tdir, DOWNLOADS),
shell=True)
shutil.rmtree(tdir)

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.7.22' __version__ = '0.7.23'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re import re

View File

@ -462,7 +462,7 @@ from calibre.devices.edge.driver import EDGE
from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS, SOVOS from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS, SOVOS
from calibre.devices.sne.driver import SNE from calibre.devices.sne.driver import SNE
from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN, \ from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN, \
GEMEI, VELOCITYMICRO GEMEI, VELOCITYMICRO, PDNOVEL_KOBO
from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG
from calibre.devices.kobo.driver import KOBO from calibre.devices.kobo.driver import KOBO
@ -576,6 +576,7 @@ plugins += [
SPECTRA, SPECTRA,
GEMEI, GEMEI,
VELOCITYMICRO, VELOCITYMICRO,
PDNOVEL_KOBO,
ITUNES, ITUNES,
] ]
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ plugins += [x for x in list(locals().values()) if isinstance(x, type) and \

View File

@ -13,7 +13,8 @@ from calibre.devices.errors import UserFeedback
from calibre.devices.usbms.deviceconfig import DeviceConfig from calibre.devices.usbms.deviceconfig import DeviceConfig
from calibre.devices.interface import DevicePlugin from calibre.devices.interface import DevicePlugin
from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.ebooks.metadata import authors_to_string, MetaInformation from calibre.ebooks.metadata import authors_to_string, MetaInformation, \
title_sort
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.epub import set_metadata from calibre.ebooks.metadata.epub import set_metadata
from calibre.library.server.utils import strftime from calibre.library.server.utils import strftime
@ -96,6 +97,9 @@ class ITUNES(DriverBase):
OPEN_FEEDBACK_MESSAGE = _( OPEN_FEEDBACK_MESSAGE = _(
'Apple device detected, launching iTunes, please wait ...') 'Apple device detected, launching iTunes, please wait ...')
BACKLOADING_ERROR_MESSAGE = _(
"Cannot copy books directly from iDevice. "
"Drag from iTunes Library to desktop, then add to calibre's Library window.")
# Product IDs: # Product IDs:
# 0x1291 iPod Touch # 0x1291 iPod Touch
@ -259,6 +263,8 @@ class ITUNES(DriverBase):
self.report_progress(1.0, _('Updating device metadata listing...')) self.report_progress(1.0, _('Updating device metadata listing...'))
# Add new books to booklists[0] # Add new books to booklists[0]
# Charles thinks this should be
# for new_book in metadata[0]:
for new_book in locations[0]: for new_book in locations[0]:
if DEBUG: if DEBUG:
self.log.info(" adding '%s' by '%s' to booklists[0]" % self.log.info(" adding '%s' by '%s' to booklists[0]" %
@ -1208,6 +1214,10 @@ class ITUNES(DriverBase):
except: except:
self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0])) self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0]))
self.log.error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title)) self.log.error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title))
import traceback
traceback.print_exc()
return thumb return thumb
if isosx: if isosx:
@ -2400,7 +2410,7 @@ class ITUNES(DriverBase):
try: try:
storage_path = os.path.split(cached_book['lib_book'].location().path) storage_path = os.path.split(cached_book['lib_book'].location().path)
if cached_book['lib_book'].location().path.startswith(self.iTunes_media) and \ if cached_book['lib_book'].location().path.startswith(self.iTunes_media) and \
not storage_path[0].startswith(self.calibre_library_path): not storage_path[0].startswith(prefs['library_path']):
title_storage_path = storage_path[0] title_storage_path = storage_path[0]
if DEBUG: if DEBUG:
self.log.info(" removing title_storage_path: %s" % title_storage_path) self.log.info(" removing title_storage_path: %s" % title_storage_path)
@ -2452,7 +2462,7 @@ class ITUNES(DriverBase):
if book: if book:
if self.iTunes_media and path.startswith(self.iTunes_media) and \ if self.iTunes_media and path.startswith(self.iTunes_media) and \
not path.startswith(self.calibre_library_path): not path.startswith(prefs['library_path']):
storage_path = os.path.split(path) storage_path = os.path.split(path)
if DEBUG: if DEBUG:
self.log.info(" removing '%s' at %s" % self.log.info(" removing '%s' at %s" %
@ -3122,6 +3132,9 @@ class Book(Metadata):
See ebooks.metadata.book.base See ebooks.metadata.book.base
''' '''
def __init__(self,title,author): def __init__(self,title,author):
Metadata.__init__(self, title, authors=[author]) Metadata.__init__(self, title, authors=[author])
@property
def title_sorter(self):
return title_sort(self.title)

View File

@ -39,6 +39,10 @@ class DevicePlugin(Plugin):
#: Whether the metadata on books can be set via the GUI. #: Whether the metadata on books can be set via the GUI.
CAN_SET_METADATA = ['title', 'authors', 'collections'] CAN_SET_METADATA = ['title', 'authors', 'collections']
# Set this to None if the books on the device are files that the GUI can
# access in order to add the books from the device to the library
BACKLOADING_ERROR_MESSAGE = _('Cannot get files from this device')
#: Path separator for paths to books on device #: Path separator for paths to books on device
path_sep = os.sep path_sep = os.sep
@ -417,14 +421,16 @@ class DevicePlugin(Plugin):
select a specific plugboard. This method is called immediately before select a specific plugboard. This method is called immediately before
add_books and sync_booklists. add_books and sync_booklists.
pb_func is a callable with the following signature: pb_func is a callable with the following signature::
def pb_func(device_name, format, plugboards) def pb_func(device_name, format, plugboards)
You give it the current device name (either the class name or You give it the current device name (either the class name or
DEVICE_PLUGBOARD_NAME), the format you are interested in (a 'real' DEVICE_PLUGBOARD_NAME), the format you are interested in (a 'real'
format or 'device_db'), and the plugboards (you were given those by format or 'device_db'), and the plugboards (you were given those by
set_plugboards, the same place you got this method). set_plugboards, the same place you got this method).
Return value: None or a single plugboard instance. :return: None or a single plugboard instance.
''' '''
pass pass

View File

@ -108,6 +108,16 @@ class PDNOVEL(USBMS):
with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile: with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile:
coverfile.write(coverdata[2]) coverfile.write(coverdata[2])
class PDNOVEL_KOBO(PDNOVEL):
name = 'Pandigital Kobo device interface'
gui_name = 'PD Novel (Kobo)'
description = _('Communicate with the Pandigital Novel')
BCD = [0x222]
EBOOK_DIR_MAIN = 'eBooks/Kobo'
class VELOCITYMICRO(USBMS): class VELOCITYMICRO(USBMS):
name = 'VelocityMicro device interface' name = 'VelocityMicro device interface'
gui_name = 'VelocityMicro' gui_name = 'VelocityMicro'

View File

@ -6,6 +6,7 @@ __docformat__ = 'restructuredtext en'
import os, re, time, sys import os, re, time, sys
from calibre.ebooks.metadata import title_sort
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
from calibre.devices.mime import mime_type_ext from calibre.devices.mime import mime_type_ext
from calibre.devices.interface import BookList as _BookList from calibre.devices.interface import BookList as _BookList
@ -54,7 +55,7 @@ class Book(Metadata):
def title_sorter(self): def title_sorter(self):
doc = '''String to sort the title. If absent, title is returned''' doc = '''String to sort the title. If absent, title is returned'''
def fget(self): def fget(self):
return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', self.title).rstrip() return title_sort(self.title)
return property(doc=doc, fget=fget) return property(doc=doc, fget=fget)
@dynamic_property @dynamic_property
@ -124,7 +125,6 @@ class CollectionsBookList(BookList):
collections = {} collections = {}
# This map of sets is used to avoid linear searches when testing for # This map of sets is used to avoid linear searches when testing for
# book equality # book equality
collections_lpaths = {}
for book in self: for book in self:
# Make sure we can identify this book via the lpath # Make sure we can identify this book via the lpath
lpath = getattr(book, 'lpath', None) lpath = getattr(book, 'lpath', None)
@ -198,20 +198,22 @@ class CollectionsBookList(BookList):
cat_name = category cat_name = category
if cat_name not in collections: if cat_name not in collections:
collections[cat_name] = [] collections[cat_name] = {}
collections_lpaths[cat_name] = set()
if lpath in collections_lpaths[cat_name]:
continue
collections_lpaths[cat_name].add(lpath)
if is_series: if is_series:
collections[cat_name].append( if doing_dc:
(book, book.get(attr+'_index', sys.maxint))) collections[cat_name][lpath] = \
(book, book.get('series_index', sys.maxint))
else: else:
collections[cat_name].append( collections[cat_name][lpath] = \
(book, book.get('title_sort', 'zzzz'))) (book, book.get(attr+'_index', sys.maxint))
else:
if lpath not in collections[cat_name]:
collections[cat_name][lpath] = \
(book, book.get('title_sort', 'zzzz'))
# Sort collections # Sort collections
result = {} result = {}
for category, books in collections.items(): for category, lpaths in collections.items():
books = lpaths.values()
books.sort(cmp=lambda x,y:cmp(x[1], y[1])) books.sort(cmp=lambda x,y:cmp(x[1], y[1]))
result[category] = [x[0] for x in books] result[category] = [x[0] for x in books]
return result return result

View File

@ -94,6 +94,9 @@ class Device(DeviceConfig, DevicePlugin):
EBOOK_DIR_CARD_B = '' EBOOK_DIR_CARD_B = ''
DELETE_EXTS = [] DELETE_EXTS = []
# USB disk-based devices can see the book files on the device, so can
# copy these back to the library
BACKLOADING_ERROR_MESSAGE = None
def reset(self, key='-1', log_packets=False, report_progress=None, def reset(self, key='-1', log_packets=False, report_progress=None,
detected_device=None): detected_device=None):
@ -626,6 +629,50 @@ class Device(DeviceConfig, DevicePlugin):
setattr(self, prefix, mp) setattr(self, prefix, mp)
self._linux_mount_map[card] = mp self._linux_mount_map[card] = mp
self.filter_read_only_mount_points()
def filter_read_only_mount_points(self):
def is_readonly(mp):
if mp is None:
return True
path = os.path.join(mp, 'calibre_readonly_test')
ro = True
try:
with open(path, 'wb'):
ro = False
except:
pass
else:
try:
os.remove(path)
except:
pass
return ro
for mp in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'):
if is_readonly(getattr(self, mp, None)):
setattr(self, mp, None)
if self._main_prefix is None:
for p in ('_card_a_prefix', '_card_b_prefix'):
nmp = getattr(self, p, None)
if nmp is not None:
self._main_prefix = nmp
setattr(self, p, None)
break
if self._main_prefix is None:
raise DeviceError(_('The main memory of %s is read only. '
'This usually happens because of file system errors.')
%self.__class__.__name__)
if self._card_a_prefix is None and self._card_b_prefix is not None:
self._card_a_prefix = self._card_b_prefix
self._card_b_prefix = None
def open(self): def open(self):
time.sleep(5) time.sleep(5)
self._main_prefix = self._card_a_prefix = self._card_b_prefix = None self._main_prefix = self._card_a_prefix = self._card_b_prefix = None

View File

@ -15,7 +15,6 @@ from calibre.utils.chm.chmlib import (
chm_enumerate, chm_enumerate,
) )
from calibre.utils.config import OptionParser
from calibre.ebooks.metadata.toc import TOC from calibre.ebooks.metadata.toc import TOC
from calibre.ebooks.chardet import xml_to_unicode from calibre.ebooks.chardet import xml_to_unicode
@ -37,41 +36,6 @@ def check_empty(s, rex = re.compile(r'\S')):
return rex.search(s) is None return rex.search(s) is None
def option_parser():
parser = OptionParser(usage=_('%prog [options] mybook.chm'))
parser.add_option('--output-dir', '-d', default='.', help=_('Output directory. Defaults to current directory'), dest='output')
parser.add_option('--verbose', default=False, action='store_true', dest='verbose')
parser.add_option("-t", "--title", action="store", type="string", \
dest="title", help=_("Set the book title"))
parser.add_option('--title-sort', action='store', type='string', default=None,
dest='title_sort', help=_('Set sort key for the title'))
parser.add_option("-a", "--author", action="store", type="string", \
dest="author", help=_("Set the author"))
parser.add_option('--author-sort', action='store', type='string', default=None,
dest='author_sort', help=_('Set sort key for the author'))
parser.add_option("-c", "--category", action="store", type="string", \
dest="category", help=_("The category this book belongs"
" to. E.g.: History"))
parser.add_option("--thumbnail", action="store", type="string", \
dest="thumbnail", help=_("Path to a graphic that will be"
" set as this files' thumbnail"))
parser.add_option("--comment", action="store", type="string", \
dest="freetext", help=_("Path to a txt file containing a comment."))
parser.add_option("--get-thumbnail", action="store_true", \
dest="get_thumbnail", default=False, \
help=_("Extract thumbnail from LRF file"))
parser.add_option('--publisher', default=None, help=_('Set the publisher'))
parser.add_option('--classification', default=None, help=_('Set the book classification'))
parser.add_option('--creator', default=None, help=_('Set the book creator'))
parser.add_option('--producer', default=None, help=_('Set the book producer'))
parser.add_option('--get-cover', action='store_true', default=False,
help=_('Extract cover from LRF file. Note that the LRF format has no defined cover, so we use some heuristics to guess the cover.'))
parser.add_option('--bookid', action='store', type='string', default=None,
dest='book_id', help=_('Set book ID'))
parser.add_option('--font-delta', action='store', type='int', default=0,
dest='font_delta', help=_('Set font delta'))
return parser
class CHMError(Exception): class CHMError(Exception):
pass pass

View File

@ -94,7 +94,7 @@ class PageProcessor(list):
from calibre.utils.magick import PixelWand from calibre.utils.magick import PixelWand
for i, wand in enumerate(self.pages): for i, wand in enumerate(self.pages):
pw = PixelWand() pw = PixelWand()
pw.color = 'white' pw.color = '#ffffff'
wand.set_border_color(pw) wand.set_border_color(pw)
if self.rotate: if self.rotate:

View File

@ -38,6 +38,7 @@ class SafeFormat(TemplateFormatter):
def get_value(self, key, args, kwargs): def get_value(self, key, args, kwargs):
try: try:
if key != 'title_sort':
key = field_metadata.search_term_to_field_key(key.lower()) key = field_metadata.search_term_to_field_key(key.lower())
b = self.book.get_user_metadata(key, False) b = self.book.get_user_metadata(key, False)
if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0: if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0:

View File

@ -45,7 +45,7 @@ def fetch_metadata(url, max=100, timeout=5.):
class ISBNDBMetadata(Metadata): class ISBNDBMetadata(Metadata):
def __init__(self, book): def __init__(self, book):
Metadata.__init__(self, None, []) Metadata.__init__(self, None)
def tostring(e): def tostring(e):
if not hasattr(e, 'string'): if not hasattr(e, 'string'):
@ -58,21 +58,21 @@ class ISBNDBMetadata(Metadata):
return ans return ans
self.isbn = unicode(book.get('isbn13', book.get('isbn'))) self.isbn = unicode(book.get('isbn13', book.get('isbn')))
self.title = tostring(book.find('titlelong')) title = tostring(book.find('titlelong'))
if not self.title: if not title:
self.title = tostring(book.find('title')) title = tostring(book.find('title'))
if not self.title: self.title = title
self.title = _('Unknown')
self.title = unicode(self.title).strip() self.title = unicode(self.title).strip()
self.authors = [] authors = []
au = tostring(book.find('authorstext')) au = tostring(book.find('authorstext'))
if au: if au:
au = au.strip() au = au.strip()
temp = au.split(',') temp = au.split(',')
for au in temp: for au in temp:
if not au: continue if not au: continue
self.authors.extend([a.strip() for a in au.split('&amp;')]) authors.extend([a.strip() for a in au.split('&amp;')])
if authors:
self.authors = authors
try: try:
self.author_sort = tostring(book.find('authors').find('person')) self.author_sort = tostring(book.find('authors').find('person'))
if self.authors and self.author_sort == self.authors[0]: if self.authors and self.author_sort == self.authors[0]:

View File

@ -189,7 +189,7 @@ class MobiMLizer(object):
para = wrapper para = wrapper
emleft = int(round(left / self.profile.fbase)) - ems emleft = int(round(left / self.profile.fbase)) - ems
emleft = min((emleft, 10)) emleft = min((emleft, 10))
while emleft > 0: while emleft > ems/2.0:
para = etree.SubElement(para, XHTML('blockquote')) para = etree.SubElement(para, XHTML('blockquote'))
emleft -= ems emleft -= ems
else: else:

View File

@ -122,7 +122,6 @@ def rescale_image(data, maxsizeb, dimen=None):
img = Image() img = Image()
quality = 95 quality = 95
if hasattr(img, 'set_compression_quality'):
img.load(data) img.load(data)
while len(data) >= maxsizeb and quality >= 10: while len(data) >= maxsizeb and quality >= 10:
quality -= 5 quality -= 5
@ -138,7 +137,6 @@ def rescale_image(data, maxsizeb, dimen=None):
img.load(orig_data) img.load(orig_data)
w, h = img.size w, h = img.size
img.size = (int(scale*w), int(scale*h)) img.size = (int(scale*w), int(scale*h))
if hasattr(img, 'set_compression_quality'):
img.set_compression_quality(quality) img.set_compression_quality(quality)
data = img.export('jpg') data = img.export('jpg')
scale -= 0.05 scale -= 0.05

View File

@ -131,6 +131,7 @@ class InterfaceAction(QObject):
Called whenever the current library is changed. Called whenever the current library is changed.
:param db: The LibraryDatabase corresponding to the current library. :param db: The LibraryDatabase corresponding to the current library.
''' '''
pass pass
@ -149,5 +150,6 @@ class InterfaceAction(QObject):
:return: False to halt the shutdown. You are responsible for telling :return: False to halt the shutdown. You are responsible for telling
the user why the shutdown was halted. the user why the shutdown was halted.
''' '''
return True return True

View File

@ -235,6 +235,10 @@ class AddAction(InterfaceAction):
self.gui.refresh_ondevice() self.gui.refresh_ondevice()
def add_books_from_device(self, view, paths=None): def add_books_from_device(self, view, paths=None):
backloading_err = self.gui.device_manager.device.BACKLOADING_ERROR_MESSAGE
if backloading_err is not None:
return error_dialog(self.gui, _('Add to library'), backloading_err,
show=True)
if paths is None: if paths is None:
rows = view.selectionModel().selectedRows() rows = view.selectionModel().selectedRows()
if not rows or len(rows) == 0: if not rows or len(rows) == 0:

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import os, shutil import os, shutil
from functools import partial from functools import partial
from PyQt4.Qt import QMenu, Qt, QInputDialog from PyQt4.Qt import QMenu, Qt, QInputDialog, QThread, pyqtSignal, QProgressDialog
from calibre import isbytestring from calibre import isbytestring
from calibre.constants import filesystem_encoding from calibre.constants import filesystem_encoding
@ -16,6 +16,7 @@ from calibre.utils.config import prefs
from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \ from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \
question_dialog, info_dialog question_dialog, info_dialog
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
from calibre.gui2.dialogs.check_library import CheckLibraryDialog
class LibraryUsageStats(object): # {{{ class LibraryUsageStats(object): # {{{
@ -75,6 +76,72 @@ class LibraryUsageStats(object): # {{{
self.write_stats() self.write_stats()
# }}} # }}}
# Check Integrity {{{
class VacThread(QThread):
check_done = pyqtSignal(object, object)
callback = pyqtSignal(object, object)
def __init__(self, parent, db):
QThread.__init__(self, parent)
self.db = db
self._parent = parent
def run(self):
err = bad = None
try:
bad = self.db.check_integrity(self.callbackf)
except:
import traceback
err = traceback.format_exc()
self.check_done.emit(bad, err)
def callbackf(self, progress, msg):
self.callback.emit(progress, msg)
class CheckIntegrity(QProgressDialog):
def __init__(self, db, parent=None):
QProgressDialog.__init__(self, parent)
self.db = db
self.setCancelButton(None)
self.setMinimum(0)
self.setMaximum(100)
self.setWindowTitle(_('Checking database integrity'))
self.setAutoReset(False)
self.setValue(0)
self.vthread = VacThread(self, db)
self.vthread.check_done.connect(self.check_done,
type=Qt.QueuedConnection)
self.vthread.callback.connect(self.callback, type=Qt.QueuedConnection)
self.vthread.start()
def callback(self, progress, msg):
self.setLabelText(msg)
self.setValue(int(100*progress))
def check_done(self, bad, err):
if err:
error_dialog(self, _('Error'),
_('Failed to check database integrity'),
det_msg=err, show=True)
elif bad:
titles = [self.db.title(x, index_is_id=True) for x in bad]
det_msg = '\n'.join(titles)
warning_dialog(self, _('Some inconsistencies found'),
_('The following books had formats listed in the '
'database that are not actually available. '
'The entries for the formats have been removed. '
'You should check them manually. This can '
'happen if you manipulate the files in the '
'library folder directly.'), det_msg=det_msg, show=True)
self.reset()
# }}}
class ChooseLibraryAction(InterfaceAction): class ChooseLibraryAction(InterfaceAction):
name = 'Choose Library' name = 'Choose Library'
@ -117,11 +184,28 @@ class ChooseLibraryAction(InterfaceAction):
self.rename_separator = self.choose_menu.addSeparator() self.rename_separator = self.choose_menu.addSeparator()
self.create_action(spec=(_('Library backup status...'), 'lt.png', None, self.maintenance_menu = QMenu(_('Library Maintenance'))
None), attr='action_backup_status') ac = self.create_action(spec=(_('Library metadata backup status'),
self.action_backup_status.triggered.connect(self.backup_status, 'lt.png', None, None), attr='action_backup_status')
type=Qt.QueuedConnection) ac.triggered.connect(self.backup_status, type=Qt.QueuedConnection)
self.choose_menu.addAction(self.action_backup_status) self.maintenance_menu.addAction(ac)
ac = self.create_action(spec=(_('Start backing up metadata of all books'),
'lt.png', None, None), attr='action_backup_metadata')
ac.triggered.connect(self.mark_dirty, type=Qt.QueuedConnection)
self.maintenance_menu.addAction(ac)
ac = self.create_action(spec=(_('Check library'), 'lt.png',
None, None), attr='action_check_library')
ac.triggered.connect(self.check_library, type=Qt.QueuedConnection)
self.maintenance_menu.addAction(ac)
ac = self.create_action(spec=(_('Check database integrity'), 'lt.png',
None, None), attr='action_check_database')
ac.triggered.connect(self.check_database, type=Qt.QueuedConnection)
self.maintenance_menu.addAction(ac)
ac = self.create_action(spec=(_('Recover database'), 'lt.png',
None, None), attr='action_restore_database')
ac.triggered.connect(self.restore_database, type=Qt.QueuedConnection)
self.maintenance_menu.addAction(ac)
self.choose_menu.addMenu(self.maintenance_menu)
def library_name(self): def library_name(self):
db = self.gui.library_view.model().db db = self.gui.library_view.model().db
@ -234,6 +318,37 @@ class ChooseLibraryAction(InterfaceAction):
_('Book metadata files remaining to be written: %s') % dirty_text, _('Book metadata files remaining to be written: %s') % dirty_text,
show=True) show=True)
def mark_dirty(self):
db = self.gui.library_view.model().db
db.dirtied(list(db.data.iterallids()))
info_dialog(self.gui, _('Backup metadata'),
_('Metadata will be backed up while calibre is running, at the '
'rate of approximately 1 book per second.'), show=True)
def check_library(self):
db = self.gui.library_view.model().db
d = CheckLibraryDialog(self.gui.parent(), db)
d.exec_()
def check_database(self, *args):
m = self.gui.library_view.model()
m.stop_metadata_backup()
try:
d = CheckIntegrity(m.db, self.gui)
d.exec_()
finally:
m.start_metadata_backup()
def restore_database(self):
info_dialog(self.gui, _('Recover database'), '<p>'+
_(
'This command rebuilds your calibre database from the information '
'stored by calibre in the OPF files.<p>'
'This function is not currently available in the GUI. You can '
'recover your database using the \'calibredb restore_database\' '
'command line function.'
), show=True)
def switch_requested(self, location): def switch_requested(self, location):
if not self.change_library_allowed(): if not self.change_library_allowed():
return return

View File

@ -18,7 +18,7 @@ from calibre.utils.config import prefs, tweaks
class Worker(Thread): class Worker(Thread):
def __init__(self, ids, db, loc, progress, done): def __init__(self, ids, db, loc, progress, done, delete_after):
Thread.__init__(self) Thread.__init__(self)
self.ids = ids self.ids = ids
self.processed = set([]) self.processed = set([])
@ -27,6 +27,7 @@ class Worker(Thread):
self.error = None self.error = None
self.progress = progress self.progress = progress
self.done = done self.done = done
self.delete_after = delete_after
def run(self): def run(self):
try: try:
@ -68,7 +69,8 @@ class Worker(Thread):
self.add_formats(identical_book, paths, newdb, replace=False) self.add_formats(identical_book, paths, newdb, replace=False)
if not added: if not added:
newdb.import_book(mi, paths, notify=False, import_hooks=False, newdb.import_book(mi, paths, notify=False, import_hooks=False,
apply_import_tags=tweaks['add_new_book_tags_when_importing_books']) apply_import_tags=tweaks['add_new_book_tags_when_importing_books'],
preserve_uuid=self.delete_after)
co = self.db.conversion_options(x, 'PIPE') co = self.db.conversion_options(x, 'PIPE')
if co is not None: if co is not None:
newdb.set_conversion_options(x, 'PIPE', co) newdb.set_conversion_options(x, 'PIPE', co)
@ -134,7 +136,8 @@ class CopyToLibraryAction(InterfaceAction):
self.pd.set_msg(_('Copying') + ' ' + title) self.pd.set_msg(_('Copying') + ' ' + title)
self.pd.set_value(idx) self.pd.set_value(idx)
self.worker = Worker(ids, db, loc, Dispatcher(progress), Dispatcher(self.pd.accept)) self.worker = Worker(ids, db, loc, Dispatcher(progress),
Dispatcher(self.pd.accept), delete_after)
self.worker.start() self.worker.start()
self.pd.exec_() self.pd.exec_()

View File

@ -169,7 +169,7 @@ class EditMetadataAction(InterfaceAction):
self.gui.tags_view.blockSignals(False) self.gui.tags_view.blockSignals(False)
if changed: if changed:
m = self.gui.library_view.model() m = self.gui.library_view.model()
m.resort(reset=False) m.refresh(reset=False)
m.research() m.research()
self.gui.tags_view.recount() self.gui.tags_view.recount()
if self.gui.cover_flow: if self.gui.cover_flow:

View File

@ -172,7 +172,7 @@ class MetadataWidget(Widget, Ui_Form):
if _file: if _file:
_file = os.path.abspath(_file) _file = os.path.abspath(_file)
if not os.access(_file, os.R_OK): if not os.access(_file, os.R_OK):
d = error_dialog(self.window, _('Cannot read'), d = error_dialog(self.parent(), _('Cannot read'),
_('You do not have permission to read the file: ') + _file) _('You do not have permission to read the file: ') + _file)
d.exec_() d.exec_()
return return
@ -181,14 +181,14 @@ class MetadataWidget(Widget, Ui_Form):
cf = open(_file, "rb") cf = open(_file, "rb")
cover = cf.read() cover = cf.read()
except IOError, e: except IOError, e:
d = error_dialog(self.window, _('Error reading file'), d = error_dialog(self.parent(), _('Error reading file'),
_("<p>There was an error reading from file: <br /><b>") + _file + "</b></p><br />"+str(e)) _("<p>There was an error reading from file: <br /><b>") + _file + "</b></p><br />"+str(e))
d.exec_() d.exec_()
if cover: if cover:
pix = QPixmap() pix = QPixmap()
pix.loadFromData(cover) pix.loadFromData(cover)
if pix.isNull(): if pix.isNull():
d = error_dialog(self.window, _('Error reading file'), d = error_dialog(self.parent(), _('Error reading file'),
_file + _(" is not a valid picture")) _file + _(" is not a valid picture"))
d.exec_() d.exec_()
else: else:

View File

@ -793,11 +793,17 @@ class DeviceMixin(object): # {{{
self.set_books_in_library(job.result, reset=True) self.set_books_in_library(job.result, reset=True)
mainlist, cardalist, cardblist = job.result mainlist, cardalist, cardblist = job.result
self.memory_view.set_database(mainlist) self.memory_view.set_database(mainlist)
self.memory_view.set_editable(self.device_manager.device.CAN_SET_METADATA) self.memory_view.set_editable(self.device_manager.device.CAN_SET_METADATA,
self.device_manager.device.BACKLOADING_ERROR_MESSAGE
is None)
self.card_a_view.set_database(cardalist) self.card_a_view.set_database(cardalist)
self.card_a_view.set_editable(self.device_manager.device.CAN_SET_METADATA) self.card_a_view.set_editable(self.device_manager.device.CAN_SET_METADATA,
self.device_manager.device.BACKLOADING_ERROR_MESSAGE
is None)
self.card_b_view.set_database(cardblist) self.card_b_view.set_database(cardblist)
self.card_b_view.set_editable(self.device_manager.device.CAN_SET_METADATA) self.card_b_view.set_editable(self.device_manager.device.CAN_SET_METADATA,
self.device_manager.device.BACKLOADING_ERROR_MESSAGE
is None)
self.sync_news() self.sync_news()
self.sync_catalogs() self.sync_catalogs()
self.refresh_ondevice() self.refresh_ondevice()
@ -1413,15 +1419,16 @@ class DeviceMixin(object): # {{{
# Force a reset if the caches are not initialized # Force a reset if the caches are not initialized
if reset or not hasattr(self, 'db_book_title_cache'): if reset or not hasattr(self, 'db_book_title_cache'):
# Build a cache (map) of the library, so the search isn't On**2
self.db_book_title_cache = {}
self.db_book_uuid_cache = {}
# It might be possible to get here without having initialized the # It might be possible to get here without having initialized the
# library view. In this case, simply give up # library view. In this case, simply give up
try: try:
db = self.library_view.model().db db = self.library_view.model().db
except: except:
return False return False
# Build a cache (map) of the library, so the search isn't On**2
self.db_book_title_cache = {}
self.db_book_uuid_cache = {}
for id in db.data.iterallids(): for id in db.data.iterallids():
mi = db.get_metadata(id, index_is_id=True) mi = db.get_metadata(id, index_is_id=True)
title = clean_string(mi.title) title = clean_string(mi.title)
@ -1455,7 +1462,7 @@ class DeviceMixin(object): # {{{
if update_metadata: if update_metadata:
book.smart_update(self.db_book_uuid_cache[book.uuid], book.smart_update(self.db_book_uuid_cache[book.uuid],
replace_metadata=True) replace_metadata=True)
book.in_library = True book.in_library = 'UUID'
# ensure that the correct application_id is set # ensure that the correct application_id is set
book.application_id = \ book.application_id = \
self.db_book_uuid_cache[book.uuid].application_id self.db_book_uuid_cache[book.uuid].application_id
@ -1468,21 +1475,21 @@ class DeviceMixin(object): # {{{
# will match if any of the db_id, author, or author_sort # will match if any of the db_id, author, or author_sort
# also match. # also match.
if getattr(book, 'application_id', None) in d['db_ids']: if getattr(book, 'application_id', None) in d['db_ids']:
book.in_library = True
# app_id already matches a db_id. No need to set it. # app_id already matches a db_id. No need to set it.
if update_metadata: if update_metadata:
book.smart_update(d['db_ids'][book.application_id], book.smart_update(d['db_ids'][book.application_id],
replace_metadata=True) replace_metadata=True)
book.in_library = 'APP_ID'
continue continue
# Sonys know their db_id independent of the application_id # Sonys know their db_id independent of the application_id
# in the metadata cache. Check that as well. # in the metadata cache. Check that as well.
if getattr(book, 'db_id', None) in d['db_ids']: if getattr(book, 'db_id', None) in d['db_ids']:
book.in_library = True
book.application_id = \
d['db_ids'][book.db_id].application_id
if update_metadata: if update_metadata:
book.smart_update(d['db_ids'][book.db_id], book.smart_update(d['db_ids'][book.db_id],
replace_metadata=True) replace_metadata=True)
book.in_library = 'DB_ID'
book.application_id = \
d['db_ids'][book.db_id].application_id
continue continue
# We now know that the application_id is not right. Set it # We now know that the application_id is not right. Set it
# to None to prevent book_on_device from accidentally # to None to prevent book_on_device from accidentally
@ -1494,19 +1501,19 @@ class DeviceMixin(object): # {{{
# either can appear as the author # either can appear as the author
book_authors = clean_string(authors_to_string(book.authors)) book_authors = clean_string(authors_to_string(book.authors))
if book_authors in d['authors']: if book_authors in d['authors']:
book.in_library = True
book.application_id = \
d['authors'][book_authors].application_id
if update_metadata: if update_metadata:
book.smart_update(d['authors'][book_authors], book.smart_update(d['authors'][book_authors],
replace_metadata=True) replace_metadata=True)
elif book_authors in d['author_sort']: book.in_library = 'AUTHOR'
book.in_library = True
book.application_id = \ book.application_id = \
d['author_sort'][book_authors].application_id d['authors'][book_authors].application_id
elif book_authors in d['author_sort']:
if update_metadata: if update_metadata:
book.smart_update(d['author_sort'][book_authors], book.smart_update(d['author_sort'][book_authors],
replace_metadata=True) replace_metadata=True)
book.in_library = 'AUTH_SORT'
book.application_id = \
d['author_sort'][book_authors].application_id
else: else:
# Book definitely not matched. Clear its application ID # Book definitely not matched. Clear its application ID
book.application_id = None book.application_id = None

View File

@ -32,7 +32,7 @@ class CheckLibraryDialog(QDialog):
self.copy = QPushButton(_('Copy to clipboard')) self.copy = QPushButton(_('Copy to clipboard'))
self.copy.setDefault(False) self.copy.setDefault(False)
self.copy.clicked.connect(self.copy_to_clipboard) self.copy.clicked.connect(self.copy_to_clipboard)
self.ok = QPushButton('&OK') self.ok = QPushButton('&Done')
self.ok.setDefault(True) self.ok.setDefault(True)
self.ok.clicked.connect(self.accept) self.ok.clicked.connect(self.accept)
self.cancel = QPushButton('&Cancel') self.cancel = QPushButton('&Cancel')

View File

@ -263,7 +263,7 @@
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>20</width> <width>20</width>
<height>00</height> <height>0</height>
</size> </size>
</property> </property>
</spacer> </spacer>
@ -357,13 +357,13 @@ from the value in the box</string>
</item> </item>
<item row="12" column="0" colspan="2"> <item row="12" column="0" colspan="2">
<widget class="QCheckBox" name="change_title_to_title_case"> <widget class="QCheckBox" name="change_title_to_title_case">
<property name="text">
<string>Change title to title case</string>
</property>
<property name="toolTip"> <property name="toolTip">
<string>Force the title to be in title case. If both this and swap authors are checked, <string>Force the title to be in title case. If both this and swap authors are checked,
title and author are swapped before the title case is set</string> title and author are swapped before the title case is set</string>
</property> </property>
<property name="text">
<string>Change title to title case</string>
</property>
</widget> </widget>
</item> </item>
<item row="10" column="0" colspan="2"> <item row="10" column="0" colspan="2">
@ -486,15 +486,15 @@ Future conversion of these books will use the default settings.</string>
</item> </item>
<item row="4" column="1"> <item row="4" column="1">
<widget class="HistoryLineEdit" name="search_for"> <widget class="HistoryLineEdit" name="search_for">
<property name="toolTip">
<string>Enter the what you are looking for, either plain text or a regular expression, depending on the mode</string>
</property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>100</horstretch> <horstretch>100</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="toolTip">
<string>Enter the what you are looking for, either plain text or a regular expression, depending on the mode</string>
</property>
</widget> </widget>
</item> </item>
<item row="4" column="2"> <item row="4" column="2">
@ -656,6 +656,14 @@ nothing should be put between the original text and the inserted text</string>
<bool>true</bool> <bool>true</bool>
</property> </property>
<widget class="QWidget" name="gridLayoutWidget_2"> <widget class="QWidget" name="gridLayoutWidget_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>122</width>
<height>34</height>
</rect>
</property>
<layout class="QGridLayout" name="testgrid"> <layout class="QGridLayout" name="testgrid">
<item row="8" column="0"> <item row="8" column="0">
<widget class="QLabel" name="label_31"> <widget class="QLabel" name="label_31">
@ -733,14 +741,33 @@ nothing should be put between the original text and the inserted text</string>
<tabstop>author_sort</tabstop> <tabstop>author_sort</tabstop>
<tabstop>rating</tabstop> <tabstop>rating</tabstop>
<tabstop>publisher</tabstop> <tabstop>publisher</tabstop>
<tabstop>tag_editor_button</tabstop>
<tabstop>tags</tabstop> <tabstop>tags</tabstop>
<tabstop>tag_editor_button</tabstop>
<tabstop>remove_tags</tabstop> <tabstop>remove_tags</tabstop>
<tabstop>remove_all_tags</tabstop>
<tabstop>series</tabstop> <tabstop>series</tabstop>
<tabstop>clear_series</tabstop>
<tabstop>autonumber_series</tabstop> <tabstop>autonumber_series</tabstop>
<tabstop>series_numbering_restarts</tabstop>
<tabstop>series_start_number</tabstop>
<tabstop>remove_format</tabstop> <tabstop>remove_format</tabstop>
<tabstop>remove_conversion_settings</tabstop>
<tabstop>swap_title_and_author</tabstop> <tabstop>swap_title_and_author</tabstop>
<tabstop>change_title_to_title_case</tabstop>
<tabstop>button_box</tabstop> <tabstop>button_box</tabstop>
<tabstop>central_widget</tabstop>
<tabstop>search_field</tabstop>
<tabstop>search_mode</tabstop>
<tabstop>search_for</tabstop>
<tabstop>case_sensitive</tabstop>
<tabstop>replace_with</tabstop>
<tabstop>replace_func</tabstop>
<tabstop>destination_field</tabstop>
<tabstop>replace_mode</tabstop>
<tabstop>comma_separated</tabstop>
<tabstop>scrollArea11</tabstop>
<tabstop>test_text</tabstop>
<tabstop>test_result</tabstop>
</tabstops> </tabstops>
<resources> <resources>
<include location="../../../../resources/images.qrc"/> <include location="../../../../resources/images.qrc"/>

View File

@ -25,7 +25,7 @@ from calibre.ebooks.metadata.covers import download_cover
from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.meta import get_metadata
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
from calibre.utils.config import prefs, tweaks from calibre.utils.config import prefs, tweaks
from calibre.utils.date import qt_to_dt, local_tz, utcfromtimestamp from calibre.utils.date import qt_to_dt, local_tz, utcfromtimestamp, utc_tz
from calibre.customize.ui import run_plugins_on_import, get_isbndb_key from calibre.customize.ui import run_plugins_on_import, get_isbndb_key
from calibre.gui2.preferences.social import SocialMetadata from calibre.gui2.preferences.social import SocialMetadata
from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.gui2.custom_column_widgets import populate_metadata_page
@ -434,9 +434,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.pubdate.setDate(QDate(pubdate.year, pubdate.month, self.pubdate.setDate(QDate(pubdate.year, pubdate.month,
pubdate.day)) pubdate.day))
timestamp = db.timestamp(self.id, index_is_id=True) timestamp = db.timestamp(self.id, index_is_id=True)
self.orig_timestamp = timestamp
self.date.setDate(QDate(timestamp.year, timestamp.month, self.date.setDate(QDate(timestamp.year, timestamp.month,
timestamp.day)) timestamp.day))
self.orig_date = qt_to_dt(self.date.date())
exts = self.db.formats(row) exts = self.db.formats(row)
if exts: if exts:
@ -802,7 +802,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.db.set_pubdate(self.id, d, notify=False, commit=False) self.db.set_pubdate(self.id, d, notify=False, commit=False)
d = self.date.date() d = self.date.date()
d = qt_to_dt(d) d = qt_to_dt(d)
if d.date() != self.orig_timestamp.date(): if d != self.orig_date:
self.db.set_timestamp(self.id, d, notify=False, commit=False) self.db.set_timestamp(self.id, d, notify=False, commit=False)
self.db.commit() self.db.commit()

View File

@ -38,7 +38,10 @@ class CustomRecipeModel(QAbstractListModel):
return False return False
def rowCount(self, *args): def rowCount(self, *args):
try:
return len(self.recipe_model.custom_recipe_collection) return len(self.recipe_model.custom_recipe_collection)
except:
return 0
def data(self, index, role): def data(self, index, role):
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
@ -100,6 +103,8 @@ class UserProfiles(ResizableDialog, Ui_Dialog):
def break_cycles(self): def break_cycles(self):
self.recipe_model = self._model.recipe_model = None self.recipe_model = self._model.recipe_model = None
self.available_profiles = None
self.model = self._model = None
def remove_selected_items(self): def remove_selected_items(self):
indices = self.available_profiles.selectionModel().selectedRows() indices = self.available_profiles.selectionModel().selectedRows()

View File

@ -24,7 +24,7 @@ from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
REGEXP_MATCH, CoverCache, MetadataBackup REGEXP_MATCH, CoverCache, MetadataBackup
from calibre.library.cli import parse_series_string from calibre.library.cli import parse_series_string
from calibre import strftime, isbytestring, prepare_string_for_xml from calibre import strftime, isbytestring, prepare_string_for_xml
from calibre.constants import filesystem_encoding from calibre.constants import filesystem_encoding, DEBUG
from calibre.gui2.library import DEFAULT_SORT from calibre.gui2.library import DEFAULT_SORT
def human_readable(size, precision=1): def human_readable(size, precision=1):
@ -699,6 +699,10 @@ class BooksModel(QAbstractTableModel): # {{{
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
return QVariant(self.headers[self.column_map[section]]) return QVariant(self.headers[self.column_map[section]])
return NONE return NONE
if DEBUG and role == Qt.ToolTipRole and orientation == Qt.Vertical:
col = self.db.field_metadata['uuid']['rec_index']
return QVariant(_('This book\'s UUID is "{0}"').format(self.db.data[section][col]))
if role == Qt.DisplayRole: # orientation is vertical if role == Qt.DisplayRole: # orientation is vertical
return QVariant(section+1) return QVariant(section+1)
return NONE return NONE
@ -1206,6 +1210,8 @@ class DeviceBooksModel(BooksModel): # {{{
if tags: if tags:
tags.sort(cmp=lambda x,y: cmp(x.lower(), y.lower())) tags.sort(cmp=lambda x,y: cmp(x.lower(), y.lower()))
return QVariant(', '.join(tags)) return QVariant(', '.join(tags))
elif DEBUG and cname == 'inlibrary':
return QVariant(self.db[self.map[row]].in_library)
elif role == Qt.ToolTipRole and index.isValid(): elif role == Qt.ToolTipRole and index.isValid():
if self.map[row] in self.indices_to_be_deleted(): if self.map[row] in self.indices_to_be_deleted():
return QVariant(_('Marked for deletion')) return QVariant(_('Marked for deletion'))
@ -1227,8 +1233,10 @@ class DeviceBooksModel(BooksModel): # {{{
return NONE return NONE
def headerData(self, section, orientation, role): def headerData(self, section, orientation, role):
if role == Qt.ToolTipRole: if role == Qt.ToolTipRole and orientation == Qt.Horizontal:
return QVariant(_('The lookup/search name is "{0}"').format(self.column_map[section])) return QVariant(_('The lookup/search name is "{0}"').format(self.column_map[section]))
if DEBUG and role == Qt.ToolTipRole and orientation == Qt.Vertical:
return QVariant(_('This book\'s UUID is "{0}"').format(self.db[self.map[section]].uuid))
if role != Qt.DisplayRole: if role != Qt.DisplayRole:
return NONE return NONE
if orientation == Qt.Horizontal: if orientation == Qt.Horizontal:

View File

@ -30,6 +30,7 @@ class BooksView(QTableView): # {{{
def __init__(self, parent, modelcls=BooksModel): def __init__(self, parent, modelcls=BooksModel):
QTableView.__init__(self, parent) QTableView.__init__(self, parent)
self.drag_allowed = True
self.setDragEnabled(True) self.setDragEnabled(True)
self.setDragDropOverwriteMode(False) self.setDragDropOverwriteMode(False)
self.setDragDropMode(self.DragDrop) self.setDragDropMode(self.DragDrop)
@ -505,6 +506,8 @@ class BooksView(QTableView): # {{{
return QTableView.mousePressEvent(self, event) return QTableView.mousePressEvent(self, event)
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
if not self.drag_allowed:
return
if self.drag_start_pos is None: if self.drag_start_pos is None:
return QTableView.mouseMoveEvent(self, event) return QTableView.mouseMoveEvent(self, event)
@ -613,7 +616,7 @@ class BooksView(QTableView): # {{{
def close(self): def close(self):
self._model.close() self._model.close()
def set_editable(self, editable): def set_editable(self, editable, supports_backloading):
self._model.set_editable(editable) self._model.set_editable(editable)
def connect_to_search_box(self, sb, search_done): def connect_to_search_box(self, sb, search_done):
@ -700,5 +703,9 @@ class DeviceBooksView(BooksView): # {{{
error_dialog(self, _('Not allowed'), error_dialog(self, _('Not allowed'),
_('Dropping onto a device is not supported. First add the book to the calibre library.')).exec_() _('Dropping onto a device is not supported. First add the book to the calibre library.')).exec_()
def set_editable(self, editable, supports_backloading):
self._model.set_editable(editable)
self.drag_allowed = supports_backloading
# }}} # }}}

View File

@ -5,81 +5,14 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from PyQt4.Qt import QProgressDialog, QThread, Qt, pyqtSignal
from calibre.gui2.dialogs.check_library import CheckLibraryDialog
from calibre.gui2.preferences import ConfigWidgetBase, test_widget from calibre.gui2.preferences import ConfigWidgetBase, test_widget
from calibre.gui2.preferences.misc_ui import Ui_Form from calibre.gui2.preferences.misc_ui import Ui_Form
from calibre.gui2 import error_dialog, config, warning_dialog, \ from calibre.gui2 import error_dialog, config, open_local_file, info_dialog
open_local_file, info_dialog
from calibre.constants import isosx from calibre.constants import isosx
# Check Integrity {{{ # Check Integrity {{{
class VacThread(QThread):
check_done = pyqtSignal(object, object)
callback = pyqtSignal(object, object)
def __init__(self, parent, db):
QThread.__init__(self, parent)
self.db = db
self._parent = parent
def run(self):
err = bad = None
try:
bad = self.db.check_integrity(self.callbackf)
except:
import traceback
err = traceback.format_exc()
self.check_done.emit(bad, err)
def callbackf(self, progress, msg):
self.callback.emit(progress, msg)
class CheckIntegrity(QProgressDialog):
def __init__(self, db, parent=None):
QProgressDialog.__init__(self, parent)
self.db = db
self.setCancelButton(None)
self.setMinimum(0)
self.setMaximum(100)
self.setWindowTitle(_('Checking database integrity'))
self.setAutoReset(False)
self.setValue(0)
self.vthread = VacThread(self, db)
self.vthread.check_done.connect(self.check_done,
type=Qt.QueuedConnection)
self.vthread.callback.connect(self.callback, type=Qt.QueuedConnection)
self.vthread.start()
def callback(self, progress, msg):
self.setLabelText(msg)
self.setValue(int(100*progress))
def check_done(self, bad, err):
if err:
error_dialog(self, _('Error'),
_('Failed to check database integrity'),
det_msg=err, show=True)
elif bad:
titles = [self.db.title(x, index_is_id=True) for x in bad]
det_msg = '\n'.join(titles)
warning_dialog(self, _('Some inconsistencies found'),
_('The following books had formats listed in the '
'database that are not actually available. '
'The entries for the formats have been removed. '
'You should check them manually. This can '
'happen if you manipulate the files in the '
'library folder directly.'), det_msg=det_msg, show=True)
self.reset()
# }}}
class ConfigWidget(ConfigWidgetBase, Ui_Form): class ConfigWidget(ConfigWidgetBase, Ui_Form):
def genesis(self, gui): def genesis(self, gui):
@ -88,39 +21,15 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('worker_limit', config, restart_required=True) r('worker_limit', config, restart_required=True)
r('enforce_cpu_limit', config, restart_required=True) r('enforce_cpu_limit', config, restart_required=True)
self.device_detection_button.clicked.connect(self.debug_device_detection) self.device_detection_button.clicked.connect(self.debug_device_detection)
self.compact_button.clicked.connect(self.compact)
self.button_all_books_dirty.clicked.connect(self.mark_dirty)
self.button_check_library.clicked.connect(self.check_library)
self.button_open_config_dir.clicked.connect(self.open_config_dir) self.button_open_config_dir.clicked.connect(self.open_config_dir)
self.button_osx_symlinks.clicked.connect(self.create_symlinks) self.button_osx_symlinks.clicked.connect(self.create_symlinks)
self.button_osx_symlinks.setVisible(isosx) self.button_osx_symlinks.setVisible(isosx)
def mark_dirty(self):
db = self.gui.library_view.model().db
db.dirtied(list(db.data.iterallids()))
info_dialog(self, _('Backup metadata'),
_('Metadata will be backed up while calibre is running, at the '
'rate of 30 books per minute.'), show=True)
def check_library(self):
db = self.gui.library_view.model().db
d = CheckLibraryDialog(self.gui.parent(), db)
d.exec_()
def debug_device_detection(self, *args): def debug_device_detection(self, *args):
from calibre.gui2.preferences.device_debug import DebugDevice from calibre.gui2.preferences.device_debug import DebugDevice
d = DebugDevice(self) d = DebugDevice(self)
d.exec_() d.exec_()
def compact(self, *args):
m = self.gui.library_view.model()
m.stop_metadata_backup()
try:
d = CheckIntegrity(m.db, self)
d.exec_()
finally:
m.start_metadata_backup()
def open_config_dir(self, *args): def open_config_dir(self, *args):
from calibre.utils.config import config_dir from calibre.utils.config import config_dir
open_local_file(config_dir) open_local_file(config_dir)

View File

@ -77,13 +77,6 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="5" column="0" colspan="2">
<widget class="QPushButton" name="compact_button">
<property name="text">
<string>&amp;Check database integrity</string>
</property>
</widget>
</item>
<item row="6" column="0"> <item row="6" column="0">
<spacer name="verticalSpacer_7"> <spacer name="verticalSpacer_7">
<property name="orientation"> <property name="orientation">
@ -124,20 +117,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="10" column="0" colspan="2">
<widget class="QPushButton" name="button_all_books_dirty">
<property name="text">
<string>Back up metadata of all books</string>
</property>
</widget>
</item>
<item row="11" column="0" colspan="2">
<widget class="QPushButton" name="button_check_library">
<property name="text">
<string>Check the library folders for potential problems</string>
</property>
</widget>
</item>
<item row="20" column="0"> <item row="20" column="0">
<spacer name="verticalSpacer_9"> <spacer name="verticalSpacer_9">
<property name="orientation"> <property name="orientation">

View File

@ -73,6 +73,8 @@ class SearchBox2(QComboBox):
self.normal_background = 'rgb(255, 255, 255, 0%)' self.normal_background = 'rgb(255, 255, 255, 0%)'
self.line_edit = SearchLineEdit(self) self.line_edit = SearchLineEdit(self)
self.setLineEdit(self.line_edit) self.setLineEdit(self.line_edit)
c = self.line_edit.completer()
c.setCompletionMode(c.PopupCompletion)
self.line_edit.key_pressed.connect(self.key_pressed, self.line_edit.key_pressed.connect(self.key_pressed,
type=Qt.DirectConnection) type=Qt.DirectConnection)
self.line_edit.mouse_released.connect(self.mouse_released, self.line_edit.mouse_released.connect(self.mouse_released,

View File

@ -84,12 +84,14 @@ class TagsView(QTreeView): # {{{
self.setAcceptDrops(True) self.setAcceptDrops(True)
self.setDragDropMode(self.DropOnly) self.setDragDropMode(self.DropOnly)
self.setDropIndicatorShown(True) self.setDropIndicatorShown(True)
self.setAutoExpandDelay(500)
def set_database(self, db, tag_match, sort_by): def set_database(self, db, tag_match, sort_by):
self.hidden_categories = config['tag_browser_hidden_categories'] self.hidden_categories = config['tag_browser_hidden_categories']
self._model = TagsModel(db, parent=self, self._model = TagsModel(db, parent=self,
hidden_categories=self.hidden_categories, hidden_categories=self.hidden_categories,
search_restriction=None) search_restriction=None,
drag_drop_finished=self.drag_drop_finished)
self.sort_by = sort_by self.sort_by = sort_by
self.tag_match = tag_match self.tag_match = tag_match
self.db = db self.db = db
@ -109,103 +111,6 @@ class TagsView(QTreeView): # {{{
def database_changed(self, event, ids): def database_changed(self, event, ids):
self.refresh_required.emit() self.refresh_required.emit()
def dragEnterEvent(self, event):
md = event.mimeData()
if md.hasFormat("application/calibre+from_library"):
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
allowed = False
idx = self.indexAt(event.pos())
m = self.model()
p = m.parent(idx)
if idx.isValid() and p.isValid():
item = m.data(p, Qt.UserRole)
fm = self.db.metadata_for_field(item.category_key)
if item.category_key in \
('tags', 'series', 'authors', 'rating', 'publisher') or\
(fm['is_custom'] and \
fm['datatype'] in ['text', 'rating', 'series']):
allowed = True
if allowed:
event.acceptProposedAction()
else:
event.ignore()
def dropEvent(self, event):
idx = self.indexAt(event.pos())
m = self.model()
p = m.parent(idx)
if idx.isValid() and p.isValid():
item = m.data(p, Qt.UserRole)
if item.type == TagTreeItem.CATEGORY:
fm = self.db.metadata_for_field(item.category_key)
if item.category_key in \
('tags', 'series', 'authors', 'rating', 'publisher') or\
(fm['is_custom'] and \
fm['datatype'] in ['text', 'rating', 'series']):
child = m.data(idx, Qt.UserRole)
md = event.mimeData()
mime = 'application/calibre+from_library'
ids = list(map(int, str(md.data(mime)).split()))
self.handle_drop(item, child, ids)
event.accept()
return
event.ignore()
def handle_drop(self, parent, child, ids):
# print 'Dropped ids:', ids, parent.category_key, child.tag.name
key = parent.category_key
if (key == 'authors' and len(ids) >= 5):
if not confirm('<p>'+_('Changing the authors for several books can '
'take a while. Are you sure?')
+'</p>', 'tag_browser_drop_authors', self):
return
elif len(ids) > 15:
if not confirm('<p>'+_('Changing the metadata for that many books '
'can take a while. Are you sure?')
+'</p>', 'tag_browser_many_changes', self):
return
fm = self.db.metadata_for_field(key)
is_multiple = fm['is_multiple']
val = child.tag.name
for id in ids:
mi = self.db.get_metadata(id, index_is_id=True)
# Prepare to ignore the author, unless it is changed. Title is
# always ignored -- see the call to set_metadata
set_authors = False
# Author_sort cannot change explicitly. Changing the author might
# change it.
mi.author_sort = None # Never will change by itself.
if key == 'authors':
mi.authors = [val]
set_authors=True
elif fm['datatype'] == 'rating':
mi.set(key, len(val) * 2)
elif fm['is_custom'] and fm['datatype'] == 'series':
mi.set(key, val, extra=1.0)
elif is_multiple:
new_val = mi.get(key, [])
if val in new_val:
# Fortunately, only one field can change, so the continue
# won't break anything
continue
new_val.append(val)
mi.set(key, new_val)
else:
mi.set(key, val)
self.db.set_metadata(id, mi, set_title=False,
set_authors=set_authors, commit=False)
self.db.commit()
self.drag_drop_finished.emit(ids)
@property @property
def match_all(self): def match_all(self):
return self.tag_match and self.tag_match.currentIndex() > 0 return self.tag_match and self.tag_match.currentIndex() > 0
@ -374,7 +279,8 @@ class TagsView(QTreeView): # {{{
try: try:
self._model = TagsModel(self.db, parent=self, self._model = TagsModel(self.db, parent=self,
hidden_categories=self.hidden_categories, hidden_categories=self.hidden_categories,
search_restriction=self.search_restriction) search_restriction=self.search_restriction,
drag_drop_finished=self.drag_drop_finished)
self.setModel(self._model) self.setModel(self._model)
except: except:
# The DB must be gone. Set the model to None and hope that someone # The DB must be gone. Set the model to None and hope that someone
@ -469,7 +375,8 @@ class TagTreeItem(object): # {{{
class TagsModel(QAbstractItemModel): # {{{ class TagsModel(QAbstractItemModel): # {{{
def __init__(self, db, parent, hidden_categories=None, search_restriction=None): def __init__(self, db, parent, hidden_categories=None,
search_restriction=None, drag_drop_finished=None):
QAbstractItemModel.__init__(self, parent) QAbstractItemModel.__init__(self, parent)
# must do this here because 'QPixmap: Must construct a QApplication # must do this here because 'QPixmap: Must construct a QApplication
@ -487,6 +394,7 @@ class TagsModel(QAbstractItemModel): # {{{
':user' : QIcon(I('drawer.png')), ':user' : QIcon(I('drawer.png')),
'search' : QIcon(I('search.png'))}) 'search' : QIcon(I('search.png'))})
self.categories_with_ratings = ['authors', 'series', 'publisher', 'tags'] self.categories_with_ratings = ['authors', 'series', 'publisher', 'tags']
self.drag_drop_finished = drag_drop_finished
self.icon_state_map = [None, QIcon(I('plus.png')), QIcon(I('minus.png'))] self.icon_state_map = [None, QIcon(I('plus.png')), QIcon(I('minus.png'))]
self.db = db self.db = db
@ -519,6 +427,79 @@ class TagsModel(QAbstractItemModel): # {{{
tag.avg_rating = None tag.avg_rating = None
TagTreeItem(parent=c, data=tag, icon_map=self.icon_state_map) TagTreeItem(parent=c, data=tag, icon_map=self.icon_state_map)
def mimeTypes(self):
return ["application/calibre+from_library"]
def dropMimeData(self, md, action, row, column, parent):
if not md.hasFormat("application/calibre+from_library") or \
action != Qt.CopyAction:
return False
idx = parent
if idx.isValid():
node = self.data(idx, Qt.UserRole)
if node.type == TagTreeItem.TAG:
fm = self.db.metadata_for_field(node.tag.category)
if node.tag.category in \
('tags', 'series', 'authors', 'rating', 'publisher') or \
(fm['is_custom'] and \
fm['datatype'] in ['text', 'rating', 'series']):
mime = 'application/calibre+from_library'
ids = list(map(int, str(md.data(mime)).split()))
self.handle_drop(node, ids)
return True
return False
def handle_drop(self, on_node, ids):
#print 'Dropped ids:', ids, on_node.tag
key = on_node.tag.category
if (key == 'authors' and len(ids) >= 5):
if not confirm('<p>'+_('Changing the authors for several books can '
'take a while. Are you sure?')
+'</p>', 'tag_browser_drop_authors', self.parent()):
return
elif len(ids) > 15:
if not confirm('<p>'+_('Changing the metadata for that many books '
'can take a while. Are you sure?')
+'</p>', 'tag_browser_many_changes', self.parent()):
return
fm = self.db.metadata_for_field(key)
is_multiple = fm['is_multiple']
val = on_node.tag.name
for id in ids:
mi = self.db.get_metadata(id, index_is_id=True)
# Prepare to ignore the author, unless it is changed. Title is
# always ignored -- see the call to set_metadata
set_authors = False
# Author_sort cannot change explicitly. Changing the author might
# change it.
mi.author_sort = None # Never will change by itself.
if key == 'authors':
mi.authors = [val]
set_authors=True
elif fm['datatype'] == 'rating':
mi.set(key, len(val) * 2)
elif fm['is_custom'] and fm['datatype'] == 'series':
mi.set(key, val, extra=1.0)
elif is_multiple:
new_val = mi.get(key, [])
if val in new_val:
# Fortunately, only one field can change, so the continue
# won't break anything
continue
new_val.append(val)
mi.set(key, new_val)
else:
mi.set(key, val)
self.db.set_metadata(id, mi, set_title=False,
set_authors=set_authors, commit=False)
self.db.commit()
self.drag_drop_finished.emit(ids)
def set_search_restriction(self, s): def set_search_restriction(self, s):
self.search_restriction = s self.search_restriction = s
@ -650,12 +631,19 @@ class TagsModel(QAbstractItemModel): # {{{
def flags(self, index, *args): def flags(self, index, *args):
ans = Qt.ItemIsEnabled|Qt.ItemIsSelectable|Qt.ItemIsEditable ans = Qt.ItemIsEnabled|Qt.ItemIsSelectable|Qt.ItemIsEditable
if index.isValid() and self.parent(index).isValid(): if index.isValid():
node = self.data(index, Qt.UserRole)
if node.type == TagTreeItem.TAG:
fm = self.db.metadata_for_field(node.tag.category)
if node.tag.category in \
('tags', 'series', 'authors', 'rating', 'publisher') or \
(fm['is_custom'] and \
fm['datatype'] in ['text', 'rating', 'series']):
ans |= Qt.ItemIsDropEnabled ans |= Qt.ItemIsDropEnabled
return ans return ans
def supportedDropActions(self): def supportedDropActions(self):
return Qt.CopyAction|Qt.MoveAction return Qt.CopyAction
def path_for_index(self, index): def path_for_index(self, index):
ans = [] ans = []
@ -836,11 +824,11 @@ class TagBrowserMixin(object): # {{{
rename_func = partial(db.rename_custom_item, label=cc_label) rename_func = partial(db.rename_custom_item, label=cc_label)
delete_func = partial(db.delete_custom_item_using_id, label=cc_label) delete_func = partial(db.delete_custom_item_using_id, label=cc_label)
if rename_func: if rename_func:
for item in to_delete:
delete_func(item)
for text in to_rename: for text in to_rename:
for old_id in to_rename[text]: for old_id in to_rename[text]:
rename_func(old_id, new_name=unicode(text)) rename_func(old_id, new_name=unicode(text))
for item in to_delete:
delete_func(item)
# Clean up everything, as information could have changed for many books. # Clean up everything, as information could have changed for many books.
self.library_view.model().refresh() self.library_view.model().refresh()

View File

@ -3,13 +3,14 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import traceback import traceback
from PyQt4.Qt import QThread, pyqtSignal, Qt, QUrl from PyQt4.Qt import QThread, pyqtSignal, Qt, QUrl, QDialog, QGridLayout, \
QLabel, QCheckBox, QDialogButtonBox, QIcon, QPixmap
import mechanize import mechanize
from calibre.constants import __appname__, __version__, iswindows, isosx from calibre.constants import __appname__, __version__, iswindows, isosx
from calibre import browser from calibre import browser
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.gui2 import config, dynamic, question_dialog, open_url from calibre.gui2 import config, dynamic, open_url
URL = 'http://status.calibre-ebook.com/latest' URL = 'http://status.calibre-ebook.com/latest'
@ -37,6 +38,53 @@ class CheckForUpdates(QThread):
traceback.print_exc() traceback.print_exc()
self.sleep(self.INTERVAL) self.sleep(self.INTERVAL)
class UpdateNotification(QDialog):
def __init__(self, version, parent=None):
QDialog.__init__(self, parent)
self.resize(400, 250)
self.l = QGridLayout()
self.setLayout(self.l)
self.logo = QLabel()
self.logo.setMaximumWidth(110)
self.logo.setPixmap(QPixmap(I('lt.png')).scaled(100, 100,
Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
self.label = QLabel('<p>'+
_('%s has been updated to version <b>%s</b>. '
'See the <a href="http://calibre-ebook.com/whats-new'
'">new features</a>. Visit the download pa'
'ge?')%(__appname__, version))
self.label.setOpenExternalLinks(True)
self.label.setWordWrap(True)
self.setWindowTitle(_('Update available!'))
self.setWindowIcon(QIcon(I('lt.png')))
self.l.addWidget(self.logo, 0, 0)
self.l.addWidget(self.label, 0, 1)
self.cb = QCheckBox(
_('Show this notification for future updates'), self)
self.l.addWidget(self.cb, 1, 0, 1, -1)
self.cb.setChecked(config.get('new_version_notification'))
self.cb.stateChanged.connect(self.show_future)
self.bb = QDialogButtonBox(self)
b = self.bb.addButton(_('&Get update'), self.bb.AcceptRole)
b.setDefault(True)
b.setIcon(QIcon(I('arrow-down.png')))
self.bb.addButton(self.bb.Cancel)
self.l.addWidget(self.bb, 2, 0, 1, -1)
self.bb.accepted.connect(self.accept)
self.bb.rejected.connect(self.reject)
dynamic.set('update to version %s'%version, False)
def show_future(self, *args):
config.set('new_version_notification', bool(self.cb.isChecked()))
def accept(self):
url = 'http://calibre-ebook.com/download_'+\
('windows' if iswindows else 'osx' if isosx else 'linux')
open_url(QUrl(url))
QDialog.accept(self)
class UpdateMixin(object): class UpdateMixin(object):
def __init__(self, opts): def __init__(self, opts):
@ -53,15 +101,8 @@ class UpdateMixin(object):
if config.get('new_version_notification') and \ if config.get('new_version_notification') and \
dynamic.get('update to version %s'%version, True): dynamic.get('update to version %s'%version, True):
if question_dialog(self, _('Update available'), self._update_notification__ = UpdateNotification(version,
_('%s has been updated to version %s. ' parent=self)
'See the <a href="http://calibre-ebook.com/whats-new' self._update_notification__.show()
'">new features</a>. Visit the download pa'
'ge?')%(__appname__, version)):
url = 'http://calibre-ebook.com/download_'+\
('windows' if iswindows else 'osx' if isosx else 'linux')
open_url(QUrl(url))
dynamic.set('update to version %s'%version, False)

View File

@ -150,6 +150,7 @@ class Document(QWebPage):
self.setObjectName("py_bridge") self.setObjectName("py_bridge")
self.debug_javascript = False self.debug_javascript = False
self.current_language = None self.current_language = None
self.loaded_javascript = False
self.setLinkDelegationPolicy(self.DelegateAllLinks) self.setLinkDelegationPolicy(self.DelegateAllLinks)
self.scroll_marks = [] self.scroll_marks = []
@ -175,9 +176,9 @@ class Document(QWebPage):
self.set_user_stylesheet() self.set_user_stylesheet()
self.misc_config() self.misc_config()
# Load jQuery # Load javascript
self.connect(self.mainFrame(), SIGNAL('javaScriptWindowObjectCleared()'), self.mainFrame().javaScriptWindowObjectCleared.connect(
self.load_javascript_libraries) self.add_window_objects)
def set_user_stylesheet(self): def set_user_stylesheet(self):
raw = config().parse().user_css raw = config().parse().user_css
@ -196,16 +197,20 @@ class Document(QWebPage):
if self.do_fit_images: if self.do_fit_images:
self.javascript('setup_image_scaling_handlers()') self.javascript('setup_image_scaling_handlers()')
def add_window_objects(self):
self.mainFrame().addToJavaScriptWindowObject("py_bridge", self)
self.loaded_javascript = False
def load_javascript_libraries(self): def load_javascript_libraries(self):
global bookmarks, referencing, hyphenation, jquery, jquery_scrollTo, hyphenator, images global bookmarks, referencing, hyphenation, jquery, jquery_scrollTo, hyphenator, images
self.mainFrame().addToJavaScriptWindowObject("py_bridge", self) if self.loaded_javascript:
return
self.loaded_javascript = True
if jquery is None: if jquery is None:
jquery = P('content_server/jquery.js', data=True) jquery = P('content_server/jquery.js', data=True)
self.javascript(jquery)
if jquery_scrollTo is None: if jquery_scrollTo is None:
jquery_scrollTo = P('viewer/jquery_scrollTo.js', data=True) jquery_scrollTo = P('viewer/jquery_scrollTo.js', data=True)
if hyphenator is None:
hyphenator = P('viewer/hyphenate/Hyphenator.js', data=True).decode('utf-8')
self.javascript(jquery)
self.javascript(jquery_scrollTo) self.javascript(jquery_scrollTo)
if bookmarks is None: if bookmarks is None:
bookmarks = P('viewer/bookmarks.js', data=True) bookmarks = P('viewer/bookmarks.js', data=True)
@ -224,6 +229,8 @@ class Document(QWebPage):
if not lang: if not lang:
lang = default_lang lang = default_lang
lang = lang.lower()[:2] lang = lang.lower()[:2]
if hyphenator is None:
hyphenator = P('viewer/hyphenate/Hyphenator.js', data=True).decode('utf-8')
self.javascript(hyphenator) self.javascript(hyphenator)
p = P('viewer/hyphenate/patterns/%s.js'%lang) p = P('viewer/hyphenate/patterns/%s.js'%lang)
if not os.path.exists(p): if not os.path.exists(p):
@ -256,6 +263,9 @@ class Document(QWebPage):
self.javascript('goto_reference("%s")'%ref) self.javascript('goto_reference("%s")'%ref)
def goto_bookmark(self, bm): def goto_bookmark(self, bm):
bm = bm.strip()
if bm.startswith('>'):
bm = bm[1:].strip()
self.javascript('scroll_to_bookmark("%s")'%bm) self.javascript('scroll_to_bookmark("%s")'%bm)
def javascript(self, string, typ=None): def javascript(self, string, typ=None):
@ -641,6 +651,7 @@ class DocumentView(QWebView):
# An <iframe> finished loading # An <iframe> finished loading
return return
self.loading_url = None self.loading_url = None
self.document.load_javascript_libraries()
self.document.set_bottom_padding(0) self.document.set_bottom_padding(0)
self.document.fit_images() self.document.fit_images()
self._size_hint = self.document.mainFrame().contentsSize() self._size_hint = self.document.mainFrame().contentsSize()
@ -804,6 +815,7 @@ class DocumentView(QWebView):
def wheelEvent(self, event): def wheelEvent(self, event):
if event.delta() < -14: if event.delta() < -14:
if self.document.at_bottom: if self.document.at_bottom:
self.scroll_by(y=15) # at_bottom can lie on windows
if self.manager is not None: if self.manager is not None:
self.manager.next_document() self.manager.next_document()
event.accept() event.accept()

View File

@ -695,6 +695,9 @@ def config(defaults=None):
c.add_opt('raise_window', ['--raise-window'], default=False, c.add_opt('raise_window', ['--raise-window'], default=False,
help=_('If specified, viewer window will try to come to the ' help=_('If specified, viewer window will try to come to the '
'front when started.')) 'front when started.'))
c.add_opt('full_screen', ['--full-screen', '--fullscreen', '-f'], default=False,
help=_('If specified, viewer window will try to open '
'full screen when started.'))
c.add_opt('remember_window_size', default=False, c.add_opt('remember_window_size', default=False,
help=_('Remember last used window size')) help=_('Remember last used window size'))
c.add_opt('debug_javascript', ['--debug-javascript'], default=False, c.add_opt('debug_javascript', ['--debug-javascript'], default=False,
@ -726,6 +729,8 @@ def main(args=sys.argv):
main.show() main.show()
if opts.raise_window: if opts.raise_window:
main.raise_() main.raise_()
if opts.full_screen:
main.action_full_screen.trigger()
with main: with main:
return app.exec_() return app.exec_()
return 0 return 0

View File

@ -958,16 +958,22 @@ def command_check_library(args, dbpath):
def restore_database_option_parser(): def restore_database_option_parser():
parser = get_parser(_( parser = get_parser(_(
''' '''\
%prog restore_database [options] %prog restore_database [options]
Restore this database from the metadata stored in OPF Restore this database from the metadata stored in OPF files in each
files in each directory of the calibre library. This is directory of the calibre library. This is useful if your metadata.db file
useful if your metadata.db file has been corrupted. has been corrupted.
WARNING: This completely regenerates your database. You will WARNING: This command completely regenerates your database. You will lose
lose stored per-book conversion settings and custom recipes. all saved searches, user categories, plugboards, stored per-book conversion
settings, and custom recipes. Restored metadata will only be as accurate as
what is found in the OPF files.
''')) '''))
parser.add_option('-r', '--really-do-it', default=False, action='store_true',
help=_('Really do the recovery. The command will not run '
'unless this option is specified.'))
return parser return parser
def command_restore_database(args, dbpath): def command_restore_database(args, dbpath):
@ -978,6 +984,12 @@ def command_restore_database(args, dbpath):
parser.print_help() parser.print_help()
return 1 return 1
if not opts.really_do_it:
prints(_('You must provide the --really-do-it option to do a'
' recovery'), end='\n\n')
parser.print_help()
return 1
if opts.library_path is not None: if opts.library_path is not None:
dbpath = opts.library_path dbpath = opts.library_path
@ -1025,10 +1037,10 @@ information is the equivalent of what is shown in the tags pane.
parser.add_option('-q', '--quote', default='"', parser.add_option('-q', '--quote', default='"',
help=_('The character to put around the category value in CSV mode. ' help=_('The character to put around the category value in CSV mode. '
'Default is quotes (").')) 'Default is quotes (").'))
parser.add_option('-r', '--categories', default=None, dest='report', parser.add_option('-r', '--categories', default='', dest='report',
help=_("Comma-separated list of category lookup names.\n" help=_("Comma-separated list of category lookup names.\n"
"Default: all")) "Default: all"))
parser.add_option('-w', '--line-width', default=-1, type=int, parser.add_option('-w', '--idth', default=-1, type=int,
help=_('The maximum width of a single line in the output. ' help=_('The maximum width of a single line in the output. '
'Defaults to detecting screen size.')) 'Defaults to detecting screen size.'))
parser.add_option('-s', '--separator', default=',', parser.add_option('-s', '--separator', default=',',
@ -1052,8 +1064,10 @@ def command_list_categories(args, dbpath):
db = LibraryDatabase2(dbpath) db = LibraryDatabase2(dbpath)
category_data = db.get_categories() category_data = db.get_categories()
data = [] data = []
report_on = [c.strip() for c in opts.report.split(',') if c.strip()]
categories = [k for k in category_data.keys() categories = [k for k in category_data.keys()
if db.metadata_for_field(k)['kind'] not in ['user', 'search']] if db.metadata_for_field(k)['kind'] not in ['user', 'search'] and
(not report_on or k in report_on)]
categories.sort(cmp=lambda x,y: cmp(x if x[0] != '#' else x[1:], categories.sort(cmp=lambda x,y: cmp(x if x[0] != '#' else x[1:],
y if y[0] != '#' else y[1:])) y if y[0] != '#' else y[1:]))

View File

@ -10,6 +10,7 @@ import os, sys, shutil, cStringIO, glob, time, functools, traceback, re
from itertools import repeat from itertools import repeat
from math import floor from math import floor
from Queue import Queue from Queue import Queue
from operator import itemgetter
from PyQt4.QtGui import QImage from PyQt4.QtGui import QImage
@ -68,7 +69,7 @@ copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
class Tag(object): class Tag(object):
def __init__(self, name, id=None, count=0, state=0, avg=0, sort=None, def __init__(self, name, id=None, count=0, state=0, avg=0, sort=None,
tooltip=None, icon=None): tooltip=None, icon=None, category=None):
self.name = name self.name = name
self.id = id self.id = id
self.count = count self.count = count
@ -81,9 +82,11 @@ class Tag(object):
tooltip = _('%sAverage rating is %3.1f')%(tooltip, self.avg_rating) tooltip = _('%sAverage rating is %3.1f')%(tooltip, self.avg_rating)
self.tooltip = tooltip self.tooltip = tooltip
self.icon = icon self.icon = icon
self.category = category
def __unicode__(self): def __unicode__(self):
return u'%s:%s:%s:%s:%s'%(self.name, self.count, self.id, self.state, self.tooltip) return u'%s:%s:%s:%s:%s:%s'%(self.name, self.count, self.id, self.state,
self.category, self.tooltip)
def __str__(self): def __str__(self):
return unicode(self).encode('utf-8') return unicode(self).encode('utf-8')
@ -681,7 +684,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi = self.data.get(idx, self.FIELD_MAP['all_metadata'], mi = self.data.get(idx, self.FIELD_MAP['all_metadata'],
row_is_id = index_is_id) row_is_id = index_is_id)
if mi is not None: if mi is not None:
if get_cover and mi.cover is None: if get_cover:
# Always get the cover, because the value can be wrong if the
# original mi was from the OPF
mi.cover = self.cover(idx, index_is_id=index_is_id, as_path=True) mi.cover = self.cover(idx, index_is_id=index_is_id, as_path=True)
return mi return mi
@ -1100,21 +1105,22 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
tooltip = self.custom_column_label_map[label]['name'] tooltip = self.custom_column_label_map[label]['name']
datatype = cat['datatype'] datatype = cat['datatype']
avgr = itemgetter(3)
item_not_zero_func = lambda x: x[2] > 0
if datatype == 'rating': if datatype == 'rating':
# eliminate the zero ratings line as well as count == 0 # eliminate the zero ratings line as well as count == 0
item_not_zero_func = (lambda x: x[1] > 0 and x[2] > 0) item_not_zero_func = (lambda x: x[1] > 0 and x[2] > 0)
formatter = (lambda x:u'\u2605'*int(x/2)) formatter = (lambda x:u'\u2605'*int(x/2))
avgr = itemgetter(1)
elif category == 'authors': elif category == 'authors':
item_not_zero_func = (lambda x: x[2] > 0)
# Clean up the authors strings to human-readable form # Clean up the authors strings to human-readable form
formatter = (lambda x: x.replace('|', ',')) formatter = (lambda x: x.replace('|', ','))
else: else:
item_not_zero_func = (lambda x: x[2] > 0)
formatter = (lambda x:unicode(x)) formatter = (lambda x:unicode(x))
categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0], categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0],
avg=r[3], sort=r[4], avg=avgr(r), sort=r[4], icon=icon,
icon=icon, tooltip=tooltip) tooltip=tooltip, category=category)
for r in data if item_not_zero_func(r)] for r in data if item_not_zero_func(r)]
# Needed for legacy databases that have multiple ratings that # Needed for legacy databases that have multiple ratings that
@ -1146,7 +1152,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
WHERE format="%s"'''%fmt, WHERE format="%s"'''%fmt,
all=False) all=False)
if count > 0: if count > 0:
categories['formats'].append(Tag(fmt, count=count, icon=icon)) categories['formats'].append(Tag(fmt, count=count, icon=icon,
category='formats'))
if sort == 'popularity': if sort == 'popularity':
categories['formats'].sort(key=lambda x: x.count, reverse=True) categories['formats'].sort(key=lambda x: x.count, reverse=True)
@ -1192,7 +1199,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if icon_map and 'search' in icon_map: if icon_map and 'search' in icon_map:
icon = icon_map['search'] icon = icon_map['search']
for srch in saved_searches().names(): for srch in saved_searches().names():
items.append(Tag(srch, tooltip=saved_searches().lookup(srch), icon=icon)) items.append(Tag(srch, tooltip=saved_searches().lookup(srch),
icon=icon, category='search'))
if len(items): if len(items):
if icon_map is not None: if icon_map is not None:
icon_map['search'] = icon_map['search'] icon_map['search'] = icon_map['search']
@ -1252,6 +1260,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
''' '''
Set metadata for the book `id` from the `Metadata` object `mi` Set metadata for the book `id` from the `Metadata` object `mi`
''' '''
if callable(getattr(mi, 'to_book_metadata', None)):
# Handle code passing in a OPF object instead of a Metadata object
mi = mi.to_book_metadata()
def doit(func, *args, **kwargs): def doit(func, *args, **kwargs):
try: try:
func(*args, **kwargs) func(*args, **kwargs)
@ -1281,8 +1293,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
doit(self.set_series, id, mi.series, notify=False, commit=False) doit(self.set_series, id, mi.series, notify=False, commit=False)
if mi.cover_data[1] is not None: if mi.cover_data[1] is not None:
doit(self.set_cover, id, mi.cover_data[1]) # doesn't use commit doit(self.set_cover, id, mi.cover_data[1]) # doesn't use commit
elif mi.cover is not None and os.access(mi.cover, os.R_OK): elif mi.cover is not None:
doit(self.set_cover, id, lopen(mi.cover, 'rb')) if os.access(mi.cover, os.R_OK):
with lopen(mi.cover, 'rb') as f:
doit(self.set_cover, id, f)
if mi.tags: if mi.tags:
doit(self.set_tags, id, mi.tags, notify=False, commit=False) doit(self.set_tags, id, mi.tags, notify=False, commit=False)
if mi.comments: if mi.comments:
@ -1462,6 +1476,16 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if notify: if notify:
self.notify('metadata', [id]) self.notify('metadata', [id])
def set_uuid(self, id, uuid, notify=True, commit=True):
if uuid:
self.conn.execute('UPDATE books SET uuid=? WHERE id=?', (uuid, id))
self.data.set(id, self.FIELD_MAP['uuid'], uuid, row_is_id=True)
self.dirtied([id], commit=False)
if commit:
self.conn.commit()
if notify:
self.notify('metadata', [id])
# Convenience methods for tags_list_editor # Convenience methods for tags_list_editor
# Note: we generally do not need to refresh_ids because library_view will # Note: we generally do not need to refresh_ids because library_view will
# refresh everything. # refresh everything.
@ -1485,7 +1509,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return result return result
def rename_tag(self, old_id, new_name): def rename_tag(self, old_id, new_name):
new_name = new_name.strip() # It is possible that new_name is in fact a set of names. Split it on
# comma to find out. If it is, then rename the first one and append the
# rest
new_names = [t.strip() for t in new_name.strip().split(',') if t.strip()]
new_name = new_names[0]
new_names = new_names[1:]
# get the list of books that reference the tag being changed
books = self.conn.get('''SELECT book from books_tags_link
WHERE tag=?''', (old_id,))
books = [b[0] for b in books]
new_id = self.conn.get( new_id = self.conn.get(
'''SELECT id from tags '''SELECT id from tags
WHERE name=?''', (new_name,), all=False) WHERE name=?''', (new_name,), all=False)
@ -1501,9 +1536,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# all the changes. To get around this, we first delete any links # all the changes. To get around this, we first delete any links
# to the new_id from books referencing the old_id, so that # to the new_id from books referencing the old_id, so that
# renaming old_id to new_id will be unique on the book # renaming old_id to new_id will be unique on the book
books = self.conn.get('''SELECT book from books_tags_link for book_id in books:
WHERE tag=?''', (old_id,))
for (book_id,) in books:
self.conn.execute('''DELETE FROM books_tags_link self.conn.execute('''DELETE FROM books_tags_link
WHERE book=? and tag=?''', (book_id, new_id)) WHERE book=? and tag=?''', (book_id, new_id))
@ -1512,7 +1545,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
WHERE tag=?''',(new_id, old_id,)) WHERE tag=?''',(new_id, old_id,))
# Get rid of the no-longer used publisher # Get rid of the no-longer used publisher
self.conn.execute('DELETE FROM tags WHERE id=?', (old_id,)) self.conn.execute('DELETE FROM tags WHERE id=?', (old_id,))
self.dirty_books_referencing('tags', new_id, commit=False)
if new_names:
# have some left-over names to process. Add them to the book.
for book_id in books:
self.set_tags(book_id, new_names, append=True, notify=False,
commit=False)
self.dirtied(books, commit=False)
self.conn.commit() self.conn.commit()
def delete_tag_using_id(self, id): def delete_tag_using_id(self, id):
@ -2110,7 +2149,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return None, len(ids) return None, len(ids)
def import_book(self, mi, formats, notify=True, import_hooks=True, def import_book(self, mi, formats, notify=True, import_hooks=True,
apply_import_tags=True): apply_import_tags=True, preserve_uuid=False):
series_index = 1.0 if mi.series_index is None else mi.series_index series_index = 1.0 if mi.series_index is None else mi.series_index
if apply_import_tags: if apply_import_tags:
self._add_newbook_tag(mi) self._add_newbook_tag(mi)
@ -2133,6 +2172,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if mi.pubdate is None: if mi.pubdate is None:
mi.pubdate = utcnow() mi.pubdate = utcnow()
self.set_metadata(id, mi, ignore_errors=True) self.set_metadata(id, mi, ignore_errors=True)
if preserve_uuid and mi.uuid:
self.set_uuid(id, mi.uuid, commit=False)
for path in formats: for path in formats:
ext = os.path.splitext(path)[1][1:].lower() ext = os.path.splitext(path)[1][1:].lower()
if ext == 'opf': if ext == 'opf':
@ -2142,6 +2183,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
else: else:
with lopen(path, 'rb') as f: with lopen(path, 'rb') as f:
self.add_format(id, ext, f, index_is_id=True) self.add_format(id, ext, f, index_is_id=True)
# Mark the book dirty, It probably already has been done by
# set_metadata, but probably isn't good enough
self.dirtied([id], commit=False)
self.conn.commit() self.conn.commit()
self.data.refresh_ids(self, [id]) # Needed to update format list and size self.data.refresh_ids(self, [id]) # Needed to update format list and size
if notify: if notify:

View File

@ -200,6 +200,8 @@ class Restore(Thread):
def restore_book(self, book, db): def restore_book(self, book, db):
db.create_book_entry(book['mi'], add_duplicates=True, db.create_book_entry(book['mi'], add_duplicates=True,
force_id=book['id']) force_id=book['id'])
if book['mi'].uuid:
db.set_uuid(book['id'], book['mi'].uuid, commit=False, notify=False)
db.conn.execute('UPDATE books SET path=? WHERE id=?', (book['path'], db.conn.execute('UPDATE books SET path=? WHERE id=?', (book['path'],
book['id'])) book['id']))

View File

@ -22,6 +22,7 @@ from calibre.library.server.mobile import MobileServer
from calibre.library.server.xml import XMLServer from calibre.library.server.xml import XMLServer
from calibre.library.server.opds import OPDSServer from calibre.library.server.opds import OPDSServer
from calibre.library.server.cache import Cache from calibre.library.server.cache import Cache
from calibre.library.server.browse import BrowseServer
class DispatchController(object): # {{{ class DispatchController(object): # {{{
@ -53,7 +54,8 @@ class DispatchController(object): # {{{
# }}} # }}}
class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache): class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
BrowseServer):
server_name = __appname__ + '/' + __version__ server_name = __appname__ + '/' + __version__

View File

@ -0,0 +1,385 @@
#!/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 operator, os, json
from urllib import quote
from binascii import hexlify
import cherrypy
from calibre.constants import filesystem_encoding
from calibre import isbytestring, force_unicode, prepare_string_for_xml as xml
from calibre.utils.ordered_dict import OrderedDict
def paginate(offsets, content, base_url, up_url=None): # {{{
'Create markup for pagination'
if '?' not in base_url:
base_url += '?'
if base_url[-1] != '?':
base_url += '&'
def navlink(decoration, name, cls, offset):
label = xml(name)
if cls in ('next', 'last'):
label += '&nbsp;' + decoration
else:
label = decoration + '&nbsp;' + label
return (u'<a class="{cls}" href="{base_url}&amp;offset={offset}" title={name}>'
u'{label}</a>').format(cls=cls, decoration=decoration,
name=xml(name, True), offset=offset,
base_url=xml(base_url, True), label=label)
left = ''
if offsets.offset > 0 and offsets.previous_offset > 0:
left += navlink(u'\u219e', _('First'), 'first', 0)
if offsets.offset > 0:
left += ' ' + navlink('&larr;', _('Previous'), 'previous',
offsets.previous_offset)
middle = ''
if up_url:
middle = '<a href="{0}" title="{1}">[{1} &uarr;]</a>'.format(xml(up_url, True),
xml(_('Up')))
right = ''
if offsets.next_offset > -1:
right += navlink('&rarr', _('Next'), 'next', offsets.next_offset)
if offsets.last_offset > offsets.next_offset and offsets.last_offset > 0:
right += ' ' + navlink(u'\u21A0', _('Last'), 'last', offsets.last_offset)
navbar = u'''
<table class="navbar">
<tr>
<td class="left">{left}</td>
<td class="middle">{middle}</td>
<td class="right">{right}</td>
</tr>
<table>
'''.format(left=left, right=right, middle=middle)
templ = u'''
<div class="page">
{navbar}
<div class="page-contents">
{content}
</div>
{navbar}
</div>
'''
return templ.format(navbar=navbar, content=content)
# }}}
def utf8(x): # {{{
if isinstance(x, unicode):
x = x.encode('utf-8')
return x
# }}}
def render_rating(rating, container='span'): # {{{
if rating < 0.1:
return '', ''
added = 0
rstring = xml(_('Average rating: %.1f stars')% (rating if rating else 0.0),
True)
ans = ['<%s class="rating">' % (container)]
for i in range(5):
n = rating - added
x = 'half'
if n <= 0.1:
x = 'off'
elif n >= 0.9:
x = 'on'
ans.append(
u'<img alt="{0}" title="{0}" src="/static/star-{1}.png" />'.format(
rstring, x))
added += 1
ans.append('</%s>'%container)
return u''.join(ans), rstring
# }}}
def get_category_items(category, items, db, datatype): # {{{
def item(i):
templ = (u'<div title="{4}" class="category-item">'
'<div class="category-name">{0}</div><div>{1}</div>'
'<div>{2}'
'<span class="href">{3}</span></div></div>')
rating, rstring = render_rating(i.avg_rating)
name = xml(i.name)
if datatype == 'rating':
name = xml(_('%d stars')%int(i.avg_rating))
id_ = i.id
if id_ is None:
id_ = hexlify(force_unicode(name).encode('utf-8'))
id_ = xml(str(id_))
desc = ''
if i.count > 0:
desc += '[' + _('%d items')%i.count + ']'
href = '/browse/matches/%s/%s'%(category, id_)
return templ.format(xml(name), rating,
xml(desc), xml(quote(href)), rstring)
items = list(map(item, items))
return '\n'.join(['<div class="category-container">'] + items + ['</div>'])
# }}}
class Endpoint(object): # {{{
'Manage encoding, mime-type, last modified, cookies, etc.'
def __init__(self, mimetype='text/html; charset=utf-8', sort_type='category'):
self.mimetype = mimetype
self.sort_type = sort_type
self.sort_kwarg = sort_type + '_sort'
self.sort_cookie_name = 'calibre_browse_server_sort_'+self.sort_type
def __call__(eself, func):
def do(self, *args, **kwargs):
sort_val = None
cookie = cherrypy.request.cookie
if cookie.has_key(eself.sort_cookie_name):
sort_val = cookie[eself.sort_cookie_name].value
kwargs[eself.sort_kwarg] = sort_val
ans = func(self, *args, **kwargs)
cherrypy.response.headers['Content-Type'] = eself.mimetype
updated = self.db.last_modified()
cherrypy.response.headers['Last-Modified'] = \
self.last_modified(max(updated, self.build_time))
ans = utf8(ans)
return ans
do.__name__ = func.__name__
return do
# }}}
class BrowseServer(object):
def add_routes(self, connect):
base_href = '/browse'
connect('browse', base_href, self.browse_catalog)
connect('browse_catalog', base_href+'/category/{category}',
self.browse_catalog)
connect('browse_category_group',
base_href+'/category_group/{category}/{group}',
self.browse_category_group)
connect('browse_list', base_href+'/list/{query}', self.browse_list)
connect('browse_search', base_href+'/search/{query}',
self.browse_search)
connect('browse_book', base_href+'/book/{uuid}', self.browse_book)
def browse_template(self, sort, category=True):
def generate():
scn = 'calibre_browse_server_sort_'
if category:
sort_opts = [('rating', _('Average rating')), ('name',
_('Name')), ('popularity', _('Popularity'))]
scn += 'category'
else:
scn += 'list'
fm = self.db.field_metadata
sort_opts, added = [], set([])
for x in fm.sortable_field_keys():
n = fm[x]['name']
if n not in added:
added.add(n)
sort_opts.append((x, n))
ans = P('content_server/browse/browse.html',
data=True).decode('utf-8')
ans = ans.replace('{sort_select_label}', xml(_('Sort by')+':'))
ans = ans.replace('{sort_cookie_name}', scn)
opts = ['<option %svalue="%s">%s</option>' % (
'selected="selected" ' if k==sort else '',
xml(k), xml(n), ) for k, n in
sorted(sort_opts, key=operator.itemgetter(1))]
ans = ans.replace('{sort_select_options}', ('\n'+' '*20).join(opts))
lp = self.db.library_path
if isbytestring(lp):
lp = force_unicode(lp, filesystem_encoding)
if isinstance(ans, unicode):
ans = ans.encode('utf-8')
ans = ans.replace('{library_name}', xml(os.path.basename(lp)))
ans = ans.replace('{library_path}', xml(lp, True))
return ans
if self.opts.develop:
return generate()
if not hasattr(self, '__browse_template__'):
self.__browse_template__ = generate()
return self.__browse_template__
# Catalogs {{{
def browse_toplevel(self):
categories = self.categories_cache()
category_meta = self.db.field_metadata
cats = [
(_('Newest'), 'newest'),
]
def getter(x):
return category_meta[x]['name'].lower()
for category in sorted(categories,
cmp=lambda x,y: cmp(getter(x), getter(y))):
if len(categories[category]) == 0:
continue
if category == 'formats':
continue
meta = category_meta.get(category, None)
if meta is None:
continue
cats.append((meta['name'], category))
cats = ['<li title="{2} {0}">{0}<span>/browse/category/{1}</span></li>'\
.format(xml(x, True), xml(quote(y)), xml(_('Browse books by')))
for x, y in cats]
main = '<div class="toplevel"><h3>{0}</h3><ul>{1}</ul></div>'\
.format(_('Choose a category to browse by:'), '\n\n'.join(cats))
return self.browse_template('name').format(title='',
script='toplevel();', main=main)
def browse_sort_categories(self, items, sort):
if sort not in ('rating', 'name', 'popularity'):
sort = 'name'
def sorter(x):
ans = getattr(x, 'sort', x.name)
if hasattr(ans, 'upper'):
ans = ans.upper()
return ans
items.sort(key=sorter)
if sort == 'popularity':
items.sort(key=operator.attrgetter('count'), reverse=True)
elif sort == 'rating':
items.sort(key=operator.attrgetter('avg_rating'), reverse=True)
return sort
def browse_category(self, category, sort):
categories = self.categories_cache()
category_meta = self.db.field_metadata
category_name = category_meta[category]['name']
datatype = category_meta[category]['datatype']
if category not in categories:
raise cherrypy.HTTPError(404, 'category not found')
items = categories[category]
sort = self.browse_sort_categories(items, sort)
script = 'true'
if len(items) <= self.opts.max_opds_ungrouped_items:
script = 'false'
items = get_category_items(category, items, self.db, datatype)
else:
getter = lambda x: unicode(getattr(x, 'sort', x.name))
starts = set([])
for x in items:
val = getter(x)
if not val:
val = u'A'
starts.add(val[0].upper())
category_groups = OrderedDict()
for x in sorted(starts):
category_groups[x] = len([y for y in items if
getter(y).upper().startswith(x)])
items = [(u'<h3 title="{0}">{0} <span>[{2}]</span></h3><div>'
u'<div class="loaded" style="display:none"></div>'
u'<div class="loading"><img alt="{1}" src="/static/loading.gif" /><em>{1}</em></div>'
u'<span class="load_href">{3}</span></div>').format(
xml(s, True),
xml(_('Loading, please wait'))+'&hellip;',
unicode(c),
xml(u'/browse/category_group/%s/%s'%(category, s)))
for s, c in category_groups.items()]
items = '\n\n'.join(items)
items = u'<div id="groups">\n{0}</div>'.format(items)
script = 'category(%s);'%script
main = u'''
<div class="category">
<h3>{0}</h3>
<a class="navlink" href="/browse"
title="{2}">{2}&nbsp;&uarr;</a>
{1}
</div>
'''.format(
xml(_('Browsing by')+': ' + category_name), items,
xml(_('Up'), True))
return self.browse_template(sort).format(title=category_name,
script=script, main=main)
@Endpoint(mimetype='application/json; charset=utf-8')
def browse_category_group(self, category=None, group=None,
category_sort=None):
sort = category_sort
if sort not in ('rating', 'name', 'popularity'):
sort = 'name'
categories = self.categories_cache()
category_meta = self.db.field_metadata
datatype = category_meta[category]['datatype']
if category not in categories:
raise cherrypy.HTTPError(404, 'category not found')
if not group:
raise cherrypy.HTTPError(404, 'invalid group')
items = categories[category]
entries = []
getter = lambda x: unicode(getattr(x, 'sort', x.name))
for x in items:
val = getter(x)
if not val:
val = u'A'
if val.upper().startswith(group):
entries.append(x)
sort = self.browse_sort_categories(entries, sort)
entries = get_category_items(category, entries, self.db, datatype)
return json.dumps(entries, ensure_ascii=False)
@Endpoint()
def browse_catalog(self, category=None, category_sort=None):
'Entry point for top-level, categories and sub-categories'
if category == None:
ans = self.browse_toplevel()
else:
ans = self.browse_category(category, category_sort)
return ans
# }}}
# Book Lists {{{
def browse_list(self, query=None, offset=0, sort=None):
raise NotImplementedError()
# }}}
# Search {{{
def browse_search(self, query=None, offset=0, sort=None):
raise NotImplementedError()
# }}}
# Book {{{
def browse_book(self, uuid=None):
raise NotImplementedError()
# }}}

View File

@ -29,6 +29,11 @@ class Cache(object):
def categories_cache(self, restrict_to=frozenset([])): def categories_cache(self, restrict_to=frozenset([])):
base_restriction = self.search_cache('')
if restrict_to:
restrict_to = frozenset(restrict_to).intersection(base_restriction)
else:
restrict_to = base_restriction
old = self._category_cache.pop(frozenset(restrict_to), None) old = self._category_cache.pop(frozenset(restrict_to), None)
if old is None or old[0] <= self.db.last_modified(): if old is None or old[0] <= self.db.last_modified():
categories = self.db.get_categories(ids=restrict_to) categories = self.db.get_categories(ids=restrict_to)

View File

@ -35,9 +35,10 @@ class ContentServer(object):
def add_routes(self, connect): def add_routes(self, connect):
connect('root', '/', self.index) connect('root', '/', self.index)
connect('old', '/old', self.old)
connect('get', '/get/{what}/{id}', self.get, connect('get', '/get/{what}/{id}', self.get,
conditions=dict(method=["GET", "HEAD"])) conditions=dict(method=["GET", "HEAD"]))
connect('static', '/static/{name}', self.static, connect('static', '/static/{name:.*?}', self.static,
conditions=dict(method=["GET", "HEAD"])) conditions=dict(method=["GET", "HEAD"]))
# Utility methods {{{ # Utility methods {{{
@ -123,6 +124,9 @@ class ContentServer(object):
return self.static('index.html') return self.static('index.html')
def old(self, **kwargs):
return self.static('index.html')
# Actually get content from the database {{{ # Actually get content from the database {{{
def get_cover(self, id, thumbnail=False): def get_cover(self, id, thumbnail=False):
cover = self.db.cover(id, index_is_id=True, as_file=False) cover = self.db.cover(id, index_is_id=True, as_file=False)

View File

@ -121,7 +121,7 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS):
book['id'], fmt) book['id'], fmt)
), ),
CLASS('button')) CLASS('button'))
s.tail = u'\u202f' # &nbsp; s.tail = u''
last = s last = s
data.append(s) data.append(s)

View File

@ -18,7 +18,7 @@ from calibre.constants import __appname__
from calibre.ebooks.metadata import fmt_sidx from calibre.ebooks.metadata import fmt_sidx
from calibre.library.comments import comments_to_html from calibre.library.comments import comments_to_html
from calibre.library.server import custom_fields_to_display from calibre.library.server import custom_fields_to_display
from calibre.library.server.utils import format_tag_string from calibre.library.server.utils import format_tag_string, Offsets
from calibre import guess_type from calibre import guess_type
from calibre.utils.ordered_dict import OrderedDict from calibre.utils.ordered_dict import OrderedDict
@ -321,26 +321,6 @@ class CategoryGroupFeed(NavFeed):
self.root.append(CATALOG_GROUP_ENTRY(item, which, base_href, version, updated)) self.root.append(CATALOG_GROUP_ENTRY(item, which, base_href, version, updated))
class OPDSOffsets(object):
def __init__(self, offset, delta, total):
if offset < 0:
offset = 0
if offset >= total:
raise cherrypy.HTTPError(404, 'Invalid offset: %r'%offset)
last_allowed_index = total - 1
last_current_index = offset + delta - 1
self.offset = offset
self.next_offset = last_current_index + 1
if self.next_offset > last_allowed_index:
self.next_offset = -1
self.previous_offset = self.offset - delta
if self.previous_offset < 0:
self.previous_offset = 0
self.last_offset = last_allowed_index - delta
if self.last_offset < 0:
self.last_offset = 0
class OPDSServer(object): class OPDSServer(object):
@ -374,7 +354,7 @@ class OPDSServer(object):
items = [x for x in self.db.data.iterall() if x[idx] in ids] items = [x for x in self.db.data.iterall() if x[idx] in ids]
self.sort(items, sort_by, ascending) self.sort(items, sort_by, ascending)
max_items = self.opts.max_opds_items max_items = self.opts.max_opds_items
offsets = OPDSOffsets(offset, max_items, len(items)) offsets = Offsets(offset, max_items, len(items))
items = items[offsets.offset:offsets.offset+max_items] items = items[offsets.offset:offsets.offset+max_items]
updated = self.db.last_modified() updated = self.db.last_modified()
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
@ -448,7 +428,7 @@ class OPDSServer(object):
id_ = 'calibre-category-group-feed:'+category+':'+which id_ = 'calibre-category-group-feed:'+category+':'+which
max_items = self.opts.max_opds_items max_items = self.opts.max_opds_items
offsets = OPDSOffsets(offset, max_items, len(items)) offsets = Offsets(offset, max_items, len(items))
items = list(items)[offsets.offset:offsets.offset+max_items] items = list(items)[offsets.offset:offsets.offset+max_items]
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
@ -495,7 +475,7 @@ class OPDSServer(object):
if len(items) <= MAX_ITEMS: if len(items) <= MAX_ITEMS:
max_items = self.opts.max_opds_items max_items = self.opts.max_opds_items
offsets = OPDSOffsets(offset, max_items, len(items)) offsets = Offsets(offset, max_items, len(items))
items = list(items)[offsets.offset:offsets.offset+max_items] items = list(items)[offsets.offset:offsets.offset+max_items]
ans = CategoryFeed(items, which, id_, updated, version, offsets, ans = CategoryFeed(items, which, id_, updated, version, offsets,
page_url, up_url, self.db) page_url, up_url, self.db)
@ -516,7 +496,7 @@ class OPDSServer(object):
getattr(y, 'sort', y.name).startswith(x)]) getattr(y, 'sort', y.name).startswith(x)])
items = [Group(x, y) for x, y in category_groups.items()] items = [Group(x, y) for x, y in category_groups.items()]
max_items = self.opts.max_opds_items max_items = self.opts.max_opds_items
offsets = OPDSOffsets(offset, max_items, len(items)) offsets = Offsets(offset, max_items, len(items))
items = items[offsets.offset:offsets.offset+max_items] items = items[offsets.offset:offsets.offset+max_items]
ans = CategoryGroupFeed(items, which, id_, updated, version, offsets, ans = CategoryGroupFeed(items, which, id_, updated, version, offsets,
page_url, up_url) page_url, up_url)

View File

@ -13,6 +13,28 @@ from calibre import strftime as _strftime, prints
from calibre.utils.date import now as nowf from calibre.utils.date import now as nowf
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
class Offsets(object):
'Calculate offsets for a paginated view'
def __init__(self, offset, delta, total):
if offset < 0:
offset = 0
if offset >= total:
raise cherrypy.HTTPError(404, 'Invalid offset: %r'%offset)
last_allowed_index = total - 1
last_current_index = offset + delta - 1
self.slice_upper_bound = offset+delta
self.offset = offset
self.next_offset = last_current_index + 1
if self.next_offset > last_allowed_index:
self.next_offset = -1
self.previous_offset = self.offset - delta
if self.previous_offset < 0:
self.previous_offset = 0
self.last_offset = last_allowed_index - delta
if self.last_offset < 0:
self.last_offset = 0
def expose(func): def expose(func):

View File

@ -418,3 +418,14 @@ How do I run calibre from my USB stick?
A portable version of calibre is available at: `portableapps.com <http://portableapps.com/node/20518>`_. However, this is usually out of date. You can also setup your own portable calibre install by following :ref:`these instructions <portablecalibre>`. A portable version of calibre is available at: `portableapps.com <http://portableapps.com/node/20518>`_. However, this is usually out of date. You can also setup your own portable calibre install by following :ref:`these instructions <portablecalibre>`.
Why are there so many calibre-parallel processes on my system?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|app| maintains two separate worker process pools. One is used for adding books/saving to disk and the other for conversions. You can control the number of worker processes via :guilabel:`Preferences->Advanced->Miscellaneous`. So if you set it to 6 that means a maximum of 3 conversions will run simultaneously. And that is why you will see the number of worker processes changes by two when you use the up and down arrows. On windows, you can set the priority that these processes run with. This can be useful on older, single CPU machines, if you find them slowing down to a crawl when conversions are running.
In addition to this some conversion plugins run tasks in their own pool of processes, so for example if you bulk convert comics, each comic conversion will use three separate processes to render the images. The job manager knows this so it will run only a single comic conversion simultaneously.
And since I'm sure someone will ask: The reason adding/saving books are in separate processes is because of PDF. PDF processing libraries can crash on reading PDFs and I dont want the crash to take down all of calibre. Also when adding EPUB books, in order to extract the cover you have to sometimes render the HTML of the first page, which means that it either has to run the GUI thread of the main process or in a separate process.
Finally, the reason calibre keep workers alive and idle instead of launching on demand is to workaround the slow startup time of python processes.

View File

@ -17,9 +17,9 @@ To get started with more advanced usage, you should read about the :ref:`Graphic
You will find the list of :ref:`Frequently Asked Questions <faq>` useful as well. You will find the list of :ref:`Frequently Asked Questions <faq>` useful as well.
.. only:: html and online .. only:: online
An e-book version of this User Manual is available in `EPUB format <calibre.epub>`_. Because the User Manual uses advanced formatting, it is only suitable for use with the |app| e-book viewer. An e-book version of this User Manual is available in `EPUB format <calibre.epub>`_.
Sections Sections
------------ ------------

View File

@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
Provides platform independent temporary files that persist even after Provides platform independent temporary files that persist even after
being closed. being closed.
""" """
import tempfile, os, atexit import tempfile, os, atexit, binascii, cPickle
from calibre import __version__, __appname__ from calibre import __version__, __appname__
@ -30,6 +30,15 @@ def remove_dir(x):
def base_dir(): def base_dir():
global _base_dir global _base_dir
if _base_dir is None: if _base_dir is None:
td = os.environ.get('CALIBRE_WORKER_TEMP_DIR', None)
if td is not None:
try:
td = cPickle.loads(binascii.unhexlify(td))
except:
td = None
if td and os.path.exists(td):
_base_dir = td
else:
_base_dir = tempfile.mkdtemp(prefix='%s_%s_tmp_'%(__appname__, _base_dir = tempfile.mkdtemp(prefix='%s_%s_tmp_'%(__appname__,
__version__)) __version__))
atexit.register(remove_dir, _base_dir) atexit.register(remove_dir, _base_dir)

File diff suppressed because it is too large Load Diff

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