Version 69
|
@ -49,10 +49,10 @@
|
|||
<p><img src="similar_gununus.png" /></p>
|
||||
<h3>PIL errors</h3>
|
||||
<p>At some point, you will probably encounter a PIL error when importing a file. PIL is the Python Image Library, the code I use to manipulate image files. Some files are kooky, and just won't load with it. I can't fix these errors, since PIL is not mine. It would take me thousands of hours to write my own image library, and even then it would have its own odd errors. Just gotta deal with it.</p>
|
||||
<p>If the PIL error'ing file is one you particularly care about, I suggest you import it into photoshop or similar and save it again. Photoshop should be clever enough to parse the file's weirdness, and then it'll hopefully save again to a simpler format which PIL, and hence the client, will be able to understand.</p>
|
||||
<p>If the PIL error'ing file is one you particularly care about, I suggest you import it into photoshop or similar and save it again. Photoshop should be clever enough to parse the file's weirdness, and then it'll hopefully save again to a simpler format that PIL, and hence the client, will be able to understand.</p>
|
||||
<h3>busted up gifs</h3>
|
||||
<p>Animated gifs are a real pain in the neck. There are several loopholes in the standard that permit odd palettes and colourspaces, and PIL has a hard time parsing it all.</p>
|
||||
<p>So, some gifs will have a coloured first frame but grey frames thereafter; or they will have odd washy noise all over; or they will just be black. The file isn't broken, but lib is looking at it wrong. Every ten versions or so, I gather enough enthusiasm to fix a few more.</p>
|
||||
<p>So, some gifs will have a coloured first frame but grey frames thereafter; or they will have odd washy noise all over; or they will just be black. The file isn't broken, the client is just looking at it wrong. Every ten versions or so, I gather enough enthusiasm to fix a few more.</p>
|
||||
<h3>setting a password</h3>
|
||||
<p>the client offers a very simple password system, enough to keep out noobs. You can set it at <i>database->set a password</i>. It will thereafter ask for the password every time you start the program, and will not open without it. However none of the database is encrypted, and someone with enough enthusiasm or a tool and access to your computer can still very easily see what files you have. The password is mainly to stop idle snoops checking your images if you are away from your machine.</p>
|
||||
<h3>backing up</h3>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>advanced - parents</title>
|
||||
<link href="hydrus.ico" rel="shortcut icon" />
|
||||
<link href="style.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<p class="warning">This is a draft document. None of this code actually exists yet!</p>
|
||||
<h3>what's the problem?</h3>
|
||||
<p>Tags often fall into certain heirarchies. Files with one particular tag often always have certain other tags, and it is annoying and time-consuming to add them all individually every time.</p>
|
||||
<p>For example, whenever you tag a file with <i>ak-47</i>, you probably also want to tag it <i>assault rifle</i>, and maybe even <i>firearm</i> as well.</p>
|
||||
<p><img src="tag_parents_venn.png" /></p>
|
||||
<p>Another time, you might tag a file <i>character:eddard stark</i>, and then also have to type in <i>house stark</i> and then <i>series:game of thrones</i>. (you might also think <i>series:game of thrones</i> should actually be <i>series:a song of ice and fire</i>, but that is an issue for <a href="advanced_siblings.html">siblings</a>)</p>
|
||||
<p>Drawing more relationships would make a significantly more complicated venn diagram, so let's draw it like a family tree instead:</p>
|
||||
<p><img src="tag_parents_got.png" /></p>
|
||||
<p>If we were to search for the tag <i>house stark</i>, we would expect our results to include all the images of any of the Starks. Even so, it is tedious to have to remember to tag every image of a character with their house affiliation or other parent tags. You may not even remember.</p>
|
||||
<p>Wouldn't it be great if we could add these sorts of tags at the same time?</p>
|
||||
<h3>tag parents</h3>
|
||||
<p>Let's define the relationship 'tag P is tag C's parent' as saying that tag P is the semantic superset of tag C. All files that have C should have P. Let's say that when the user tries to add a tag to a file, the tag's parents are added recursively.</p>
|
||||
<p>Let's expand our weapon example:</p>
|
||||
<p><img src="tag_parents_firearms.png" /></p>
|
||||
<p>In that graph, adding <i>ar-15</i> to a file would also add <i>semi-automatic rifle</i>, <i>rifle</i>, and <i>firearm</i>. Searching for <i>handgun</i> would everything with <i>m1911</i> and <i>smith and wesson model 10</i>.</p>
|
||||
<p>Although characters are an obvious and convenient step, we need to be careful to make sure we only link characters to series if they have unique names, and only if they are <i>inextricably</i> linked with their series. Series with crossovers and spinoffs (like CSI) make this difficult.</p>
|
||||
<h3>how you do it</h3>
|
||||
<p>services->manage tag parents</p>
|
||||
<p>picture of dialog</p>
|
||||
<p>when you add a tag, all of its parents will be added. This is transitive, so grandparents and great-grandparents and so on are also added.</p>
|
||||
<p>parents are not cast-iron. from time to time, even the simplest and most seemingly-obvious of relationships can be broken. Rule 63 will often break a 'character x is a male' relationship, for example. so, you can de-pend and petition parents without the children being similarly removed. (screenshot?)</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,52 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>advanced - siblings</title>
|
||||
<link href="hydrus.ico" rel="shortcut icon" />
|
||||
<link href="style.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<p class="warning">This is another area under active development right now. I will expand this as I add remote siblings.</p>
|
||||
<h3>what's the problem?</h3>
|
||||
<p>It is a common problem, in communal tagging, for ambiguity to arise when different tags with the same meaning are applied non-uniformly.</p>
|
||||
<p>A great example is in Japanese names, which are natively written surname first. <i>character:ayanami rei</i> and <i>character:rei ayanami</i> have the same meaning, but different users will use one, or the other, or even both.</p>
|
||||
<p>Other examples are tiny syntactic changes, common misspellings, and unique acronyms:
|
||||
<ul>
|
||||
<li><i>smiling</i> and <i>smile</i></li>
|
||||
<li><i>staring at camera</i> and <i>looking at viewer</i></li>
|
||||
<li><i>pokemon</i> and <i>pokémon</i></li>
|
||||
<li><i>jersualem</i> and <i>jerusalem</i></li>
|
||||
<li><i>lotr</i> and <i>series:the lord of the rings</i></li>
|
||||
<li><i>marimite</i> and <i>series:maria-sama ga miteru</i></li>
|
||||
<li><i>ishygddt</i> and <i>i sure hope you guys don't do that</i></li>
|
||||
</ul>
|
||||
<p>A particular repository may have a preferred standard, but it is not easy to guarantee that all the users will know exactly which tag to use or search for.</p>
|
||||
<p>After some time, you get this:</p>
|
||||
<p><img src="tag_siblings_venn_1.png" /></p>
|
||||
<p>Without continual intervention by janitors or other experienced users to make sure y⊇x (i.e. making the yellow circle entirely overlap the blue by manually giving y to everything with x), searches can only return x (blue circle) or y (yellow circle) or x∩y (the lens-shaped overlap). What we really want is x∪y (both circles).</p>
|
||||
<p>So, how do we fix this problem?</p>
|
||||
<h3>tag siblings</h3>
|
||||
<p>Let's define a relationship, <b>A->B</b>, that means that any time we would normally see or use tag A or tag B, we will instead get the union of tag A and tag B, and this union will be labelled as tag B. In short, we fold A into B, unioning the two tag spaces.</p>
|
||||
<p>Note that this relationship implies that B is in some way 'better' than A. For instance, I might say that I prefer <i>character:ayanami rei</i>, as I think Japanese characters should probably get their names in the Japanese fashion. So, I would say <i>character:rei ayanami</i>-><i>character:ayanami rei</i>. This means that whenever I might see or type the Western arrangement of Rei's name, I'll automatically get the Japanese instead. Any search will return files with either tag as if they were all tagged <i>character:ayanami rei</i>.</p>
|
||||
<p><img src="tag_siblings_venn_2.png" /></p>
|
||||
<h3>ok, I understand; now confuse me</h3>
|
||||
<p>This relationship is transitive, which means as well as saying A->B, you can also say B->C, which implies A->C and B->C.</p>
|
||||
<p><img src="tag_siblings_usa.png" /></p>
|
||||
<p>You can also have an A->C and B->C that does not include A->B.</p>
|
||||
<p><img src="tag_siblings_what_is_a_man.png" /></p>
|
||||
<p>The outcome of these two arrangements is the same (everything ends up as C), but the underlying semantics are a little different if you ever want to edit them.</p>
|
||||
<p>Many complicated arrangements are possible:</p>
|
||||
<p><img src="tag_siblings_yo_dawg.png" /></p>
|
||||
<p>Note that if you say A->B, you cannot say A->C; the left-hand side can only go to one. The right-hand side can receive many. The client will stop you from constructing loops.</p>
|
||||
<h3>how you do it</h3>
|
||||
<p>It is easy. Just open <i>services->manage tag siblings</i>, and add a few.</p>
|
||||
<p><img src="tag_siblings_dialog.png" /></p>
|
||||
<p>The client will automatically collapse the tagspace to whatever you set. It'll even work with autocomplete, so when you start to type in 'rei ayanami' in search, it will show <i>character:ayanami rei</i> instead:</p>
|
||||
<p><img src="tag_siblings_rei.png" /></p>
|
||||
<p>It will not collapse in the write dialog. You will be able to add or remove A as normal, but it will be written in some form of "A (B)" to let you know that, ultimately, the tag will end up displaying in the main gui as B:</p>
|
||||
<p><img src="tag_siblings_ac_write.png" /></p>
|
||||
<p>Although the client may present A as B, it will secretly remember A! You can remove the association A->B, and everything will return to how it was. <b>No information is lost at any point.</b></p>
|
||||
<p>At the moment, this only supports local siblings. Just like tags, don't go crazy and try to do everything yourself. Have a play with local, but please wait for remote support so your efforts are shared.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -8,6 +8,33 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 69</h3></li>
|
||||
<ul>
|
||||
<li>first dialog for tag siblings</li>
|
||||
<li>tag siblings db table</li>
|
||||
<li>tag siblings manager in controller</li>
|
||||
<li>tag siblings display in tagsboxcpp</li>
|
||||
<li>tag siblings display in tagsboxflat</li>
|
||||
<li>tag siblings display in tagsboxmanage</li>
|
||||
<li>tag siblings display in tagsboxactiveonly, via A/C dropdowns, for both read and write</li>
|
||||
<li>top result sibling switcheroo in A/C write</li>
|
||||
<li>A/C db fetch now does siblings too</li>
|
||||
<li>db tag search does siblings</li>
|
||||
<li>siblings help, with some nice mini-charts</li>
|
||||
<li>I fixed collections, which were typo-broke; I'll make sure it doesn't happen again</li>
|
||||
<li>more granular subs error handling, meaning individual file failures won't crash an entire sub</li>
|
||||
<li>individual subs can now be paused</li>
|
||||
<li>e621 fixed for real this time</li>
|
||||
<li>A/C improvement that slows tag updates a little but should stop A/C lag after an update</li>
|
||||
<li>as a result, trying out dropping the CPU-intensive fatten_ac_cache maintenance call</li>
|
||||
<li>A/C read will now update system preds every time you click on it, so inbex/archive counts will stay accurate</li>
|
||||
<li>A/C "all known files + tags" will no longer show the mega-laggy total file count</li>
|
||||
<li>a list -> tuple convenience fix in sanelistctrl</li>
|
||||
<li>if you don't have any pixiv credentials set up, you will now no longer get the option to start downloading pixiv stuff</li>
|
||||
<li>fixed a tiny typo in the thumbnail resizer that made it wait far more politely than was intended</li>
|
||||
<li>slight change to ratio system pred that fixes some lockups, sometimes, I think</li>
|
||||
<li>fullscreen flash and video will get a pixel of whitespace on the right</li>
|
||||
</ul>
|
||||
<li><h3>version 68</h3></li>
|
||||
<ul>
|
||||
<li>fullscreen view now takes addmediaresult</li>
|
||||
|
|
|
@ -24,7 +24,8 @@
|
|||
<li><a href="registration_keys.html">registering new accounts</a></li>
|
||||
<li><a href="access_keys.html">access keys to my server's services</a></li>
|
||||
<li><a href="tagging_schema.html">thoughts on a public tagging schema</a></li>
|
||||
<li><a href="advanced.html">advanced usage</a></li>
|
||||
<li><a href="advanced.html">advanced usage - general</a></li>
|
||||
<li><a href="advanced_siblings.html">advanced usage - tag siblings</a></li>
|
||||
<li><a href="privacy.html">privacy</a></li>
|
||||
<li><a href="server.html">setting up your own server</a></li>
|
||||
<li><a href="running_from_source.html">running a client or server from source</a></li>
|
||||
|
|
|
@ -0,0 +1,722 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="744.09448819"
|
||||
height="1052.3622047"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.2 r9819"
|
||||
sodipodi:docname="tag_parents.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="231.77811"
|
||||
inkscape:cy="465.57944"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1300"
|
||||
inkscape:window-height="964"
|
||||
inkscape:window-x="1327"
|
||||
inkscape:window-y="858"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="color:#000000;fill:none;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect2985"
|
||||
width="374.87128"
|
||||
height="264.81741"
|
||||
x="15.099963"
|
||||
y="17.913582"
|
||||
inkscape:export-filename="C:\code\Hydrus\help\tag_parents_venn.png"
|
||||
inkscape:export-xdpi="146.31"
|
||||
inkscape:export-ydpi="146.31" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="color:#000000;fill:#00ff00;fill-opacity:0.24705882;stroke:#001d00;stroke-width:2.64672065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path2987"
|
||||
sodipodi:cx="194.95944"
|
||||
sodipodi:cy="191.71222"
|
||||
sodipodi:rx="147.48227"
|
||||
sodipodi:ry="147.48227"
|
||||
d="m 342.44171,191.71222 a 147.48227,147.48227 0 1 1 -294.964537,0 147.48227,147.48227 0 1 1 294.964537,0 z"
|
||||
transform="matrix(0.75581183,0,0,0.75549245,107.86342,5.4851574)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="color:#000000;fill:#00ffff;fill-opacity:0.24705882;stroke:#001d00;stroke-width:2.64672065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path2989"
|
||||
sodipodi:cx="167.68532"
|
||||
sodipodi:cy="169.48886"
|
||||
sodipodi:rx="69.700523"
|
||||
sodipodi:ry="69.700523"
|
||||
d="m 237.38584,169.48886 a 69.700523,69.700523 0 1 1 -139.401045,0 69.700523,69.700523 0 1 1 139.401045,0 z"
|
||||
transform="matrix(0.75581183,0,0,0.75549245,93.738942,-7.8701796)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="color:#000000;fill:#ff00ff;fill-opacity:0.24705882;stroke:#001d00;stroke-width:2.64672065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path2991"
|
||||
sodipodi:cx="148.9975"
|
||||
sodipodi:cy="157.8721"
|
||||
sodipodi:rx="24.748737"
|
||||
sodipodi:ry="24.748737"
|
||||
d="m 173.74623,157.8721 a 24.748737,24.748737 0 1 1 -49.49747,0 24.748737,24.748737 0 1 1 49.49747,0 z"
|
||||
transform="matrix(0.75581183,0,0,0.75549245,95.265916,-9.3965047)" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:13.60173798px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="43.33976"
|
||||
y="107.22626"
|
||||
id="text3761"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0002113,0.9997887)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3763"
|
||||
x="43.33976"
|
||||
y="107.22626">ak-47</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:13.60173798px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="26.546631"
|
||||
y="144.62915"
|
||||
id="text3765"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0002113,0.9997887)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3767"
|
||||
x="26.546631"
|
||||
y="144.62915">assault rifle</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:13.60173798px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="35.706516"
|
||||
y="188.1386"
|
||||
id="text3769"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0002113,0.9997887)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3771"
|
||||
x="35.706516"
|
||||
y="188.1386">firearm</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:13.60173798px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="20.440039"
|
||||
y="256.0744"
|
||||
id="text3773"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0002113,0.9997887)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3775"
|
||||
x="20.440039"
|
||||
y="256.0744">all ak-47s are assault rifles</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="20.440039"
|
||||
y="273.07657"
|
||||
id="tspan3777">all assault rifles are firearms</tspan></text>
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 202.15385,101.86147 -117.57673,0"
|
||||
id="path3779"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 187.64763,140.78277 -87.03732,0"
|
||||
id="path3781"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 175.43186,185.04619 -93.908681,0"
|
||||
id="path3783"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="color:#000000;fill:none;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect3785"
|
||||
width="413.79507"
|
||||
height="256.16187"
|
||||
x="16.270475"
|
||||
y="500.10254"
|
||||
inkscape:export-filename="C:\code\Hydrus\help\tag_parents_firearms.png"
|
||||
inkscape:export-xdpi="146.31"
|
||||
inkscape:export-ydpi="146.31" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="176.80841"
|
||||
y="516.01611"
|
||||
id="text3787"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990979,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3789"
|
||||
x="176.80841"
|
||||
y="516.01611">firearm</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="105.77372"
|
||||
y="578.70276"
|
||||
id="text3791"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3793"
|
||||
x="105.77372"
|
||||
y="578.70276">rifle</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="25.647068"
|
||||
y="616.71912"
|
||||
id="text3795"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3797"
|
||||
x="25.647068"
|
||||
y="616.71912">assault rifle</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="212.80423"
|
||||
y="575.19354"
|
||||
id="text3799"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3801"
|
||||
x="212.80423"
|
||||
y="575.19354">shotgun</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="308.13751"
|
||||
y="585.72113"
|
||||
id="text3803"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3805"
|
||||
x="308.13751"
|
||||
y="585.72113">handgun</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="23.307581"
|
||||
y="675.79053"
|
||||
id="text3815"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3817"
|
||||
x="23.307581"
|
||||
y="675.79053">ak-47</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="36.174656"
|
||||
y="703.86414"
|
||||
id="text3819"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3821"
|
||||
x="36.174656"
|
||||
y="703.86414">ak-74</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="76.530373"
|
||||
y="710.88257"
|
||||
id="text3823"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3825"
|
||||
x="76.530373"
|
||||
y="710.88257">m16</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="107.6714"
|
||||
y="698.70959"
|
||||
id="text3827"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3829"
|
||||
x="107.6714"
|
||||
y="698.70959">m4</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="191.16414"
|
||||
y="708.54297"
|
||||
id="text3831"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3833"
|
||||
x="191.16414"
|
||||
y="708.54297">remington model 870</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="116.88616"
|
||||
y="669.94189"
|
||||
id="text3835"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3837"
|
||||
x="116.88616"
|
||||
y="669.94189">ar-15</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="295.85522"
|
||||
y="675.79053"
|
||||
id="text3839"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3841"
|
||||
x="295.85522"
|
||||
y="675.79053">smith & wesson model 10</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="250.23575"
|
||||
y="598.5882"
|
||||
id="text3843"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3845"
|
||||
x="250.23575"
|
||||
y="598.5882">remington</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="338.55051"
|
||||
y="614.37964"
|
||||
id="text3847"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3849"
|
||||
x="338.55051"
|
||||
y="614.37964">smith and wesson</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="293.51581"
|
||||
y="636.60449"
|
||||
id="text3851"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3853"
|
||||
x="293.51581"
|
||||
y="636.60449">m1911</tspan></text>
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 54.885403,605.66122 51.463557,-21.642"
|
||||
id="path3855"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 123.89337,568.2264 66.66874,-47.37844"
|
||||
id="path3857"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 230.32944,564.13196 -38.59765,-43.284"
|
||||
id="path3859"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 328.57808,575.83035 194.65588,521.43289"
|
||||
id="path3861"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 310.44894,625.54846 325.65399,589.8684"
|
||||
id="path3863"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 351.38583,663.56817 c 0,-2.92456 -25.147,-72.52993 -25.147,-72.52993"
|
||||
id="path3865"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 374.19358,617.3596 -22.22291,45.62368"
|
||||
id="path3867"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 239.68643,695.73873 c -1.16957,-2.33967 -9.35699,-116.39886 -9.35699,-116.39886"
|
||||
id="path3869"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 276.52967,602.1517 -35.08872,94.17197"
|
||||
id="path3871"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 125.64778,620.86911 C 125.06306,618.52944 113.3667,582.84937 113.3667,582.84937"
|
||||
id="path3873"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 113.3667,689.3046 60.733545,621.45403"
|
||||
id="path3875"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 85.880569,699.83317 56.055026,624.37862"
|
||||
id="path3877"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 49.622089,692.22922 2.33926,-69.60535"
|
||||
id="path3879"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 36.756192,664.15312 49.622089,621.45403"
|
||||
id="path3881"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="134.43222"
|
||||
y="684.56348"
|
||||
id="text3883"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3885"
|
||||
x="134.43222"
|
||||
y="684.56348">winchester model 70</tspan></text>
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 184.71405,606.83105 117.46047,582.26446"
|
||||
id="path3887"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="75.360664"
|
||||
y="632.51044"
|
||||
id="text3889"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3891"
|
||||
x="75.360664"
|
||||
y="632.51044">semi-automatic rifle</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="153.14783"
|
||||
y="617.88885"
|
||||
id="text3893"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3895"
|
||||
x="153.14783"
|
||||
y="617.88885">bolt-action rifle</tspan></text>
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 125.06306,636.66193 2.33924,23.98168"
|
||||
id="path3897"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 187.0533,621.45403 -3.50893,52.05778"
|
||||
id="path3899"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="color:#000000;fill:none;stroke:#001d00;stroke-width:1.99999964;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect3901"
|
||||
width="414.52017"
|
||||
height="155.0789"
|
||||
x="15.4862"
|
||||
y="304.98154"
|
||||
inkscape:export-filename="C:\code\Hydrus\help\tag_parents_got.png"
|
||||
inkscape:export-xdpi="146.31"
|
||||
inkscape:export-ydpi="146.31" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.45069885px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="170.32401"
|
||||
y="323.65546"
|
||||
id="text3903"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99969264,1.0003075)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3905"
|
||||
x="170.32401"
|
||||
y="323.65546">series:game of thrones</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.45069885px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="26.047758"
|
||||
y="367.05563"
|
||||
id="text3907"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99969264,1.0003075)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3909"
|
||||
x="26.047758"
|
||||
y="367.05563">house stark</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.45069885px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="233.73761"
|
||||
y="367.05563"
|
||||
id="text3911"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99969264,1.0003075)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3913"
|
||||
x="233.73761"
|
||||
y="367.05563">house lannister</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.45069885px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="118.00807"
|
||||
y="367.05563"
|
||||
id="text3915"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99969264,1.0003075)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3917"
|
||||
x="118.00807"
|
||||
y="367.05563">house baratheon</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.45069885px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="342.75174"
|
||||
y="366.01718"
|
||||
id="text3919"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99969264,1.0003075)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3921"
|
||||
x="342.75174"
|
||||
y="366.01718">house targaryen</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="332.19489"
|
||||
y="414.56122"
|
||||
id="text3923"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99969264,1.0003075)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3925"
|
||||
x="332.19489"
|
||||
y="414.56122">character:daenerys targaryen</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="19.009895"
|
||||
y="407.52341"
|
||||
id="text3927"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99969264,1.0003075)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3929"
|
||||
x="19.009895"
|
||||
y="407.52341">character:eddard stark</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="39.53701"
|
||||
y="416.9072"
|
||||
id="text3931"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99969264,1.0003075)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3933"
|
||||
x="39.53701"
|
||||
y="416.9072">character:sansa stark</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="55.372204"
|
||||
y="426.29102"
|
||||
id="text3935"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99969264,1.0003075)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3937"
|
||||
x="55.372204"
|
||||
y="426.29102">character:bran stark</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="220.17557"
|
||||
y="408.10989"
|
||||
id="text3939"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99969264,1.0003075)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3941"
|
||||
x="220.17557"
|
||||
y="408.10989">character:cersei lannister</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="242.46216"
|
||||
y="416.32071"
|
||||
id="text3943"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99969264,1.0003075)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3945"
|
||||
x="242.46216"
|
||||
y="416.32071">character:jaime lannister</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="117.53999"
|
||||
y="407.52335"
|
||||
id="text3947"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99969264,1.0003075)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3949"
|
||||
x="117.53999"
|
||||
y="407.52335">character:robert baratheon</tspan></text>
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 54.768874,357.1951 C 59.459342,356.02176 220.6942,326.10165 220.6942,326.10165"
|
||||
id="path3951"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 161.47703,355.43509 59.80348,-28.74678"
|
||||
id="path3953"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 271.70305,357.78177 c -3.51785,-1.17333 -49.83623,-31.09346 -49.83623,-31.09346"
|
||||
id="path3955"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 384.2743,357.78177 223.62575,326.10165"
|
||||
id="path3957"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 41.870082,400.60863 55.355186,370.6885"
|
||||
id="path3959"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 98.155705,409.99533 C 96.396789,407.06199 57.114114,370.10184 57.114114,370.10184"
|
||||
id="path3961"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 112.22711,417.62203 c 0,-2.93335 -51.595154,-48.10688 -51.595154,-48.10688"
|
||||
id="path3963"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 157.95918,399.43528 0,-28.16012"
|
||||
id="path3965"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 255.87272,399.43528 c 0,-4.10667 9.96725,-27.57345 9.96725,-27.57345"
|
||||
id="path3967"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 304.53633,407.06199 269.35782,371.86183"
|
||||
id="path3969"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 381.34275,403.54197 3.51786,-31.68014"
|
||||
id="path3971"
|
||||
inkscape:connector-curvature="0" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9.28950977px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="224.76657"
|
||||
y="445.36945"
|
||||
id="text3973"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99969262,1.0003075)"><tspan
|
||||
sodipodi:role="line"
|
||||
x="224.76657"
|
||||
y="445.36945"
|
||||
id="tspan3977">e.g. cersei lannister is a member of house lannister, which occurs in the series game of thrones</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.4217844px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="217.65623"
|
||||
y="734.31873"
|
||||
id="text3979"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(0.99990977,1.0000902)"><tspan
|
||||
sodipodi:role="line"
|
||||
x="217.65623"
|
||||
y="734.31873"
|
||||
id="tspan3999">searching for any term should generally return all the files tagged with anything below</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="217.65623"
|
||||
y="747.34595"
|
||||
id="tspan3993">note that a tag can have more than one parent</tspan></text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 36 KiB |
|
@ -0,0 +1,487 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="744.09448819"
|
||||
height="1052.3622047"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.2 r9819"
|
||||
sodipodi:docname="tag_siblings.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="199.89168"
|
||||
inkscape:cy="212.99738"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1038"
|
||||
inkscape:window-height="1015"
|
||||
inkscape:window-x="1989"
|
||||
inkscape:window-y="820"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="color:#000000;fill:#ffff00;fill-opacity:0.24705882;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path2987"
|
||||
sodipodi:cx="319.71329"
|
||||
sodipodi:cy="175.04469"
|
||||
sodipodi:rx="107.58125"
|
||||
sodipodi:ry="107.58125"
|
||||
d="m 427.29454,175.04469 a 107.58125,107.58125 0 1 1 -215.16251,0 107.58125,107.58125 0 1 1 215.16251,0 z"
|
||||
transform="translate(-5.0507632,-36.870568)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="color:#000000;fill:#0000ff;fill-opacity:0.24705882;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path2989"
|
||||
sodipodi:cx="124.24876"
|
||||
sodipodi:cy="148.27565"
|
||||
sodipodi:rx="85.862968"
|
||||
sodipodi:ry="85.862968"
|
||||
d="m 210.11172,148.27565 a 85.862968,85.862968 0 1 1 -171.725932,0 85.862968,85.862968 0 1 1 171.725932,0 z"
|
||||
transform="translate(59.599,-10.101525)" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="18.182747"
|
||||
y="39.17918"
|
||||
id="text3759"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3761"
|
||||
x="18.182747"
|
||||
y="39.17918"
|
||||
style="font-size:14px">character:rei ayanami, x</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="385.58438"
|
||||
y="40.696476"
|
||||
id="text3759-1"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3761-7"
|
||||
x="385.58438"
|
||||
y="40.696476"
|
||||
style="font-size:14px">character:ayanami rei, y</tspan></text>
|
||||
<rect
|
||||
style="color:#000000;fill:none;stroke:#001d00;stroke-width:1.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect3784"
|
||||
width="553.5636"
|
||||
height="321.22849"
|
||||
x="1.0101523"
|
||||
y="0.79338306"
|
||||
inkscape:export-filename="C:\code\Hydrus\help\tag_siblings_venn_1.png"
|
||||
inkscape:export-xdpi="146.31"
|
||||
inkscape:export-ydpi="146.31" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="277.3613"
|
||||
y="286.66656"
|
||||
id="text3786"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3788"
|
||||
x="277.3613"
|
||||
y="286.66656">some files only have x, some only have y, some have both</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="277.3613"
|
||||
y="305.54153"
|
||||
id="tspan3835">we can typically only get x∪y if we search for y after making y⊇x, rendering x pointless</tspan></text>
|
||||
<path
|
||||
style="color:#000000;fill:#ffff00;fill-opacity:0.24705882;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
d="M 314.65625 396.28125 C 282.09122 396.28125 252.9155 410.73365 233.1875 433.59375 C 219.22457 423.76775 202.21489 418 183.84375 418 C 136.42294 418 98 456.42294 98 503.84375 C 98 551.26456 136.42294 589.71875 183.84375 589.71875 C 202.21879 589.71875 219.22291 583.92355 233.1875 574.09375 C 252.9155 596.95385 282.09122 611.4375 314.65625 611.4375 C 374.07174 611.4375 422.25 563.25924 422.25 503.84375 C 422.25 444.42826 374.07174 396.28125 314.65625 396.28125 z "
|
||||
id="path2987-4" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="385.58435"
|
||||
y="406.3717"
|
||||
id="text3759-1-8"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3761-7-8"
|
||||
x="385.58435"
|
||||
y="406.3717"
|
||||
style="font-size:14px">character:ayanami rei, y</tspan></text>
|
||||
<rect
|
||||
style="color:#000000;fill:none;stroke:#001d00;stroke-width:1.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect3784-2"
|
||||
width="553.5636"
|
||||
height="321.22849"
|
||||
x="1.0101514"
|
||||
y="366.46863"
|
||||
inkscape:export-filename="C:\code\Hydrus\help\tag_siblings_venn_2.png"
|
||||
inkscape:export-xdpi="146.31"
|
||||
inkscape:export-ydpi="146.31" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="256.71335"
|
||||
y="652.34174"
|
||||
id="text3786-4"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3788-5"
|
||||
x="256.71335"
|
||||
y="652.34174">all the files that used to have x or y now only have y</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="256.71335"
|
||||
y="669.84174"
|
||||
id="tspan3833">via database magic, any queries for x return y</tspan></text>
|
||||
<rect
|
||||
style="color:#000000;fill:none;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect3837"
|
||||
width="262.13458"
|
||||
height="26.263969"
|
||||
x="13.417028"
|
||||
y="748.92218"
|
||||
inkscape:export-filename="C:\code\Hydrus\help\tag_siblings_usa.png"
|
||||
inkscape:export-xdpi="146.31"
|
||||
inkscape:export-ydpi="146.31" />
|
||||
<g
|
||||
id="g3957"
|
||||
transform="matrix(0.5,0,0,0.5,29.43228,358.76646)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3866"
|
||||
d="m 40.47483,717.37932 35.197325,0"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868"
|
||||
d="m 65.156091,706.09888 12.0982,12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-1"
|
||||
d="m 65.025985,728.96696 12.0982,-12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="19.47794"
|
||||
y="764.57959"
|
||||
id="text3962"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3964"
|
||||
x="19.47794"
|
||||
y="764.57959">murrikkka</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="88.865494"
|
||||
y="765.41455"
|
||||
id="text3966"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3968"
|
||||
x="88.865494"
|
||||
y="765.41455">united states</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="172.51604"
|
||||
y="765.46948"
|
||||
id="text3970"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3972"
|
||||
x="172.51604"
|
||||
y="765.46948">united states of america</tspan></text>
|
||||
<g
|
||||
transform="matrix(0.5,0,0,0.5,43.245636,403.47959)"
|
||||
id="g3957-2">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3866-1"
|
||||
d="m 40.47483,717.37932 35.197325,0"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-6"
|
||||
d="m 65.156091,706.09888 12.0982,12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-1-8"
|
||||
d="m 65.025985,728.96696 12.0982,-12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.5,0,0,0.5,126.89619,403.47959)"
|
||||
id="g3957-5">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3866-7"
|
||||
d="m 40.47483,717.37932 35.197325,0"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-61"
|
||||
d="m 65.156091,706.09888 12.0982,12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-1-89"
|
||||
d="m 65.025985,728.96696 12.0982,-12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<rect
|
||||
style="color:#000000;fill:none;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect3837-2"
|
||||
width="82.832489"
|
||||
height="62.124378"
|
||||
x="12.911961"
|
||||
y="790.84351"
|
||||
inkscape:export-filename="C:\code\Hydrus\help\tag_siblings_what_is_a_man.png"
|
||||
inkscape:export-xdpi="146.31"
|
||||
inkscape:export-ydpi="146.31" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="18.972874"
|
||||
y="806.50092"
|
||||
id="text3962-7"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3964-9"
|
||||
x="18.972874"
|
||||
y="806.50092">boy</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="18.968479"
|
||||
y="843.32935"
|
||||
id="text3966-5"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3968-4"
|
||||
x="18.968479"
|
||||
y="843.32935">man</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="66.862312"
|
||||
y="826.34912"
|
||||
id="text3970-3"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3972-1"
|
||||
x="66.862312"
|
||||
y="826.34912">male</tspan></text>
|
||||
<g
|
||||
transform="matrix(0.48296291,0.12940952,-0.12940952,0.48296291,114.52425,457.13986)"
|
||||
id="g3957-2-2">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3866-1-3"
|
||||
d="m 40.47483,717.37932 35.197325,0"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-6-3"
|
||||
d="m 65.156091,706.09888 12.0982,12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-1-8-4"
|
||||
d="m 65.025985,728.96696 12.0982,-12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.48296291,-0.12940952,0.12940952,0.48296291,-71.198483,495.61989)"
|
||||
id="g3957-5-1">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3866-7-1"
|
||||
d="m 40.47483,717.37932 35.197325,0"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-61-3"
|
||||
d="m 65.156091,706.09888 12.0982,12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-1-89-8"
|
||||
d="m 65.025985,728.96696 12.0982,-12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<rect
|
||||
style="color:#000000;fill:none;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect3837-2-7"
|
||||
width="347.49246"
|
||||
height="69.837944"
|
||||
x="118.97797"
|
||||
y="791.60114"
|
||||
inkscape:export-filename="C:\code\Hydrus\help\tag_siblings_yo_dawg.png"
|
||||
inkscape:export-xdpi="146.31"
|
||||
inkscape:export-ydpi="146.31" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="124.02876"
|
||||
y="812.30927"
|
||||
id="text3962-7-4"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3964-9-2"
|
||||
x="124.02876"
|
||||
y="812.30927">Calvin Broadus</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="333.09293"
|
||||
y="811.36224"
|
||||
id="text3966-5-7"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3968-4-7"
|
||||
x="333.09293"
|
||||
y="811.36224">snoop dogg</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="222.1778"
|
||||
y="811.36224"
|
||||
id="text3970-3-9"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3972-1-3"
|
||||
x="222.1778"
|
||||
y="811.36224">snoop doggy dogg</tspan></text>
|
||||
<g
|
||||
transform="matrix(0.35355339,-0.35355339,0.35355339,0.35355339,128.03402,599.30536)"
|
||||
id="g3957-5-1-5">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3866-7-1-0"
|
||||
d="m 40.47483,717.37932 35.197325,0"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-61-3-2"
|
||||
d="m 65.156091,706.09888 12.0982,12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-1-89-8-8"
|
||||
d="m 65.025985,728.96696 12.0982,-12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="416.97293"
|
||||
y="811.41498"
|
||||
id="text4134"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4136"
|
||||
x="416.97293"
|
||||
y="811.41498">snoop lion</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
x="335.1506"
|
||||
y="848.42224"
|
||||
id="text4138"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4140"
|
||||
x="335.1506"
|
||||
y="848.42224">dj snoopadelic</tspan></text>
|
||||
<g
|
||||
transform="matrix(0.5,0,0,0.5,368.973,450.32161)"
|
||||
id="g3957-5-6">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3866-7-0"
|
||||
d="m 40.47483,717.37932 35.197325,0"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-61-2"
|
||||
d="m 65.156091,706.09888 12.0982,12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-1-89-4"
|
||||
d="m 65.025985,728.96696 12.0982,-12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.5,0,0,0.5,285.09299,450.32161)"
|
||||
id="g3957-5-8">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3866-7-6"
|
||||
d="m 40.47483,717.37932 35.197325,0"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-61-5"
|
||||
d="m 65.156091,706.09888 12.0982,12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-1-89-0"
|
||||
d="m 65.025985,728.96696 12.0982,-12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.5,0,0,0.5,174.17783,450.32161)"
|
||||
id="g3957-5-9">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3866-7-00"
|
||||
d="m 40.47483,717.37932 35.197325,0"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-61-6"
|
||||
d="m 65.156091,706.09888 12.0982,12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3868-1-89-1"
|
||||
d="m 65.025985,728.96696 12.0982,-12.09821"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 8.4 KiB |
|
@ -604,10 +604,14 @@ class AutocompleteMatchesCounted():
|
|||
|
||||
class AutocompleteMatchesPredicates():
|
||||
|
||||
def __init__( self, predicates ):
|
||||
def __init__( self, predicates, do_siblings_collapse = True ):
|
||||
|
||||
self._predicates = predicates
|
||||
|
||||
siblings_manager = wx.GetApp().GetTagSiblingsManager()
|
||||
|
||||
if do_siblings_collapse: self._predicates = siblings_manager.CollapsePredicates( self._predicates )
|
||||
|
||||
def cmp_func( x, y ): return cmp( x.GetCount(), y.GetCount() )
|
||||
|
||||
self._predicates.sort( cmp = cmp_func, reverse = True )
|
||||
|
@ -1902,7 +1906,7 @@ class FileSystemPredicates():
|
|||
|
||||
def OkFirstRound( self, width, height ):
|
||||
|
||||
if len( self._predicates[ self.RATIO ] ) > 0 and ( width is None or height is None ): return False
|
||||
if len( self._predicates[ self.RATIO ] ) > 0 and ( width is None or height is None or width == 0 or height == 0): return False
|
||||
|
||||
if False in ( function( float( width ) / float( height ), arg ) for ( function, arg ) in self._predicates[ self.RATIO ] ): return False
|
||||
|
||||
|
@ -2621,6 +2625,214 @@ class ServiceRemoteRestrictedDepotMessage( ServiceRemoteRestrictedDepot ):
|
|||
|
||||
def Decrypt( self, encrypted_message ): return HydrusMessageHandling.UnpackageDeliveredMessage( encrypted_message, self._private_key )
|
||||
|
||||
class TagSiblingsManager():
|
||||
|
||||
def __init__( self ):
|
||||
|
||||
self._tag_service_precedence = wx.GetApp().Read( 'tag_service_precedence' )
|
||||
|
||||
# I should offload this to a thread (rather than the gui thread), and have an event to say when it is ready
|
||||
|
||||
self._RefreshSiblings()
|
||||
|
||||
self._lock = threading.Lock()
|
||||
|
||||
HC.pubsub.sub( self, 'RefreshSiblings', 'notify_new_siblings' )
|
||||
|
||||
|
||||
def _RefreshSiblings( self ):
|
||||
|
||||
unprocessed_siblings = wx.GetApp().Read( 'tag_siblings' )
|
||||
|
||||
t_s_p = list( self._tag_service_precedence )
|
||||
|
||||
t_s_p.reverse()
|
||||
|
||||
processed_siblings = {}
|
||||
|
||||
for service_identifier in t_s_p:
|
||||
|
||||
if service_identifier in unprocessed_siblings: processed_siblings.update( unprocessed_siblings[ service_identifier ] )
|
||||
|
||||
|
||||
# now to collapse chains
|
||||
# A -> B and B -> C goes to A -> C and B -> C
|
||||
|
||||
self._siblings = {}
|
||||
|
||||
for ( old_tag, new_tag ) in processed_siblings.items():
|
||||
|
||||
# adding A -> B
|
||||
|
||||
if new_tag in self._siblings: # B -> C already added, so add A -> C
|
||||
|
||||
new_tag = self._siblings[ new_tag ]
|
||||
|
||||
self._siblings[ old_tag ] = new_tag
|
||||
|
||||
else:
|
||||
|
||||
while new_tag in processed_siblings: # while B -> C, C -> D, D -> E in preprocessed list, pursue endpoint F
|
||||
|
||||
new_tag = processed_siblings[ new_tag ]
|
||||
|
||||
if new_tag == old_tag: break # we have a loop!
|
||||
|
||||
|
||||
if old_tag != new_tag: self._siblings[ old_tag ] = new_tag
|
||||
|
||||
|
||||
|
||||
self._reverse_lookup = collections.defaultdict( list )
|
||||
|
||||
for ( old_tag, new_tag ) in self._siblings.items(): self._reverse_lookup[ new_tag ].append( old_tag )
|
||||
|
||||
HC.pubsub.pub( 'new_siblings_gui' )
|
||||
|
||||
|
||||
def GetAutocompleteSiblings( self, half_complete_tag ):
|
||||
|
||||
key_based_matching_values = { self._siblings[ key ] for key in self._siblings.keys() if HC.SearchEntryMatchesTag( half_complete_tag, key ) }
|
||||
|
||||
value_based_matching_values = { value for value in self._siblings.values() if HC.SearchEntryMatchesTag( half_complete_tag, value ) }
|
||||
|
||||
matching_values = key_based_matching_values.union( value_based_matching_values )
|
||||
|
||||
# all the matching values have a matching sibling somewhere in their network
|
||||
# so now fetch the networks
|
||||
|
||||
lists_of_matching_keys = [ self._reverse_lookup[ value ] for value in matching_values ]
|
||||
|
||||
matching_keys = itertools.chain.from_iterable( lists_of_matching_keys )
|
||||
|
||||
matches = matching_values.union( matching_keys )
|
||||
|
||||
return matches
|
||||
|
||||
|
||||
def GetSibling( self, tag ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
if tag in self._siblings: return self._siblings[ tag ]
|
||||
else: return None
|
||||
|
||||
|
||||
|
||||
def GetAllSiblings( self, tag ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
if tag in self._siblings:
|
||||
|
||||
new_tag = self._siblings[ tag ]
|
||||
|
||||
elif tag in self._reverse_lookup: new_tag = tag
|
||||
else: return [ tag ]
|
||||
|
||||
all_siblings = list( self._reverse_lookup[ new_tag ] )
|
||||
|
||||
all_siblings.append( new_tag )
|
||||
|
||||
return all_siblings
|
||||
|
||||
|
||||
|
||||
def RefreshSiblings( self ):
|
||||
|
||||
with self._lock: self._RefreshSiblings()
|
||||
|
||||
|
||||
def CollapseCountedTagList( self, tag_list ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
results = collections.Counter()
|
||||
|
||||
for ( tag, count ) in tag_list:
|
||||
|
||||
if tag in self._siblings: tag = self._siblings[ tag ]
|
||||
|
||||
results[ tag ] += count
|
||||
|
||||
|
||||
results = results.items()
|
||||
|
||||
return results
|
||||
|
||||
|
||||
|
||||
def CollapsePredicates( self, predicates ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
results = [ predicate for predicate in predicates if predicate.GetPredicateType() != HC.PREDICATE_TYPE_TAG ]
|
||||
|
||||
tag_predicates = [ predicate for predicate in predicates if predicate.GetPredicateType() == HC.PREDICATE_TYPE_TAG ]
|
||||
|
||||
tags_to_predicates = { predicate.GetTag() : predicate for predicate in predicates if predicate.GetPredicateType() == HC.PREDICATE_TYPE_TAG }
|
||||
|
||||
tags = tags_to_predicates.keys()
|
||||
|
||||
tags_to_include_in_results = set()
|
||||
|
||||
for tag in tags:
|
||||
|
||||
if tag in self._siblings:
|
||||
|
||||
old_tag = tag
|
||||
old_predicate = tags_to_predicates[ old_tag ]
|
||||
|
||||
new_tag = self._siblings[ old_tag ]
|
||||
|
||||
if new_tag not in tags_to_predicates:
|
||||
|
||||
( old_operator, old_tag ) = old_predicate.GetValue()
|
||||
|
||||
new_predicate = HC.Predicate( HC.PREDICATE_TYPE_TAG, ( old_operator, new_tag ), 0 )
|
||||
|
||||
tags_to_predicates[ new_tag ] = new_predicate
|
||||
|
||||
tags_to_include_in_results.add( new_tag )
|
||||
|
||||
|
||||
new_predicate = tags_to_predicates[ new_tag ]
|
||||
|
||||
count = old_predicate.GetCount()
|
||||
|
||||
new_predicate.AddToCount( count )
|
||||
|
||||
else: tags_to_include_in_results.add( tag )
|
||||
|
||||
|
||||
results.extend( [ tags_to_predicates[ tag ] for tag in tags_to_include_in_results ] )
|
||||
|
||||
return results
|
||||
|
||||
|
||||
|
||||
def CollapseTagList( self, tags ):
|
||||
|
||||
with self._lock: return { self._siblings[ tag ] if tag in self._siblings else tag for tag in tags }
|
||||
|
||||
|
||||
def CollapseTagsToCount( self, tags_to_count ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
results = collections.Counter()
|
||||
|
||||
for ( tag, count ) in tags_to_count.items():
|
||||
|
||||
if tag in self._siblings: tag = self._siblings[ tag ]
|
||||
|
||||
results[ tag ] += count
|
||||
|
||||
|
||||
return results
|
||||
|
||||
|
||||
|
||||
class ThumbnailCache():
|
||||
|
||||
def __init__( self, db, options ):
|
||||
|
|
|
@ -18,6 +18,17 @@ ID_MAINTENANCE_EVENT_TIMER = wx.NewId()
|
|||
|
||||
class Controller( wx.App ):
|
||||
|
||||
def _Read( self, action, *args, **kwargs ):
|
||||
|
||||
if action == 'options': return self._options
|
||||
elif action == 'tag_service_precedence': return self._tag_service_precedence
|
||||
elif action == 'file': return self._db.ReadFile( *args, **kwargs )
|
||||
elif action == 'thumbnail': return self._db.ReadThumbnail( *args, **kwargs )
|
||||
else: return self._db.Read( action, HC.HIGH_PRIORITY, *args, **kwargs )
|
||||
|
||||
|
||||
def _Write( self, action, priority, *args, **kwargs ): self._db.Write( action, priority, *args, **kwargs )
|
||||
|
||||
def ClearCaches( self ):
|
||||
|
||||
self._thumbnail_cache.Clear()
|
||||
|
@ -120,10 +131,12 @@ class Controller( wx.App ):
|
|||
|
||||
def GetSessionKey( self, service_identifier ): return self._session_manager.GetSessionKey( service_identifier )
|
||||
|
||||
def GetWebCookies( self, name ): return self._web_session_manager.GetCookies( name )
|
||||
def GetTagSiblingsManager( self ): return self._tag_siblings_manager
|
||||
|
||||
def GetThumbnailCache( self ): return self._thumbnail_cache
|
||||
|
||||
def GetWebCookies( self, name ): return self._web_session_manager.GetCookies( name )
|
||||
|
||||
def MaintainDB( self ):
|
||||
|
||||
now = int( time.time() )
|
||||
|
@ -131,7 +144,8 @@ class Controller( wx.App ):
|
|||
shutdown_timestamps = self.Read( 'shutdown_timestamps' )
|
||||
|
||||
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_VACUUM ] > 86400 * 5: self.Write( 'vacuum' )
|
||||
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_FATTEN_AC_CACHE ] > 50000: self.Write( 'fatten_autocomplete_cache' )
|
||||
# try no fatten, since we made the recent A/C changes
|
||||
#if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_FATTEN_AC_CACHE ] > 50000: self.Write( 'fatten_autocomplete_cache' )
|
||||
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_DELETE_ORPHANS ] > 86400 * 3: self.Write( 'delete_orphans' )
|
||||
|
||||
|
||||
|
@ -157,6 +171,7 @@ class Controller( wx.App ):
|
|||
|
||||
self._session_manager = HydrusSessions.HydrusSessionManagerClient()
|
||||
self._web_session_manager = CC.WebSessionManagerClient()
|
||||
self._tag_siblings_manager = CC.TagSiblingsManager()
|
||||
|
||||
self.SetSplashText( 'caches' )
|
||||
|
||||
|
@ -233,15 +248,6 @@ class Controller( wx.App ):
|
|||
|
||||
def ProcessServerRequest( self, *args, **kwargs ): return self._db.ProcessRequest( *args, **kwargs )
|
||||
|
||||
def _Read( self, action, *args, **kwargs ):
|
||||
|
||||
if action == 'options': return self._options
|
||||
elif action == 'tag_service_precedence': return self._tag_service_precedence
|
||||
elif action == 'file': return self._db.ReadFile( *args, **kwargs )
|
||||
elif action == 'thumbnail': return self._db.ReadThumbnail( *args, **kwargs )
|
||||
else: return self._db.Read( action, HC.HIGH_PRIORITY, *args, **kwargs )
|
||||
|
||||
|
||||
def Read( self, action, *args, **kwargs ):
|
||||
|
||||
self._last_idle_time = int( time.time() )
|
||||
|
@ -269,8 +275,6 @@ class Controller( wx.App ):
|
|||
|
||||
|
||||
|
||||
def _Write( self, action, priority, *args, **kwargs ): self._db.Write( action, priority, *args, **kwargs )
|
||||
|
||||
def Write( self, action, *args, **kwargs ):
|
||||
|
||||
self._last_idle_time = int( time.time() )
|
||||
|
|
|
@ -1289,7 +1289,21 @@ class TagDB():
|
|||
|
||||
result = c.execute( 'SELECT 1 FROM existing_tags WHERE namespace_id = ? AND tag_id = ?;', ( namespace_id, tag_id ) ).fetchone()
|
||||
|
||||
if result is None: c.execute( 'INSERT INTO existing_tags ( namespace_id, tag_id ) VALUES ( ?, ? );', ( namespace_id, tag_id ) )
|
||||
if result is None:
|
||||
|
||||
c.execute( 'INSERT INTO existing_tags ( namespace_id, tag_id ) VALUES ( ?, ? );', ( namespace_id, tag_id ) )
|
||||
|
||||
tag_service_identifiers = self._GetServiceIdentifiers( c, ( HC.TAG_REPOSITORY, HC.LOCAL_TAG ) )
|
||||
file_service_identifiers = self._GetServiceIdentifiers( c, ( HC.FILE_REPOSITORY, HC.LOCAL_FILE ) )
|
||||
|
||||
tag_service_identifiers.add( HC.NULL_SERVICE_IDENTIFIER )
|
||||
file_service_identifiers.add( HC.NULL_SERVICE_IDENTIFIER )
|
||||
|
||||
tag_service_ids = [ self._GetServiceId( c, service_identifier ) for service_identifier in tag_service_identifiers ]
|
||||
file_service_ids = [ self._GetServiceId( c, service_identifier ) for service_identifier in file_service_identifiers ]
|
||||
|
||||
c.executemany( 'INSERT OR IGNORE INTO autocomplete_tags_cache ( file_service_id, tag_service_id, namespace_id, tag_id, current_count, pending_count ) VALUES ( ?, ?, ?, ?, ?, ? );', [ ( file_service_id, tag_service_id, namespace_id, tag_id, 0, 0 ) for ( tag_service_id, file_service_id ) in itertools.product( tag_service_ids, file_service_ids ) ] )
|
||||
|
||||
|
||||
return ( namespace_id, tag_id )
|
||||
|
||||
|
@ -2050,7 +2064,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
return all_downloads
|
||||
|
||||
|
||||
def _GetAutocompleteTags( self, c, tag_service_identifier = HC.NULL_SERVICE_IDENTIFIER, file_service_identifier = HC.NULL_SERVICE_IDENTIFIER, half_complete_tag = '', include_current = True, include_pending = True ):
|
||||
def _GetAutocompleteTags( self, c, tag_service_identifier = HC.NULL_SERVICE_IDENTIFIER, file_service_identifier = HC.NULL_SERVICE_IDENTIFIER, half_complete_tag = '', include_current = True, include_pending = True, do_siblings_collapse = True ):
|
||||
|
||||
if tag_service_identifier == HC.NULL_SERVICE_IDENTIFIER:
|
||||
|
||||
|
@ -2139,6 +2153,16 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
results = { result for result in c.execute( 'SELECT namespace_id, tag_id FROM existing_tags WHERE ' + predicates_phrase + ';' ) }
|
||||
|
||||
# now fetch siblings, add to results set
|
||||
|
||||
siblings_manager = wx.GetApp().GetTagSiblingsManager()
|
||||
|
||||
all_associated_sibling_tags = siblings_manager.GetAutocompleteSiblings( half_complete_tag )
|
||||
|
||||
sibling_results = [ self._GetNamespaceIdTagId( c, sibling_tag ) for sibling_tag in all_associated_sibling_tags ]
|
||||
|
||||
results.update( sibling_results )
|
||||
|
||||
# fetch what we can from cache
|
||||
|
||||
cache_results = []
|
||||
|
@ -2187,7 +2211,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
[ tags_to_count.update( { ( 1, tag_id ) : num_tags } ) for ( namespace_id, tag_id, num_tags ) in results if namespace_id != 1 and tag_id in unnamespaced_tag_ids ]
|
||||
|
||||
|
||||
matches = CC.AutocompleteMatchesPredicates( [ HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', self._GetNamespaceTag( c, namespace_id, tag_id ) ), num_tags ) for ( ( namespace_id, tag_id ), num_tags ) in tags_to_count.items() if num_tags > 0 ] )
|
||||
matches = CC.AutocompleteMatchesPredicates( [ HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', self._GetNamespaceTag( c, namespace_id, tag_id ) ), num_tags ) for ( ( namespace_id, tag_id ), num_tags ) in tags_to_count.items() if num_tags > 0 ], do_siblings_collapse = do_siblings_collapse )
|
||||
|
||||
return matches
|
||||
|
||||
|
@ -2254,7 +2278,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
def _GetHashIdsFromTag( self, c, file_service_identifier, tag_service_identifier, tag, include_current_tags, include_pending_tags ):
|
||||
|
||||
hash_ids = set()
|
||||
# this does siblings too!
|
||||
|
||||
if file_service_identifier == HC.NULL_SERVICE_IDENTIFIER:
|
||||
|
||||
|
@ -2304,17 +2328,26 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
|
||||
|
||||
if ':' in tag:
|
||||
hash_ids = set()
|
||||
|
||||
siblings_manager = wx.GetApp().GetTagSiblingsManager()
|
||||
|
||||
tags = siblings_manager.GetAllSiblings( tag )
|
||||
|
||||
for tag in tags:
|
||||
|
||||
( namespace, tag ) = tag.split( ':', 1 )
|
||||
|
||||
if include_current_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM namespaces, ( tags, ' + current_tables_phrase + ' USING ( tag_id ) ) USING ( namespace_id ) WHERE ' + current_predicates_phrase + 'namespace = ? AND tag = ?;', ( namespace, tag ) ) ] )
|
||||
if include_pending_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM namespaces, ( tags, ' + pending_tables_phrase + ' USING ( tag_id ) ) USING ( namespace_id ) WHERE ' + pending_predicates_phrase + 'namespace = ? AND tag = ?;', ( namespace, tag ) ) ] )
|
||||
|
||||
else:
|
||||
|
||||
if include_current_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM tags, ' + current_tables_phrase + ' USING ( tag_id ) WHERE ' + current_predicates_phrase + 'tag = ?;', ( tag, ) ) ] )
|
||||
if include_pending_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM tags, ' + pending_tables_phrase + ' USING ( tag_id ) WHERE ' + pending_predicates_phrase + 'tag = ?;', ( tag, ) ) ] )
|
||||
if ':' in tag:
|
||||
|
||||
( namespace, tag ) = tag.split( ':', 1 )
|
||||
|
||||
if include_current_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM namespaces, ( tags, ' + current_tables_phrase + ' USING ( tag_id ) ) USING ( namespace_id ) WHERE ' + current_predicates_phrase + 'namespace = ? AND tag = ?;', ( namespace, tag ) ) ] )
|
||||
if include_pending_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM namespaces, ( tags, ' + pending_tables_phrase + ' USING ( tag_id ) ) USING ( namespace_id ) WHERE ' + pending_predicates_phrase + 'namespace = ? AND tag = ?;', ( namespace, tag ) ) ] )
|
||||
|
||||
else:
|
||||
|
||||
if include_current_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM tags, ' + current_tables_phrase + ' USING ( tag_id ) WHERE ' + current_predicates_phrase + 'tag = ?;', ( tag, ) ) ] )
|
||||
if include_pending_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM tags, ' + pending_tables_phrase + ' USING ( tag_id ) WHERE ' + pending_predicates_phrase + 'tag = ?;', ( tag, ) ) ] )
|
||||
|
||||
|
||||
|
||||
return hash_ids
|
||||
|
@ -3025,7 +3058,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
predicates = []
|
||||
|
||||
if service_type in ( HC.NULL_SERVICE, HC.TAG_REPOSITORY, HC.LOCAL_TAG ):
|
||||
if service_type == HC.NULL_SERVICE: predicates.extend( [ HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( system_predicate_type, None ), None ) for system_predicate_type in [ HC.SYSTEM_PREDICATE_TYPE_EVERYTHING, HC.SYSTEM_PREDICATE_TYPE_UNTAGGED, HC.SYSTEM_PREDICATE_TYPE_NUM_TAGS, HC.SYSTEM_PREDICATE_TYPE_HASH ] ] )
|
||||
elif service_type in ( HC.TAG_REPOSITORY, HC.LOCAL_TAG ):
|
||||
|
||||
service_info = self._GetServiceInfoSpecific( c, service_id, service_type, { HC.SERVICE_INFO_NUM_FILES } )
|
||||
|
||||
|
@ -3139,7 +3173,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
def _GetSubscriptions( self, c ):
|
||||
|
||||
subscriptions = [ ( site_download_type, name, query_type, query, frequency_type, frequency_number, dict( advanced_tag_options ), advanced_import_options, last_checked, url_cache ) for ( site_download_type, name, ( query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) ) in c.execute( 'SELECT site_download_type, name, info FROM subscriptions;', ) ]
|
||||
subscriptions = [ ( site_download_type, name, query_type, query, frequency_type, frequency_number, dict( advanced_tag_options ), advanced_import_options, last_checked, url_cache, paused ) for ( site_download_type, name, ( query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) ) in c.execute( 'SELECT site_download_type, name, info FROM subscriptions;' ) ]
|
||||
|
||||
return subscriptions
|
||||
|
||||
|
@ -3153,6 +3187,37 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
return [ self._GetServiceIdentifier( c, service_id ) for service_id in service_ids ]
|
||||
|
||||
|
||||
def _GetTagSiblings( self, c, service_identifier = None ):
|
||||
|
||||
if service_identifier is None:
|
||||
|
||||
results = HC.BuildKeyToListDict( ( ( service_id, ( old_namespace_id, old_tag_id, new_namespace_id, new_tag_id ) ) for ( service_id, old_namespace_id, old_tag_id, new_namespace_id, new_tag_id ) in c.execute( 'SELECT service_id, old_namespace_id, old_tag_id, new_namespace_id, new_tag_id FROM tag_siblings;' ) ) )
|
||||
|
||||
processed_results = {}
|
||||
|
||||
for ( service_id, n_t_ids ) in results.items():
|
||||
|
||||
service_identifier = self._GetServiceIdentifier( c, service_id )
|
||||
|
||||
n_ts = [ ( self._GetNamespaceTag( c, old_namespace_id, old_tag_id ), self._GetNamespaceTag( c, new_namespace_id, new_tag_id ) ) for ( old_namespace_id, old_tag_id, new_namespace_id, new_tag_id ) in n_t_ids ]
|
||||
|
||||
processed_results[ service_identifier ] = n_ts
|
||||
|
||||
|
||||
return processed_results
|
||||
|
||||
else:
|
||||
|
||||
service_id = self._GetServiceId( c, service_identifier )
|
||||
|
||||
n_t_ids = c.execute( 'SELECT old_namespace_id, old_tag_id, new_namespace_id, new_tag_id FROM tag_siblings WHERE service_id = ?;', ( service_id, ) ).fetchall()
|
||||
|
||||
n_ts = [ ( self._GetNamespaceTag( c, old_namespace_id, old_tag_id ), self._GetNamespaceTag( c, new_namespace_id, new_tag_id ) ) for ( old_namespace_id, old_tag_id, new_namespace_id, new_tag_id ) in n_t_ids ]
|
||||
|
||||
return n_ts
|
||||
|
||||
|
||||
|
||||
def _GetThumbnailHashesIShouldHave( self, c, service_identifier ):
|
||||
|
||||
service_id = self._GetServiceId( c, service_identifier )
|
||||
|
@ -3794,9 +3859,9 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
def _SetSubscription( self, c, subscription ):
|
||||
|
||||
( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) = subscription
|
||||
( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) = subscription
|
||||
|
||||
info = [ query_type, query, frequency_type, frequency_number, advanced_tag_options.items(), advanced_import_options, last_checked, url_cache ]
|
||||
info = [ query_type, query, frequency_type, frequency_number, advanced_tag_options.items(), advanced_import_options, last_checked, url_cache, paused ]
|
||||
|
||||
c.execute( 'DELETE FROM subscriptions WHERE site_download_type = ? AND name = ?;', ( site_download_type, name ) )
|
||||
|
||||
|
@ -3807,7 +3872,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
HC.repos_or_subs_changed = True
|
||||
|
||||
inserts = [ ( site_download_type, name, [ query_type, query, frequency_type, frequency_number, advanced_tag_options.items(), advanced_import_options, last_checked, url_cache ] ) for ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) in subscriptions ]
|
||||
inserts = [ ( site_download_type, name, [ query_type, query, frequency_type, frequency_number, advanced_tag_options.items(), advanced_import_options, last_checked, url_cache, paused ] ) for ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) in subscriptions ]
|
||||
|
||||
c.execute( 'DELETE FROM subscriptions;' )
|
||||
|
||||
|
@ -4430,6 +4495,38 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
|
||||
|
||||
def _UpdateTagSiblings( self, c, edit_log ):
|
||||
|
||||
for ( service_identifier, sub_edit_log ) in edit_log:
|
||||
|
||||
service_id = self._GetServiceId( c, service_identifier )
|
||||
|
||||
for ( action, data ) in sub_edit_log:
|
||||
|
||||
if action == HC.ADD:
|
||||
|
||||
( old_tag, new_tag ) = data
|
||||
|
||||
( old_namespace_id, old_tag_id ) = self._GetNamespaceIdTagId( c, old_tag )
|
||||
|
||||
( new_namespace_id, new_tag_id ) = self._GetNamespaceIdTagId( c, new_tag )
|
||||
|
||||
c.execute( 'INSERT OR IGNORE INTO tag_siblings ( service_id, old_namespace_id, old_tag_id, new_namespace_id, new_tag_id ) VALUES ( ?, ?, ?, ?, ? );', ( service_id, old_namespace_id, old_tag_id, new_namespace_id, new_tag_id ) )
|
||||
|
||||
elif action == HC.DELETE:
|
||||
|
||||
( old_tag, new_tag ) = data
|
||||
|
||||
( old_namespace_id, old_tag_id ) = self._GetNamespaceIdTagId( c, old_tag )
|
||||
|
||||
c.execute( 'DELETE FROM tag_siblings WHERE service_id = ? AND old_namespace_id = ? AND old_tag_id = ?;', ( service_id, old_namespace_id, old_tag_id ) )
|
||||
|
||||
|
||||
|
||||
|
||||
self.pub( 'notify_new_siblings' )
|
||||
|
||||
|
||||
class DB( ServiceDB ):
|
||||
|
||||
def __init__( self ):
|
||||
|
@ -4518,7 +4615,7 @@ class DB( ServiceDB ):
|
|||
|
||||
HC.DAEMONWorker( 'DownloadFiles', self.DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ) )
|
||||
HC.DAEMONWorker( 'DownloadThumbnails', self.DAEMONDownloadThumbnails, ( 'notify_new_permissions', 'notify_new_thumbnails' ) )
|
||||
HC.DAEMONWorker( 'ResizeThumbnails', self.DAEMONResizeThumbnails, init_wait = 1200 )
|
||||
HC.DAEMONWorker( 'ResizeThumbnails', self.DAEMONResizeThumbnails, init_wait = 600 )
|
||||
HC.DAEMONWorker( 'SynchroniseAccounts', self.DAEMONSynchroniseAccounts, ( 'notify_new_services', 'permissions_are_stale' ) )
|
||||
HC.DAEMONWorker( 'SynchroniseMessages', self.DAEMONSynchroniseMessages, ( 'notify_new_permissions', 'notify_check_messages' ), period = 60 )
|
||||
HC.DAEMONWorker( 'SynchroniseRepositoriesAndSubscriptions', self.DAEMONSynchroniseRepositoriesAndSubscriptions, ( 'notify_new_permissions', 'notify_new_subscriptions' ) )
|
||||
|
@ -4780,6 +4877,8 @@ class DB( ServiceDB ):
|
|||
|
||||
c.execute( 'CREATE TABLE tag_service_precedence ( service_id INTEGER PRIMARY KEY REFERENCES services ON DELETE CASCADE, precedence INTEGER );' )
|
||||
|
||||
c.execute( 'CREATE TABLE tag_siblings ( service_id INTEGER REFERENCES services ON DELETE CASCADE, old_namespace_id INTEGER, old_tag_id INTEGER, new_namespace_id INTEGER, new_tag_id INTEGER, PRIMARY KEY ( service_id, old_namespace_id, old_tag_id ) );' )
|
||||
|
||||
c.execute( 'CREATE TABLE tags ( tag_id INTEGER PRIMARY KEY, tag TEXT );' )
|
||||
c.execute( 'CREATE UNIQUE INDEX tags_tag_index ON tags ( tag );' )
|
||||
|
||||
|
@ -5235,7 +5334,7 @@ class DB( ServiceDB ):
|
|||
search_url = 'http://e621.net/post/index?page=%index%&tags=%tags%'
|
||||
search_separator = '%20'
|
||||
advance_by_page_num = True
|
||||
thumb_classname = 'thumb blacklist'
|
||||
thumb_classname = 'thumb'
|
||||
image_id = None
|
||||
image_data = 'Download'
|
||||
tag_classnames_to_namespaces = { 'tag-type-general' : '', 'tag-type-character' : 'character', 'tag-type-copyright' : 'series', 'tag-type-artist' : 'creator' }
|
||||
|
@ -5263,6 +5362,64 @@ class DB( ServiceDB ):
|
|||
|
||||
|
||||
|
||||
if version < 69:
|
||||
|
||||
boorus = []
|
||||
|
||||
name = 'e621'
|
||||
search_url = 'http://e621.net/post/index?page=%index%&tags=%tags%'
|
||||
search_separator = '%20'
|
||||
advance_by_page_num = True
|
||||
thumb_classname = 'thumb'
|
||||
image_id = None
|
||||
image_data = 'Download'
|
||||
tag_classnames_to_namespaces = { 'tag-type-general' : '', 'tag-type-character' : 'character', 'tag-type-copyright' : 'series', 'tag-type-artist' : 'creator' }
|
||||
|
||||
boorus.append( CC.Booru( name, search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces ) )
|
||||
|
||||
for booru in boorus:
|
||||
|
||||
name = booru.GetName()
|
||||
|
||||
c.execute( 'DELETE FROM boorus WHERE name = ?;', ( name, ) )
|
||||
|
||||
c.execute( 'INSERT INTO boorus VALUES ( ?, ? );', ( name, booru ) )
|
||||
|
||||
|
||||
#
|
||||
|
||||
c.execute( 'CREATE TABLE tag_siblings ( service_id INTEGER REFERENCES services ON DELETE CASCADE, old_namespace_id INTEGER, old_tag_id INTEGER, new_namespace_id INTEGER, new_tag_id INTEGER, PRIMARY KEY ( service_id, old_namespace_id, old_tag_id ) );' )
|
||||
|
||||
#
|
||||
|
||||
subscriptions = c.execute( 'SELECT site_download_type, name, info FROM subscriptions;' ).fetchall()
|
||||
|
||||
paused = False
|
||||
|
||||
for ( site_download_type, name, ( query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) ) in subscriptions:
|
||||
|
||||
updated_info = [ query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ]
|
||||
|
||||
c.execute( 'UPDATE subscriptions SET info = ? WHERE site_download_type = ? AND name = ?;', ( updated_info, site_download_type, name ) )
|
||||
|
||||
|
||||
#
|
||||
|
||||
wx.GetApp().SetSplashText( 'generating A/C cache' )
|
||||
|
||||
try:
|
||||
|
||||
tag_service_identifiers = self._GetServiceIdentifiers( c, ( HC.TAG_REPOSITORY, HC.LOCAL_TAG ) )
|
||||
file_service_identifiers = self._GetServiceIdentifiers( c, ( HC.FILE_REPOSITORY, HC.LOCAL_FILE ) )
|
||||
|
||||
tag_service_identifiers.add( HC.NULL_SERVICE_IDENTIFIER )
|
||||
file_service_identifiers.add( HC.NULL_SERVICE_IDENTIFIER )
|
||||
|
||||
for ( tag_service_identifier, file_service_identifier ) in itertools.product( tag_service_identifiers, file_service_identifiers ): self._GetAutocompleteTags( c, tag_service_identifier = tag_service_identifier, file_service_identifier = file_service_identifier )
|
||||
|
||||
except: pass
|
||||
|
||||
|
||||
unknown_account = CC.GetUnknownAccount()
|
||||
|
||||
unknown_account.MakeStale()
|
||||
|
@ -6524,7 +6681,7 @@ class DB( ServiceDB ):
|
|||
|
||||
except: print( traceback.format_exc() )
|
||||
|
||||
if i % 10: time.sleep( 2 )
|
||||
if i % 10 == 0: time.sleep( 2 )
|
||||
else:
|
||||
|
||||
if limit > 10000: time.sleep( 0.05 )
|
||||
|
@ -6887,7 +7044,9 @@ class DB( ServiceDB ):
|
|||
|
||||
subscriptions = wx.GetApp().ReadDaemon( 'subscriptions' )
|
||||
|
||||
for ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) in subscriptions:
|
||||
for ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) in subscriptions:
|
||||
|
||||
if paused: continue
|
||||
|
||||
if site_download_type == HC.SITE_DOWNLOAD_TYPE_BOORU: ( booru_name, query_type ) = query_type
|
||||
|
||||
|
@ -7032,56 +7191,66 @@ class DB( ServiceDB ):
|
|||
|
||||
if HC.shutdown: return
|
||||
|
||||
url = url_args[0]
|
||||
|
||||
url_cache.add( url )
|
||||
|
||||
x_out_of_y = HC.ConvertIntToPrettyString( i ) + '/' + HC.ConvertIntToPrettyString( len( all_url_args ) )
|
||||
|
||||
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : checking url status' )
|
||||
|
||||
( status, hash ) = wx.GetApp().ReadDaemon( 'url_status', url )
|
||||
|
||||
if status == 'deleted' and 'exclude_deleted_files' not in advanced_import_options: status = 'new'
|
||||
|
||||
if status == 'redundant':
|
||||
try:
|
||||
|
||||
if do_tags:
|
||||
url = url_args[0]
|
||||
|
||||
url_cache.add( url )
|
||||
|
||||
x_out_of_y = HC.ConvertIntToPrettyString( i ) + '/' + HC.ConvertIntToPrettyString( len( all_url_args ) )
|
||||
|
||||
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : checking url status' )
|
||||
|
||||
( status, hash ) = wx.GetApp().ReadDaemon( 'url_status', url )
|
||||
|
||||
if status == 'deleted' and 'exclude_deleted_files' not in advanced_import_options: status = 'new'
|
||||
|
||||
if status == 'redundant':
|
||||
|
||||
try:
|
||||
if do_tags:
|
||||
|
||||
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : found file in db, fetching tags' )
|
||||
try:
|
||||
|
||||
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : found file in db, fetching tags' )
|
||||
|
||||
tags = downloader.GetTags( *url_args )
|
||||
|
||||
service_identifiers_to_tags = HydrusDownloading.ConvertTagsToServiceIdentifiersToTags( tags, advanced_tag_options )
|
||||
|
||||
content_updates = HydrusDownloading.ConvertServiceIdentifiersToTagsToContentUpdates( hash, service_identifiers_to_tags )
|
||||
|
||||
wx.GetApp().Write( 'content_updates', content_updates )
|
||||
|
||||
except: pass
|
||||
|
||||
tags = downloader.GetTags( *url_args )
|
||||
|
||||
elif status == 'new':
|
||||
|
||||
num_new += 1
|
||||
|
||||
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : downloading file' )
|
||||
|
||||
if do_tags: ( file, tags ) = downloader.GetFileAndTags( *url_args )
|
||||
else:
|
||||
|
||||
service_identifiers_to_tags = HydrusDownloading.ConvertTagsToServiceIdentifiersToTags( tags, advanced_tag_options )
|
||||
file = downloader.GetFile( *url_args )
|
||||
|
||||
content_updates = HydrusDownloading.ConvertServiceIdentifiersToTagsToContentUpdates( hash, service_identifiers_to_tags )
|
||||
tags = []
|
||||
|
||||
wx.GetApp().Write( 'content_updates', content_updates )
|
||||
|
||||
except: pass
|
||||
|
||||
service_identifiers_to_tags = HydrusDownloading.ConvertTagsToServiceIdentifiersToTags( tags, advanced_tag_options )
|
||||
|
||||
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : importing file' )
|
||||
|
||||
wx.GetApp().Write( 'import_file', file, advanced_import_options = advanced_import_options, service_identifiers_to_tags = service_identifiers_to_tags, url = url )
|
||||
|
||||
|
||||
elif status == 'new':
|
||||
except Exception as e:
|
||||
|
||||
num_new += 1
|
||||
print( 'while trying to execute a subscription, the url ' + url + ' caused this problem:' )
|
||||
print( unicode( e ) )
|
||||
|
||||
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : downloading file' )
|
||||
|
||||
if do_tags: ( file, tags ) = downloader.GetFileAndTags( *url_args )
|
||||
else:
|
||||
|
||||
file = downloader.GetFile( *url_args )
|
||||
|
||||
tags = []
|
||||
|
||||
|
||||
service_identifiers_to_tags = HydrusDownloading.ConvertTagsToServiceIdentifiersToTags( tags, advanced_tag_options )
|
||||
|
||||
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : importing file' )
|
||||
|
||||
wx.GetApp().Write( 'import_file', file, advanced_import_options = advanced_import_options, service_identifiers_to_tags = service_identifiers_to_tags, url = url )
|
||||
HC.pubsub.pub( 'log_error', 'synchronise subscriptions daemon', 'problem with ' + name + ' ' + unicode( e ) )
|
||||
|
||||
|
||||
i += 1
|
||||
|
@ -7090,7 +7259,7 @@ class DB( ServiceDB ):
|
|||
|
||||
if site_download_type == HC.SITE_DOWNLOAD_TYPE_BOORU: query_type = ( booru_name, query_type )
|
||||
|
||||
subscription = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
|
||||
subscription = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
|
||||
|
||||
wx.GetApp().Write( 'subscription', subscription )
|
||||
|
||||
|
@ -7122,7 +7291,7 @@ class DB( ServiceDB ):
|
|||
|
||||
if site_download_type == HC.SITE_DOWNLOAD_TYPE_BOORU: query_type = ( booru_name, query_type )
|
||||
|
||||
subscription = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
|
||||
subscription = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
|
||||
|
||||
wx.GetApp().Write( 'subscription', subscription )
|
||||
|
||||
|
@ -7271,6 +7440,7 @@ class DB( ServiceDB ):
|
|||
elif action == 'status_num_inbox': result = self._DoStatusNumInbox( c, *args, **kwargs )
|
||||
elif action == 'subscriptions': result = self._GetSubscriptions( c, *args, **kwargs )
|
||||
elif action == 'tag_service_precedence': result = self._tag_service_precedence
|
||||
elif action == 'tag_siblings': result = self._GetTagSiblings( c, *args, **kwargs )
|
||||
elif action == 'thumbnail': result = self._GetThumbnail( *args, **kwargs )
|
||||
elif action == 'thumbnail_hashes_i_should_have': result = self._GetThumbnailHashesIShouldHave( c, *args, **kwargs )
|
||||
elif action == 'transport_message': result = self._GetTransportMessage( c, *args, **kwargs )
|
||||
|
@ -7319,6 +7489,7 @@ class DB( ServiceDB ):
|
|||
elif action == 'set_tag_service_precedence': self._SetTagServicePrecedence( c, *args, **kwargs )
|
||||
elif action == 'subscription': self._SetSubscription( c, *args, **kwargs )
|
||||
elif action == 'subscriptions': self._SetSubscriptions( c, *args, **kwargs )
|
||||
elif action == 'tag_siblings': self._UpdateTagSiblings( c, *args, **kwargs )
|
||||
elif action == 'thumbnails': self._AddThumbnails( c, *args, **kwargs )
|
||||
elif action == 'update': self._AddUpdate( c, *args, **kwargs )
|
||||
elif action == 'update_boorus': self._UpdateBoorus( c, *args, **kwargs )
|
||||
|
|
|
@ -674,6 +674,15 @@ class FrameGUI( ClientGUICommon.Frame ):
|
|||
except Exception as e: wx.MessageBox( unicode( e ) + traceback.format_exc() )
|
||||
|
||||
|
||||
def _ManageTagSiblings( self ):
|
||||
|
||||
try:
|
||||
|
||||
with ClientGUIDialogs.DialogManageTagSiblings( self ) as dlg: dlg.ShowModal()
|
||||
|
||||
except Exception as e: wx.MessageBox( unicode( e ) + traceback.format_exc() )
|
||||
|
||||
|
||||
def _ModifyAccount( self, service_identifier ):
|
||||
|
||||
service = wx.GetApp().Read( 'service', service_identifier )
|
||||
|
@ -1058,6 +1067,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
elif command == 'manage_services': self._ManageServices()
|
||||
elif command == 'manage_subscriptions': self._ManageSubscriptions()
|
||||
elif command == 'manage_tag_service_precedence': self._ManageTagServicePrecedence()
|
||||
elif command == 'manage_tag_siblings': self._ManageTagSiblings()
|
||||
elif command == 'modify_account': self._ModifyAccount( data )
|
||||
elif command == 'new_accounts': self._NewAccounts( data )
|
||||
elif command == 'new_import_booru': self._NewPageImportBooru()
|
||||
|
@ -1328,7 +1338,10 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
services.AppendSeparator()
|
||||
services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'review_services' ), p( '&Review Services' ), p( 'Review your services.' ) )
|
||||
services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_services' ), p( '&Add, Remove or Edit Services' ), p( 'Edit your services.' ) )
|
||||
if len( download_tag_service_identifiers ) > 1: services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_tag_service_precedence' ), p( '&Manage Tag Service Precedence' ), p( 'Change the order in which tag repositories\' taxonomies will be added to the database.' ) )
|
||||
services.AppendSeparator()
|
||||
services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_tag_siblings' ), p( '&Manage Tag Siblings' ), p( 'Set certain tags to be automatically replaced with other tags.' ) )
|
||||
#services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_tag_service_precedence' ), p( '&Manage Tag Service Precedence' ), p( 'Change the order in which tag repositories\' taxonomies will be added to the database.' ) )
|
||||
services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_tag_service_precedence' ), p( '&Manage Tag Service Precedence' ), p( 'Change the order in which tag repositories\' taxonomies will be added to the database.' ) )
|
||||
services.AppendSeparator()
|
||||
services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_boorus' ), p( 'Manage &Boorus' ), p( 'Change the html parsing information for boorus to download from.' ) )
|
||||
services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_imageboards' ), p( 'Manage &Imageboards' ), p( 'Change the html POST form information for imageboards to dump to.' ) )
|
||||
|
@ -1646,7 +1659,16 @@ class FramePageChooser( ClientGUICommon.Frame ):
|
|||
entries = [ ( 'page_query', HC.LOCAL_FILE_SERVICE_IDENTIFIER ) ] + file_repos
|
||||
|
||||
elif menu_keyword == 'download': entries = [ ( 'page_import_url', None ), ( 'page_import_thread_watcher', None ), ( 'menu', 'gallery' ) ]
|
||||
elif menu_keyword == 'gallery': entries = [ ( 'page_import_booru', None ), ( 'page_import_gallery', 'giphy' ), ( 'page_import_gallery', 'deviant art by artist' ), ( 'menu', 'hentai foundry' ), ( 'menu', 'pixiv' ), ( 'page_import_gallery', 'tumblr' ) ]
|
||||
elif menu_keyword == 'gallery':
|
||||
|
||||
entries = [ ( 'page_import_booru', None ), ( 'page_import_gallery', 'giphy' ), ( 'page_import_gallery', 'deviant art by artist' ), ( 'menu', 'hentai foundry' ) ]
|
||||
|
||||
( id, password ) = wx.GetApp().Read( 'pixiv_account' )
|
||||
|
||||
if id != '' and password != '': entries.append( ( 'menu', 'pixiv' ) )
|
||||
|
||||
entries.extend( [ ( 'page_import_gallery', 'tumblr' ) ] )
|
||||
|
||||
elif menu_keyword == 'hentai foundry': entries = [ ( 'page_import_gallery', 'hentai foundry by artist' ), ( 'page_import_gallery', 'hentai foundry by tags' ) ]
|
||||
elif menu_keyword == 'pixiv': entries = [ ( 'page_import_gallery', 'pixiv by artist' ), ( 'page_import_gallery', 'pixiv by tag' ) ]
|
||||
elif menu_keyword == 'messages': entries = [ ( 'page_messages', identity ) for identity in self._identities ]
|
||||
|
|
|
@ -316,6 +316,11 @@ class Canvas():
|
|||
|
||||
( media_width, media_height ) = self._current_display_media.GetResolution()
|
||||
|
||||
if self._current_display_media.GetMime() in ( HC.APPLICATION_FLASH, HC.VIDEO_FLV ):
|
||||
|
||||
my_width -= 1
|
||||
|
||||
|
||||
if media_width > my_width or media_height > my_height:
|
||||
|
||||
width_zoom = my_width / float( media_width )
|
||||
|
|
|
@ -119,8 +119,6 @@ class AutoCompleteDropdown( wx.TextCtrl ):
|
|||
|
||||
tlp.Bind( wx.EVT_MOVE, self.EventMove )
|
||||
|
||||
self._initialised = False
|
||||
|
||||
|
||||
def _BroadcastChoice( self, predicate ): pass
|
||||
|
||||
|
@ -242,12 +240,7 @@ class AutoCompleteDropdown( wx.TextCtrl ):
|
|||
|
||||
def EventSetFocus( self, event ):
|
||||
|
||||
if not self._initialised:
|
||||
|
||||
self._UpdateList()
|
||||
|
||||
self._initialised = True
|
||||
|
||||
self._UpdateList()
|
||||
|
||||
self._ShowDropdownIfFocussed()
|
||||
|
||||
|
@ -423,7 +416,7 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
|
|||
|
||||
menu = wx.Menu()
|
||||
|
||||
if len( service_identifiers ) > 0: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_file_repository', HC.NULL_SERVICE_IDENTIFIER ), 'all known files' )
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_file_repository', HC.NULL_SERVICE_IDENTIFIER ), 'all known files' )
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_file_repository', HC.LOCAL_FILE_SERVICE_IDENTIFIER ), 'local files' )
|
||||
|
||||
for service_identifier in service_identifiers: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_file_repository', service_identifier ), service_identifier.GetName() )
|
||||
|
@ -495,7 +488,7 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
|
|||
|
||||
menu = wx.Menu()
|
||||
|
||||
if len( service_identifiers ) > 0: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_tag_repository', HC.NULL_SERVICE_IDENTIFIER ), 'all known tags' )
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_tag_repository', HC.NULL_SERVICE_IDENTIFIER ), 'all known tags' )
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_tag_repository', HC.LOCAL_TAG_SERVICE_IDENTIFIER ), 'local tags' )
|
||||
|
||||
for service_identifier in service_identifiers: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_tag_repository', service_identifier ), service_identifier.GetName() )
|
||||
|
@ -769,24 +762,39 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
|
|||
|
||||
self._first_letters = half_complete_tag
|
||||
|
||||
self._cached_results = wx.GetApp().Read( 'autocomplete_tags', file_service_identifier = self._file_service_identifier, tag_service_identifier = self._tag_service_identifier, half_complete_tag = search_text )
|
||||
self._cached_results = wx.GetApp().Read( 'autocomplete_tags', file_service_identifier = self._file_service_identifier, tag_service_identifier = self._tag_service_identifier, half_complete_tag = search_text, do_siblings_collapse = False )
|
||||
|
||||
|
||||
matches = self._cached_results.GetMatches( half_complete_tag )
|
||||
|
||||
else: matches = []
|
||||
|
||||
predicate = HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', search_text ), 0 )
|
||||
# now do the 'put whatever they typed in at the top, whether it has count or not'
|
||||
# now with sibling support!
|
||||
|
||||
try:
|
||||
|
||||
predicate = matches[ matches.index( predicate ) ]
|
||||
|
||||
matches.remove( predicate )
|
||||
|
||||
except: pass
|
||||
top_predicates = []
|
||||
|
||||
matches.insert( 0, predicate )
|
||||
top_predicates.append( HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', search_text ), 0 ) )
|
||||
|
||||
siblings_manager = wx.GetApp().GetTagSiblingsManager()
|
||||
|
||||
sibling = siblings_manager.GetSibling( search_text )
|
||||
|
||||
if sibling is not None: top_predicates.append( HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', sibling ), 0 ) )
|
||||
|
||||
for predicate in top_predicates:
|
||||
|
||||
try:
|
||||
|
||||
predicate = matches[ matches.index( predicate ) ]
|
||||
|
||||
matches.remove( predicate )
|
||||
|
||||
except: pass
|
||||
|
||||
|
||||
matches.insert( 0, predicate )
|
||||
|
||||
|
||||
|
||||
return matches
|
||||
|
@ -1436,7 +1444,7 @@ class ListBox( wx.ScrolledWindow ):
|
|||
|
||||
def __len__( self ): return len( self._ordered_strings )
|
||||
|
||||
def _Activate( self, tag ): pass
|
||||
def _Activate( self, s, term ): pass
|
||||
|
||||
def _DrawTexts( self ):
|
||||
|
||||
|
@ -1564,7 +1572,14 @@ class ListBox( wx.ScrolledWindow ):
|
|||
|
||||
index = self._GetIndexUnderMouse( event )
|
||||
|
||||
if index is not None and index == self._current_selected_index: self._Activate( self._strings_to_terms[ self._ordered_strings[ self._current_selected_index ] ] )
|
||||
if index is not None and index == self._current_selected_index:
|
||||
|
||||
s = self._ordered_strings[ self._current_selected_index ]
|
||||
|
||||
term = self._strings_to_terms[ s ]
|
||||
|
||||
self._Activate( s, term )
|
||||
|
||||
|
||||
|
||||
def EventKeyDown( self, event ):
|
||||
|
@ -1573,7 +1588,14 @@ class ListBox( wx.ScrolledWindow ):
|
|||
|
||||
if self._current_selected_index is not None:
|
||||
|
||||
if key_code in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ): self._Activate( self._strings_to_terms[ self._ordered_strings[ self._current_selected_index ] ] )
|
||||
if key_code in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
||||
|
||||
s = self._ordered_strings[ self._current_selected_index ]
|
||||
|
||||
term = self._strings_to_terms[ s ]
|
||||
|
||||
self._Activate( s, term )
|
||||
|
||||
elif key_code in ( wx.WXK_UP, wx.WXK_NUMPAD_UP ): self._Select( self._current_selected_index - 1 )
|
||||
elif key_code in ( wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN ): self._Select( self._current_selected_index + 1 )
|
||||
elif key_code == wx.WXK_PAGEUP: self._Select( self._current_selected_index - self._num_rows_per_page )
|
||||
|
@ -1705,7 +1727,7 @@ class ListBoxMessagesActiveOnly( ListBoxMessages ):
|
|||
self._matches = {}
|
||||
|
||||
|
||||
def _Activate( self, tag ): self._callable( tag )
|
||||
def _Activate( self, s, term ): self._callable( term )
|
||||
|
||||
def SetTerms( self, matches ):
|
||||
|
||||
|
@ -1751,7 +1773,7 @@ class ListBoxMessagesPredicates( ListBoxMessages ):
|
|||
|
||||
|
||||
|
||||
def _Activate( self, term ): HC.pubsub.pub( 'remove_predicate', self._page_key, term )
|
||||
def _Activate( self, s, term ): HC.pubsub.pub( 'remove_predicate', self._page_key, term )
|
||||
|
||||
def ActivatePredicate( self, term ):
|
||||
|
||||
|
@ -2146,7 +2168,7 @@ class SaneListCtrl( wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin ):
|
|||
|
||||
data_indicies = [ self.GetItemData( index ) for index in range( self.GetItemCount() ) ]
|
||||
|
||||
datas = [ self.itemDataMap[ data_index ] for data_index in data_indicies ]
|
||||
datas = [ tuple( self.itemDataMap[ data_index ] ) for data_index in data_indicies ]
|
||||
|
||||
return datas
|
||||
|
||||
|
@ -2154,7 +2176,7 @@ class SaneListCtrl( wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin ):
|
|||
|
||||
data_index = self.GetItemData( index )
|
||||
|
||||
return self.itemDataMap[ data_index ]
|
||||
return tuple( self.itemDataMap[ data_index ] )
|
||||
|
||||
|
||||
|
||||
|
@ -2790,11 +2812,38 @@ class TagsBoxActiveOnly( TagsBox ):
|
|||
self._predicates = {}
|
||||
|
||||
|
||||
def _Activate( self, predicate ): self._callable( predicate )
|
||||
def _Activate( self, s, term ): self._callable( term )
|
||||
|
||||
def SetPredicates( self, predicates ):
|
||||
|
||||
if predicates != self._predicates:
|
||||
# need to do a clever compare, since normal predicate compare doesn't take count into account
|
||||
|
||||
they_are_the_same = True
|
||||
|
||||
if len( predicates ) == len( self._predicates ):
|
||||
|
||||
p_list_1 = list( predicates )
|
||||
p_list_2 = list( self._predicates )
|
||||
|
||||
p_list_1.sort()
|
||||
p_list_2.sort()
|
||||
|
||||
for index in range( len( p_list_1 ) ):
|
||||
|
||||
p_1 = p_list_1[ index ]
|
||||
p_2 = p_list_2[ index ]
|
||||
|
||||
if p_1 != p_2 or p_1.GetCount() != p_2.GetCount():
|
||||
|
||||
they_are_the_same = False
|
||||
|
||||
break
|
||||
|
||||
|
||||
|
||||
else: they_are_the_same = False
|
||||
|
||||
if not they_are_the_same:
|
||||
|
||||
self._predicates = predicates
|
||||
|
||||
|
@ -2836,9 +2885,9 @@ class TagsBoxCPP( TagsBox ):
|
|||
HC.pubsub.sub( self, 'ChangeTagRepository', 'change_tag_repository' )
|
||||
|
||||
|
||||
def _Activate( self, tag ):
|
||||
def _Activate( self, s, term ):
|
||||
|
||||
predicate = HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', tag ), None )
|
||||
predicate = HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', term ), None )
|
||||
|
||||
HC.pubsub.pub( 'add_predicate', self._page_key, predicate )
|
||||
|
||||
|
@ -2882,6 +2931,10 @@ class TagsBoxCPP( TagsBox ):
|
|||
|
||||
def SetTags( self, current_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ):
|
||||
|
||||
siblings_manager = wx.GetApp().GetTagSiblingsManager()
|
||||
|
||||
current_tags_to_count = siblings_manager.CollapseTagsToCount( current_tags_to_count )
|
||||
|
||||
if current_tags_to_count != self._current_tags_to_count or pending_tags_to_count != self._pending_tags_to_count or petitioned_tags_to_count != self._petitioned_tags_to_count:
|
||||
|
||||
self._current_tags_to_count = current_tags_to_count
|
||||
|
@ -2901,6 +2954,10 @@ class TagsBoxCPP( TagsBox ):
|
|||
if tag in self._pending_tags_to_count: tag_string += ' (+' + HC.ConvertIntToPrettyString( self._pending_tags_to_count[ tag ] ) + ')'
|
||||
if tag in self._petitioned_tags_to_count: tag_string += ' (-' + HC.ConvertIntToPrettyString( self._petitioned_tags_to_count[ tag ] ) + ')'
|
||||
|
||||
sibling = siblings_manager.GetSibling( tag )
|
||||
|
||||
if sibling is not None: tag_string += ' (' + sibling + ')'
|
||||
|
||||
self._ordered_strings.append( tag_string )
|
||||
self._strings_to_terms[ tag_string ] = tag
|
||||
|
||||
|
@ -2968,38 +3025,57 @@ class TagsBoxFlat( TagsBox ):
|
|||
TagsBox.__init__( self, parent )
|
||||
|
||||
self._removed_callable = removed_callable
|
||||
self._tags = []
|
||||
|
||||
|
||||
def _RecalcTags( self ):
|
||||
|
||||
self._ordered_strings = self._strings_to_terms.values()
|
||||
self._strings_to_terms = {}
|
||||
|
||||
siblings_manager = wx.GetApp().GetTagSiblingsManager()
|
||||
|
||||
for tag in self._tags:
|
||||
|
||||
tag_string = tag
|
||||
|
||||
sibling = siblings_manager.GetSibling( tag )
|
||||
|
||||
if sibling is not None: tag_string += ' (' + sibling + ')'
|
||||
|
||||
self._strings_to_terms[ tag_string ] = tag
|
||||
|
||||
|
||||
self._ordered_strings = self._strings_to_terms.keys()
|
||||
|
||||
self._ordered_strings.sort()
|
||||
|
||||
self._TextsHaveChanged()
|
||||
|
||||
|
||||
def _Activate( self, tag ):
|
||||
def _Activate( self, s, term ):
|
||||
|
||||
del self._strings_to_terms[ tag ]
|
||||
|
||||
self._RecalcTags()
|
||||
|
||||
self._removed_callable( tag )
|
||||
if term in self._tags:
|
||||
|
||||
self._tags.remove( term )
|
||||
|
||||
self._RecalcTags()
|
||||
|
||||
self._removed_callable( tag )
|
||||
|
||||
|
||||
|
||||
def AddTag( self, tag ):
|
||||
|
||||
self._strings_to_terms[ tag ] = tag
|
||||
self._tags.append( tag )
|
||||
|
||||
self._RecalcTags()
|
||||
|
||||
|
||||
def GetTags( self ): return self._strings_to_terms.values()
|
||||
def GetTags( self ): return self._tags
|
||||
|
||||
def SetTags( self, tags ):
|
||||
|
||||
self._strings_to_terms = { t : t for t in tags }
|
||||
self._tags = tags
|
||||
|
||||
self._RecalcTags()
|
||||
|
||||
|
@ -3022,10 +3098,12 @@ class TagsBoxManage( TagsBox ):
|
|||
self._RebuildTagStrings()
|
||||
|
||||
|
||||
def _Activate( self, tag ): self._callable( tag )
|
||||
def _Activate( self, s, term ): self._callable( term )
|
||||
|
||||
def _RebuildTagStrings( self ):
|
||||
|
||||
siblings_manager = wx.GetApp().GetTagSiblingsManager()
|
||||
|
||||
if self._show_deleted_tags: all_tags = self._current_tags | self._deleted_tags | self._pending_tags | self._petitioned_tags
|
||||
else: all_tags = self._current_tags | self._pending_tags | self._petitioned_tags
|
||||
|
||||
|
@ -3039,6 +3117,10 @@ class TagsBoxManage( TagsBox ):
|
|||
elif tag in self._pending_tags: tag_string = '(+) ' + tag
|
||||
else: tag_string = '(X) ' + tag
|
||||
|
||||
sibling = siblings_manager.GetSibling( tag )
|
||||
|
||||
if sibling is not None: tag_string += ' (' + sibling + ')'
|
||||
|
||||
self._ordered_strings.append( tag_string )
|
||||
self._strings_to_terms[ tag_string ] = tag
|
||||
|
||||
|
@ -3104,7 +3186,7 @@ class TagsBoxOptions( TagsBox ):
|
|||
self._TextsHaveChanged()
|
||||
|
||||
|
||||
def _Activate( self, tag ): self.RemoveNamespace( tag )
|
||||
def _Activate( self, s, term ): self.RemoveNamespace( term )
|
||||
|
||||
def _GetNamespaceColours( self ): return self._namespace_colours
|
||||
|
||||
|
@ -3183,7 +3265,7 @@ class TagsBoxPredicates( TagsBox ):
|
|||
|
||||
|
||||
|
||||
def _Activate( self, predicate ): HC.pubsub.pub( 'remove_predicate', self._page_key, predicate )
|
||||
def _Activate( self, s, term ): HC.pubsub.pub( 'remove_predicate', self._page_key, term )
|
||||
|
||||
def AddPredicate( self, predicate ):
|
||||
|
||||
|
|
|
@ -5425,7 +5425,7 @@ class DialogManageRatings( Dialog ):
|
|||
|
||||
if len( content_updates ) > 0: wx.GetApp().Write( 'content_updates', content_updates )
|
||||
|
||||
except Exception as e: wx.MessageBox( 'Saving pending mapping changes to DB raised this error: ' + unicode( e ) )
|
||||
except Exception as e: wx.MessageBox( 'Saving ratings changes to DB raised this error: ' + unicode( e ) )
|
||||
|
||||
self.EndModal( wx.ID_OK )
|
||||
|
||||
|
@ -6627,11 +6627,11 @@ class DialogManageSubscriptions( Dialog ):
|
|||
types_to_listbooks[ HC.SITE_DOWNLOAD_TYPE_BOORU ] = self._booru
|
||||
types_to_listbooks[ HC.SITE_DOWNLOAD_TYPE_TUMBLR ] = self._tumblr
|
||||
|
||||
for ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) in self._original_subscriptions:
|
||||
for ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) in self._original_subscriptions:
|
||||
|
||||
listbook = types_to_listbooks[ site_download_type ]
|
||||
|
||||
page = self._Panel( listbook, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
|
||||
page = self._Panel( listbook, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
|
||||
|
||||
listbook.AddPage( page, name )
|
||||
|
||||
|
@ -6746,6 +6746,18 @@ class DialogManageSubscriptions( Dialog ):
|
|||
elif subscription_listbook == self._booru: site_download_type = HC.SITE_DOWNLOAD_TYPE_BOORU
|
||||
elif subscription_listbook == self._tumblr: site_download_type = HC.SITE_DOWNLOAD_TYPE_TUMBLR
|
||||
|
||||
if site_download_type == HC.SITE_DOWNLOAD_TYPE_PIXIV:
|
||||
|
||||
( id, password ) = wx.GetApp().Read( 'pixiv_account' )
|
||||
|
||||
if id == '' and password == '':
|
||||
|
||||
wx.MessageBox( 'You need to set up your pixiv credentials before you can add a pixiv subscription!' )
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
if site_download_type in ( HC.SITE_DOWNLOAD_TYPE_DEVIANT_ART, HC.SITE_DOWNLOAD_TYPE_TUMBLR ): query_type = 'artist'
|
||||
else: query_type = 'tags'
|
||||
|
||||
|
@ -6762,7 +6774,9 @@ class DialogManageSubscriptions( Dialog ):
|
|||
last_checked = None
|
||||
url_cache = set()
|
||||
|
||||
page = self._Panel( subscription_listbook, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
|
||||
paused = False
|
||||
|
||||
page = self._Panel( subscription_listbook, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
|
||||
|
||||
subscription_listbook.AddPage( page, name, select = True )
|
||||
|
||||
|
@ -6947,7 +6961,7 @@ class DialogManageSubscriptions( Dialog ):
|
|||
|
||||
class _Panel( wx.ScrolledWindow ):
|
||||
|
||||
def __init__( self, parent, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ):
|
||||
def __init__( self, parent, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ):
|
||||
|
||||
wx.ScrolledWindow.__init__( self, parent )
|
||||
|
||||
|
@ -6957,7 +6971,7 @@ class DialogManageSubscriptions( Dialog ):
|
|||
|
||||
self.SetMinSize( ( 540, 620 ) )
|
||||
|
||||
self._original_info = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
|
||||
self._original_info = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
|
||||
|
||||
# init controls
|
||||
|
||||
|
@ -6991,6 +7005,8 @@ class DialogManageSubscriptions( Dialog ):
|
|||
|
||||
self._info_panel = ClientGUICommon.StaticBox( self, 'info' )
|
||||
|
||||
self._paused = wx.CheckBox( self._info_panel, label = 'paused' )
|
||||
|
||||
self._reset_cache_button = wx.Button( self._info_panel, label = ' reset cache on dialog ok ' )
|
||||
self._reset_cache_button.Bind( wx.EVT_BUTTON, self.EventResetCache )
|
||||
|
||||
|
@ -7035,6 +7051,7 @@ class DialogManageSubscriptions( Dialog ):
|
|||
|
||||
self._info_panel.AddF( wx.StaticText( self._info_panel, label = last_checked_message ), FLAGS_EXPAND_PERPENDICULAR )
|
||||
self._info_panel.AddF( wx.StaticText( self._info_panel, label = str( len( url_cache ) ) + ' urls in cache' ), FLAGS_EXPAND_PERPENDICULAR )
|
||||
self._info_panel.AddF( self._paused, FLAGS_LONE_BUTTON )
|
||||
self._info_panel.AddF( self._reset_cache_button, FLAGS_LONE_BUTTON )
|
||||
|
||||
vbox = wx.BoxSizer( wx.VERTICAL )
|
||||
|
@ -7048,7 +7065,7 @@ class DialogManageSubscriptions( Dialog ):
|
|||
self.SetSizer( vbox )
|
||||
|
||||
|
||||
def _SetControls( self, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ):
|
||||
def _SetControls( self, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ):
|
||||
|
||||
self._name.SetValue( name )
|
||||
|
||||
|
@ -7090,6 +7107,8 @@ class DialogManageSubscriptions( Dialog ):
|
|||
|
||||
if index_to_select is not None: self._frequency_type.Select( index_to_select )
|
||||
|
||||
self._paused.SetValue( paused )
|
||||
|
||||
self._reset_cache_button.SetLabel( ' reset cache on dialog ok ' )
|
||||
|
||||
self._advanced_tag_options.SetInfo( advanced_tag_options )
|
||||
|
@ -7107,7 +7126,7 @@ class DialogManageSubscriptions( Dialog ):
|
|||
|
||||
def GetInfo( self ):
|
||||
|
||||
( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) = self._original_info
|
||||
( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) = self._original_info
|
||||
|
||||
name = self._name.GetValue()
|
||||
|
||||
|
@ -7135,22 +7154,360 @@ class DialogManageSubscriptions( Dialog ):
|
|||
url_cache = set()
|
||||
|
||||
|
||||
return ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
|
||||
paused = self._paused.GetValue()
|
||||
|
||||
return ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
|
||||
|
||||
|
||||
def GetName( self ): return self._name.GetValue()
|
||||
|
||||
def Update( self, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ):
|
||||
def Update( self, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ):
|
||||
|
||||
site_download_type = self._original_info[0]
|
||||
name = self._original_info[1]
|
||||
|
||||
self._original_info = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
|
||||
self._original_info = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
|
||||
|
||||
self._SetControls( *self._original_info )
|
||||
|
||||
|
||||
|
||||
|
||||
class DialogManageTagSiblings( Dialog ):
|
||||
|
||||
def __init__( self, parent ):
|
||||
|
||||
def InitialiseControls():
|
||||
|
||||
self._tag_repositories = ClientGUICommon.ListBook( self )
|
||||
self._tag_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
|
||||
|
||||
#services = wx.GetApp().Read( 'services', ( HC.TAG_REPOSITORY, ) )
|
||||
|
||||
#for service in services:
|
||||
#
|
||||
# account = service.GetAccount()
|
||||
#
|
||||
# if account.HasPermission( HC.POST_DATA ):
|
||||
#
|
||||
# service_identifier = service.GetServiceIdentifier()
|
||||
#
|
||||
# page_info = ( self._Panel, ( self._tag_repositories, service_identifier, paths ), {} )
|
||||
#
|
||||
# name = service_identifier.GetName()
|
||||
#
|
||||
# self._tag_repositories.AddPage( page_info, name )
|
||||
#
|
||||
#
|
||||
|
||||
page = self._Panel( self._tag_repositories, HC.LOCAL_TAG_SERVICE_IDENTIFIER )
|
||||
|
||||
name = HC.LOCAL_TAG_SERVICE_IDENTIFIER.GetName()
|
||||
|
||||
self._tag_repositories.AddPage( page, name )
|
||||
|
||||
#default_tag_repository = self._options[ 'default_tag_repository' ]
|
||||
|
||||
#self._tag_repositories.Select( default_tag_repository.GetName() )
|
||||
|
||||
self._ok_button = wx.Button( self, label='ok' )
|
||||
self._ok_button.Bind( wx.EVT_BUTTON, self.EventOK )
|
||||
self._ok_button.SetForegroundColour( ( 0, 128, 0 ) )
|
||||
|
||||
self._close_button = wx.Button( self, id = wx.ID_CANCEL, label='cancel' )
|
||||
self._close_button.Bind( wx.EVT_BUTTON, self.EventCancel )
|
||||
self._close_button.SetForegroundColour( ( 128, 0, 0 ) )
|
||||
|
||||
|
||||
def InitialisePanel():
|
||||
|
||||
buttons = wx.BoxSizer( wx.HORIZONTAL )
|
||||
|
||||
buttons.AddF( self._ok_button, FLAGS_SMALL_INDENT )
|
||||
buttons.AddF( self._close_button, FLAGS_SMALL_INDENT )
|
||||
|
||||
vbox = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
vbox.AddF( self._tag_repositories, FLAGS_EXPAND_BOTH_WAYS )
|
||||
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
|
||||
|
||||
self.SetSizer( vbox )
|
||||
|
||||
self.SetInitialSize( ( 450, 680 ) )
|
||||
|
||||
|
||||
Dialog.__init__( self, parent, 'tag siblings' )
|
||||
|
||||
InitialiseControls()
|
||||
|
||||
InitialisePanel()
|
||||
|
||||
interested_actions = [ 'set_search_focus' ]
|
||||
|
||||
entries = []
|
||||
|
||||
for ( modifier, key_dict ) in self._options[ 'shortcuts' ].items(): entries.extend( [ ( modifier, key, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) for ( key, action ) in key_dict.items() if action in interested_actions ] )
|
||||
|
||||
self.SetAcceleratorTable( wx.AcceleratorTable( entries ) )
|
||||
|
||||
self.Bind( wx.EVT_MENU, self.EventMenu )
|
||||
|
||||
|
||||
def _SetSearchFocus( self ):
|
||||
|
||||
page = self._tag_repositories.GetCurrentPage()
|
||||
|
||||
page.SetTagBoxFocus()
|
||||
|
||||
|
||||
def EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
|
||||
|
||||
def EventMenu( self, event ):
|
||||
|
||||
action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
|
||||
|
||||
if action is not None:
|
||||
|
||||
try:
|
||||
|
||||
( command, data ) = action
|
||||
|
||||
if command == 'set_search_focus': self._SetSearchFocus()
|
||||
else: event.Skip()
|
||||
|
||||
except Exception as e:
|
||||
|
||||
wx.MessageBox( unicode( e ) )
|
||||
wx.MessageBox( traceback.format_exc() )
|
||||
|
||||
|
||||
|
||||
|
||||
def EventOK( self, event ):
|
||||
|
||||
edit_log = []
|
||||
|
||||
try:
|
||||
|
||||
for page in self._tag_repositories.GetNameToPageDict().values(): edit_log.append( page.GetChanges() )
|
||||
|
||||
if len( edit_log ) > 0: wx.GetApp().Write( 'tag_siblings', edit_log )
|
||||
|
||||
except Exception as e: wx.MessageBox( 'Saving tag sibling changes to DB raised this error: ' + unicode( e ) )
|
||||
|
||||
self.EndModal( wx.ID_OK )
|
||||
|
||||
|
||||
def EventServiceChanged( self, event ):
|
||||
|
||||
page = self._tag_repositories.GetCurrentPage()
|
||||
|
||||
wx.CallAfter( page.SetTagBoxFocus )
|
||||
|
||||
|
||||
class _Panel( wx.Panel ):
|
||||
|
||||
def __init__( self, parent, service_identifier ):
|
||||
|
||||
def InitialiseControls():
|
||||
|
||||
self._tag_siblings = ClientGUICommon.SaneListCtrl( self, 250, [ ( 'old', 160 ), ( 'new', -1 ) ] )
|
||||
|
||||
for ( old, new ) in self._original_pairs: self._tag_siblings.Append( ( old, new ), ( old, new ) )
|
||||
|
||||
self._tag_siblings.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected )
|
||||
self._tag_siblings.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected )
|
||||
|
||||
self._old_text = wx.StaticText( self )
|
||||
self._new_text = wx.StaticText( self )
|
||||
|
||||
self._old_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetOld, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
|
||||
self._new_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetNew, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
|
||||
|
||||
self._add = wx.Button( self, label = 'add' )
|
||||
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
|
||||
self._add.Disable()
|
||||
|
||||
self._remove = wx.Button( self, label = 'remove' )
|
||||
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
|
||||
self._remove.Disable()
|
||||
|
||||
|
||||
def InitialisePanel():
|
||||
|
||||
button_box = wx.BoxSizer( wx.HORIZONTAL )
|
||||
|
||||
button_box.AddF( self._add, FLAGS_MIXED )
|
||||
button_box.AddF( self._remove, FLAGS_MIXED )
|
||||
|
||||
text_box = wx.BoxSizer( wx.HORIZONTAL )
|
||||
|
||||
text_box.AddF( self._old_text, FLAGS_EXPAND_BOTH_WAYS )
|
||||
text_box.AddF( self._new_text, FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
input_box = wx.BoxSizer( wx.HORIZONTAL )
|
||||
|
||||
input_box.AddF( self._old_input, FLAGS_EXPAND_BOTH_WAYS )
|
||||
input_box.AddF( self._new_input, FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
vbox = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
vbox.AddF( self._tag_siblings, FLAGS_EXPAND_BOTH_WAYS )
|
||||
vbox.AddF( button_box, FLAGS_BUTTON_SIZERS )
|
||||
vbox.AddF( text_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
vbox.AddF( input_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
|
||||
self.SetSizer( vbox )
|
||||
|
||||
|
||||
wx.Panel.__init__( self, parent )
|
||||
|
||||
self._service_identifier = service_identifier
|
||||
|
||||
self._added = set()
|
||||
self._removed = set()
|
||||
|
||||
self._original_pairs = [ tuple( pair ) for pair in wx.GetApp().Read( 'tag_siblings', service_identifier ) ]
|
||||
|
||||
self._current_pairs = set( self._original_pairs )
|
||||
|
||||
self._current_new = None
|
||||
self._current_old = None
|
||||
|
||||
InitialiseControls()
|
||||
|
||||
InitialisePanel()
|
||||
|
||||
|
||||
def _SetButtonStatus( self ):
|
||||
|
||||
all_selected = self._tag_siblings.GetAllSelected()
|
||||
|
||||
if len( all_selected ) == 0: self._remove.Disable()
|
||||
else: self._remove.Enable()
|
||||
|
||||
if self._current_new is None or self._current_old is None: self._add.Disable()
|
||||
else: self._add.Enable()
|
||||
|
||||
|
||||
def EventAdd( self, event ):
|
||||
|
||||
pair = ( self._current_old, self._current_new )
|
||||
|
||||
if pair in self._current_pairs:
|
||||
|
||||
wx.MessageBox( 'That relationship already exists!' )
|
||||
|
||||
return
|
||||
|
||||
|
||||
self._current_olds = { old for ( old, new ) in self._current_pairs }
|
||||
|
||||
# test for ambiguity
|
||||
|
||||
if self._current_old in self._current_olds:
|
||||
|
||||
wx.MessageBox( 'There already is a relationship set for the tag ' + self._current_new + '.' )
|
||||
|
||||
return
|
||||
|
||||
|
||||
# test for loops
|
||||
|
||||
if self._current_new in self._current_olds:
|
||||
|
||||
d = dict( self._current_pairs )
|
||||
|
||||
next_new = self._current_new
|
||||
|
||||
while next_new in d:
|
||||
|
||||
next_new = d[ next_new ]
|
||||
|
||||
if next_new == self._current_old:
|
||||
|
||||
wx.MessageBox( 'Adding that pair would create a loop!' )
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
# if we got here, we are great to do it
|
||||
|
||||
self._added.add( pair )
|
||||
self._removed.discard( pair )
|
||||
|
||||
self._current_pairs.add( pair )
|
||||
|
||||
self._tag_siblings.Append( pair, pair )
|
||||
|
||||
self.SetOld( None )
|
||||
self.SetNew( None )
|
||||
|
||||
|
||||
def EventItemSelected( self, event ):
|
||||
|
||||
self._SetButtonStatus()
|
||||
|
||||
|
||||
def EventRemove( self, event ):
|
||||
|
||||
all_selected = self._tag_siblings.GetAllSelected()
|
||||
|
||||
for selected in all_selected:
|
||||
|
||||
pair = self._tag_siblings.GetClientData( selected )
|
||||
|
||||
self._removed.add( pair )
|
||||
self._added.discard( pair )
|
||||
|
||||
self._current_pairs.discard( pair )
|
||||
|
||||
|
||||
self._tag_siblings.RemoveAllSelected()
|
||||
|
||||
|
||||
def GetChanges( self ):
|
||||
|
||||
edit_log = []
|
||||
|
||||
if self._service_identifier == HC.LOCAL_TAG_SERVICE_IDENTIFIER:
|
||||
|
||||
edit_log += [ ( HC.ADD, pair ) for pair in self._added ]
|
||||
edit_log += [ ( HC.DELETE, pair ) for pair in self._removed ]
|
||||
|
||||
|
||||
return ( self._service_identifier, edit_log )
|
||||
|
||||
|
||||
def SetNew( self, tag ):
|
||||
|
||||
if tag is not None and tag == self._current_old: self.SetOld( None )
|
||||
|
||||
self._current_new = tag
|
||||
|
||||
if tag is None: self._new_text.SetLabel( '' )
|
||||
else: self._new_text.SetLabel( tag )
|
||||
|
||||
self._SetButtonStatus()
|
||||
|
||||
|
||||
def SetOld( self, tag ):
|
||||
|
||||
if tag is not None and tag == self._current_new: self.SetNew( None )
|
||||
|
||||
self._current_old = tag
|
||||
|
||||
if tag is None: self._old_text.SetLabel( '' )
|
||||
else: self._old_text.SetLabel( tag )
|
||||
|
||||
self._SetButtonStatus()
|
||||
|
||||
|
||||
def SetTagBoxFocus( self ): self._old_input.SetFocus()
|
||||
|
||||
|
||||
class DialogManageTagServicePrecedence( Dialog ):
|
||||
|
||||
def __init__( self, parent ):
|
||||
|
@ -7395,7 +7752,7 @@ class DialogManageTags( Dialog ):
|
|||
|
||||
if len( content_updates ) > 0: wx.GetApp().Write( 'content_updates', content_updates )
|
||||
|
||||
except Exception as e: wx.MessageBox( 'Saving pending mapping changes to DB raised this error: ' + unicode( e ) )
|
||||
except Exception as e: wx.MessageBox( 'Saving mapping changes to DB raised this error: ' + unicode( e ) )
|
||||
|
||||
self.EndModal( wx.ID_OK )
|
||||
|
||||
|
@ -8126,7 +8483,7 @@ class DialogPathsToTagsRegex( Dialog ):
|
|||
for ( path, tags ) in page_of_paths_to_tags.items(): paths_to_tags[ path ][ service_identifier ] = tags
|
||||
|
||||
|
||||
except Exception as e: wx.MessageBox( 'Saving pending mapping changes to DB raised this error: ' + unicode( e ) )
|
||||
except Exception as e: wx.MessageBox( 'Saving regex tags to DB raised this error: ' + unicode( e ) )
|
||||
|
||||
return paths_to_tags
|
||||
|
||||
|
|
|
@ -493,6 +493,8 @@ class MediaCollection( MediaList, Media ):
|
|||
|
||||
def GetDuration( self ): return self._duration
|
||||
|
||||
def GetHash( self ): return self.GetDisplayMedia().GetHash()
|
||||
|
||||
def GetHashes( self, discriminant = None, not_uploaded_to = None ):
|
||||
|
||||
if discriminant is None and not_uploaded_to is None: return self._hashes
|
||||
|
|
|
@ -30,7 +30,7 @@ TEMP_DIR = BASE_DIR + os.path.sep + 'temp'
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 9
|
||||
SOFTWARE_VERSION = 68
|
||||
SOFTWARE_VERSION = 69
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
@ -983,15 +983,24 @@ def SearchEntryMatchesPredicate( search_entry, predicate ):
|
|||
|
||||
def SearchEntryMatchesTag( search_entry, tag ):
|
||||
|
||||
# note that at no point is the namespace checked against the search_entry!
|
||||
sibling_manager = wx.GetApp().GetTagSiblingsManager()
|
||||
|
||||
if ':' in tag:
|
||||
tags = sibling_manager.GetAllSiblings( tag )
|
||||
|
||||
for tag in tags:
|
||||
|
||||
( n, t ) = tag.split( ':', 1 )
|
||||
if ':' in tag:
|
||||
|
||||
( n, t ) = tag.split( ':', 1 )
|
||||
|
||||
comparee = t
|
||||
|
||||
else: comparee = tag
|
||||
|
||||
return t.startswith( search_entry )
|
||||
if comparee.startswith( search_entry ): return True
|
||||
|
||||
else: return tag.startswith( search_entry )
|
||||
|
||||
return False
|
||||
|
||||
def SplayListForDB( xs ): return '(' + ','.join( [ '"' + str( x ) + '"' for x in xs ] ) + ')'
|
||||
|
||||
|
@ -1896,6 +1905,10 @@ class Predicate():
|
|||
|
||||
def __ne__( self, other ): return self.__hash__() != other.__hash__()
|
||||
|
||||
def AddToCount( self, count ): self._count += count
|
||||
|
||||
def GetCopy( self ): return Predicate( self._predicate_type, self._value, self._count )
|
||||
|
||||
def GetCountlessCopy( self ): return Predicate( self._predicate_type, self._value, None )
|
||||
|
||||
def GetCount( self ): return self._count
|
||||
|
@ -2029,6 +2042,8 @@ class Predicate():
|
|||
|
||||
|
||||
|
||||
if self._count is not None: base += u' (' + ConvertIntToPrettyString( self._count ) + u')'
|
||||
|
||||
elif self._predicate_type == PREDICATE_TYPE_TAG:
|
||||
|
||||
( operator, tag ) = self._value
|
||||
|
@ -2038,6 +2053,14 @@ class Predicate():
|
|||
|
||||
base += tag
|
||||
|
||||
if self._count is not None: base += u' (' + ConvertIntToPrettyString( self._count ) + u')'
|
||||
|
||||
siblings_manager = wx.GetApp().GetTagSiblingsManager()
|
||||
|
||||
sibling = siblings_manager.GetSibling( tag )
|
||||
|
||||
if sibling is not None: base += u' (' + sibling + u')'
|
||||
|
||||
elif self._predicate_type == PREDICATE_TYPE_NAMESPACE:
|
||||
|
||||
( operator, namespace ) = self._value
|
||||
|
@ -2048,8 +2071,18 @@ class Predicate():
|
|||
base += namespace + u':*'
|
||||
|
||||
|
||||
if self._count is None: return base
|
||||
else: return base + u' (' + ConvertIntToPrettyString( self._count ) + u')'
|
||||
return base
|
||||
|
||||
|
||||
def GetTag( self ):
|
||||
|
||||
if self._predicate_type == PREDICATE_TYPE_TAG:
|
||||
|
||||
( operator, tag ) = self._value
|
||||
|
||||
return tag
|
||||
|
||||
else: return 'no tag'
|
||||
|
||||
|
||||
def GetValue( self ): return self._value
|
||||
|
|