diff --git a/.github/workflows/release_macos.yml b/.github/workflows/release_macos.yml
index 7f96453e5..ad230be8e 100644
--- a/.github/workflows/release_macos.yml
+++ b/.github/workflows/release_macos.yml
@@ -6,7 +6,7 @@ on:
jobs:
build-macos:
- runs-on: macos-12
+ runs-on: macos-13
steps:
-
name: Checkout
diff --git a/docs/changelog.md b/docs/changelog.md
index f0fbe3529..613054b1f 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -7,6 +7,77 @@ title: Changelog
!!! note
This is the new changelog, only the most recent builds. For all versions, see the [old changelog](old_changelog.html).
+## [Version 602](https://github.com/hydrusnetwork/hydrus/releases/tag/v602)
+
+### media viewer top hover file info line
+
+* added four checkboxes to `options->media viewer` to alter what shows in the media viewer top hover window's file info summary line. you can say whether archived status is mentioned; and if it is, if there should be a timestamp; and you can say whether individual file services should be enumerated; and if so, whether they should have timestamps
+* this same line is reflected in the main gui status bar when you have the new `options->thumbnails` checkbox set on--so if you missed seeing your current local file services on the status bar, it should be doable now, and in a more compact way
+
+### archived times
+
+* there has been a bug for some time where if you import files with 'automatically archive' set in the file import options, an 'archive time' was not being recorded! this is now fixed going forward
+* for the past instances of this happening, I have written a new `database->file maintenance->fix missing file archived times` job to fill these gaps with synthetic values. it can also fill in gaps from before archive times were recorded (v474, in 2022-02)
+* on the v602 update, your client will scan for this. **if you have a large database, the update may take a couple minutes this week**. if you have either problem, it will point you to the new job
+* the job itself looks for both problems and gives you the choice of what to fix. for files imported since 2022-02, it assumes these were 'archive on import' files and inserts an archive time the same as the import time. for files imported before 2022-02, the synthetic values will be 20% between the import time and (any known deletion time or 2022-02), which is imperfect but I think it'll do
+
+### system predicates
+
+* the system tags for `duration`, `framerate`, `frames`, `width`, `height`, `notes`, `urls`, and `words` are now written `system: has/no xxxxx` rather than `system:xxxxx: has/no xxxxx`
+* the `system:file properties` UI panel is now a two-column grid. 'has xxxxx' on the left, 'no xxxxx' on the right
+* `system:has/no duration` added to `system:file properties` (it is also still in `system:duration`, but let's see how it goes having it in both places. I am 50% sold on the idea)
+* the 'paste image!' button in the `system:similar files` edit panel now understands file paths when they are copied from something like Windows Explorer. previously it was only reading bitmaps or raw text, but when you hit ctrl+c on an actual file, it actually gets encoded as a URI, which is slightly different to raw text. should work now!
+* fixed the `system:duration` predicate's string presentation around 0ms, where it would sometimes unhelpfully insert '+/- no duration' and other such weirdness
+* fixed the `system:duration` edit predicate window initialising with sub-second values. previously, the ms amount on the main value or the +/- were being zeroed
+* the various `NumberTest` objects across the program that have a +/- in absolute or percentage terms now test that boundary in an inclusive manner. previously it was doing `x < n < y`; now it does `x <= n <= y`, which satisfies 5 = 5 +/- 0, 6 = 4 +/- 50%, and 0 = 600 +/- 100%
+* the 'has/no' system pred shortcuts now all parse in the system predicate parser. the old format will still parse too. I added unit tests to check this, and more to fill in gaps for 'framerate' and 'num frames'
+
+### rating predicates specifically
+
+* system:rating search predicates are now edited separately, which means that in the edit panel, each rating widget has its own 'ok' button. you edit one at a time with a clearer workflow
+* the radio buttons involved here are now less confusing. no 'do not search' nonsense or 'is = NULL' to search for 'no rating'--you now just say 'has rating', 'no rating', or set the specific rating
+* the inc/dec system pred box now has quick-select choices for 'has count' and 'no count'
+* rating predicates have slightly simpler and nicer natural language labels. 'is like' and 'more than 2/3' rather than '= like' and '> 2/3'. 'count' instead of 'rating' for the inc/dec ratings. inc/dec rating predicates will also now swap in the new 'has count' and 'no count' too, rather than saying "> 0" explicitly
+* the system predicate parser is updated to handle the new 'count' labels (but the old format will still work, so nothing should break™). new unit tests check this
+* these panels now recover better from a predicate that refers to a service that no longer exists. in general, if you try to edit a bad pred (e.g. from an old session) it'll try to just ignore it and give you a popup letting you know
+
+### deleted similar files search
+
+* the similar files system no longer de-lists files from the search tree when they are deleted. phashes being disassociated from deleted files was not intentionally enforced before, but it was legacy policy from an old optimisation when the search tech was far more limited than today; now the file filtering tech is better, and we can handle it CPU wise, and I now intentionally want to keep them. it should be possible to search similar deleted files in future. the similar files search code can get pretty wew mode though, so I wouldn't be surprised if there are some bugs in odd 'all known files' situations
+* I do not think this functionality is _too_ useful, but we might want to build a 'oh we have seen this file before and we deleted it then, so stop this import' auto-tech one day, maybe, if we combine it with multiple/better similar files hashes, so keeping deleted file data in our search trees will be the policy going forward. although I regret these perceptual hash disassociations, the good news is that pixel hashes were never removed, and this will be more useful for this objective
+
+### misc
+
+* renamed 'human-readable embedded metadata' to 'embedded metadata' across the program, mostly just to stop it being so wide. I hate this system predicate, and with the jpeg stuff it is now a catch-all and basically useless as a discriminator. I have plans to replace it with a flexible all-in-one system that will integrate all the 'has icc profile' stuff together and bundle in fine search for 'jpeg:interlaced' or arbitrary EXIF data row search so you can search for what you actually want rather than the blanket 'uh some metadata I guess'
+* the 'move files now' dialog in `database->move media files` has a new 'run for custom time' button so you don't have to do 10/30/60/indefinite. note I also hate this whole system and want to replace it in the middle-term future with a background migration job, so fingers crossed this whole mess will be gone before long
+* when you manually lock the database with the Client API, the bottom-right status bar cell now says 'db locked'. when it is reading or writing, it now also just says 'db reading/writing' rather than 'db read/write locked'
+* the way the database updates the status bar with 'db reading' and stuff is now a little overhead efficient (it only updates the db cell, not the whole status bar)
+* clarified the text in `options->importing`
+* moved the 'default export directory' option from 'files and trash' to the new 'exporting' panel
+* if an SSLCertVerificationError occurs in a connection, the exception now has some extra info explaining the potential causes of the problem (issue #1642)
+
+### env stuff
+
+* I added a DEBUG checkbox to `options->connection` that explicitly sets the `REQUESTS_CA_BUNDLE` environment variable to your `certifi`'s `cacert.pem` path, assuming that path exists and the env variable has not already been set. I'd like to test this a bit and see if it helps or breaks any situations, and maybe force it one day, or add some more options. also, I don't know much about `CURL_CA_BUNDLE`, but if you want me to defer to that rather than `certifi` if it is already set, or you have other thoughts, let me know what you think
+* added `help->debug->data actions->show env`, which simply spams it to a popup and writes it to the log. it also splits up your various PATH vars for readability, but the list is usually giant so this is best actually read in the log rather than the popup atm. a couple other places where the env is spammed to screen also now uses this now format
+
+### Qt enum cleanup
+
+* fixed up more Qt5 Enums to the new Qt6 locations, including those now under `Qt.ItemDataRole`, `Qt.ItemFlag`, `Qt.SortOrder`, `Qt.ContextMenuPolicy`, `Qt.Key`, `Qt.KeyboardModifier`, `Qt.MouseButton`, `Qt.DropAction`, `Qt.AlignmentFlag`, `Qt.LayoutDirection`, `Qt.Orientation`, `Qt.CursorShape`, `Qt.WidgetAttribute`, `Qt.WindowState`, `Qt.WindowType`, `Qt.GlobalColor`, `Qt.FocusReason`, `Qt.FocusPolicy`, `Qt.CheckState`, `Qt.TextElideMode`, `Qt.TextFormat`, `Qt.TextFlag`, `Qt.TextInteractionFlag`, `Qt.ShortcutContext`, `Qt.ScrollBarPolicy`, `Qt.TimerType`, `Qt.ToolButtonsTyle`, `Qt.PenStyle`, and `Qt.BrushStyle`
+* and `QEvent.Type`
+* and `QItemSelectionModel.SelectionFlag`
+
+### other code cleanup
+
+* cleaned up the recent 'make width sort before height in most places' hacks into something nicer
+* deleted the old 'Edit Multiple Predicates' panel py file, which was only handling the ratings stuff. was probably an interesting idea at some point, but it was too convoluted IRL, both for users and me to work with
+* decoupled some rating-to-stars and stars-to-rating rating conversion code out of the rating services object
+
+### new macOS App
+
+* sorry for no macOS App last week; github retired the old 'runner' that builds the App, and I missed the notifications. we are today updating from `macos-12` to `macos-13`, which appears to still work on intel machines
+* `macos-14`, whose migration will presumably come in a few years, will likely require Apple Silicon (ARM), at which point I'll have to tell older mac users to run from source, but that's a problem for the future
+
## [Version 601](https://github.com/hydrusnetwork/hydrus/releases/tag/v601)
### this page is still importing
@@ -456,51 +527,3 @@ title: Changelog
* thanks to a user, the core `Globals` files get some linter magic that lets an IDE do good type checking on the core controller classes without running into circular import issues. this reduced project-wide PyCharm linter warnings from like 4,500 to 2,200 wew
* I pulled the `ServerController` and `TestController` gubbins out of `HydrusGlobals` into their own 'Globals' files in their respective modules to ensure other module-crawlers (e.g. perhaps PyInstaller) do not get confused about what they are importing here, and to generally clean this up a bit
* improved a daemon unit test that would sometimes fail because it was not waiting long enough for the daemon to finish. I cut some other fat and it is now four or five seconds faster too
-
-## [Version 592](https://github.com/hydrusnetwork/hydrus/releases/tag/v592)
-
-### misc
-
-* the 'read' autocomplete dropdown has a new one-click 'clear search' button, just beside the favourites 'star' menu button. the 'empty page' favourite is removed from new users' defaults
-* in an alteration to the recent Autocomplete key processing, Ctrl+c/Ctrl+Insert _will_ now propagate to the results list if you currently have none of the text input selected (i.e. if it would have been a no-op on the text input, we assume you wanted whatever is selected in the list)
-* in the normal thumbnail/viewer menu and _review services_, the 'files' entry is renamed to 'locations'. this continues work in the left hand button of the autocomplete dropdown where you set the 'location', which can be all sorts of complicated things these days, rather than just 'file service key selector'. I don't think I'll rename 'my files' or anything, but I will try to emphasise this 'locations' idea more when I am talking about local file domains etc.. in other places going forward; what I often think of as 'oh yeah the files bit' isn't actually referring to the files themselves, but where they are located, so let's be precise
-* last week's tag pair filtering in _tags->migrate tags_ now has 'if either the left or right of the pair have count', and when you hit 'Go' with any of the new count filter checkboxes hit, the preview summary on the yes/no confirmation dialog talks about it
-* any time a watcher subject is parsed, if the text contains non-decoded html entities (like `>`), they are now auto-converted to normal chars. these strings are often ripped from odd places and are only used for user display, so this just makes that simpler
-* if you are set to remove trashed files from view, this now works when the files are in multpile local file domains, and you choose 'delete from all local file services', and you are looking at 'all my files' or a subset of your local file domains
-* we now log any time (when the client is non-idle) that a database job's work inside the transaction wrapper takes more than 15 seconds to complete
-* fixed an issue caused by the sibling or parents system doing some regen work at an unlucky time
-
-### default downloaders
-
-* thanks to user help, the derpibooru post parser now additionally grabs the raw markdown of a description as a second note. this catches links and images better than the html string parse. if you strictly only want one of these notes, please feel free to dive into _network->downloaders->defailt import options_ for your derpi downloader and try to navigate the 'note import options' hell I designed and let me know how it could be more user friendly
-
-### parsing system
-
-* added a new NESTED formula type. this guy holds two formulae of any type internally, parsing the document with the first and passing those results on to the second. it is designed to solve the problem of 'how do I parse this JSON tucked inside HTML' and _vice versa_. various encoding stuff all seems to be handled, no extra work needed
-* added Nested formula stuff to the 'how to make a downloader' help
-* made all the screenshot in the parsing formula help clickable
-* renamed the COMPOUND formula to ZIPPER formula
-* all the 'String Processor' buttons across the program now have copy and paste buttons, so it is now easy to duplicate some rules you set up
-* in the parsing system, sidecar importer, and clipboard watcher, all strings are now cleansed of errant 'surrogate' characters caused by the source incorrectly providing utf-16 garbage in a utf-8 stream. fingers crossed, the cleansing here will actually _fix_ problem characters by converting them to utf-8, but we'll see
-* thanks to a user, the JSON parsing system has a new 'de-minify json' parsing rule, which decompresses a particular sort of minified JSON that expresses multiply-referenced values using list positions. as it happened that I added NESTED formulae this week, I wonder if we will migrate this capability to the string processing system, but let's give it time to breathe
-
-### client api
-
-* fixed the permission check on the new 'get file/thumbnail local path' commands--due to me copy/pasting stupidly, they were still just checking 'search files' perm
-* added `/get_files/local_file_storage_locations`, which spits out the stuff in _database->move media files_ and lets you do local file access _en masse_
-* added help and a unit test for this new command
-* the client api version is now 72
-
-### some security/library updates
-
-* the 'old' OpenCV version in the `(a)dvanced` setup, which pointed to version 4.5.3.56, which had the webp vulnerability, is no longer an option. I believe this means that the program will no longer run on python 3.7. I understad Win 7 can run python 3.8 at the latest, so we are nearing the end of the line on that front
-* the old/new Pillow choice in `(a)dvanced` setup, which offered support for python 3.7, is removed
-* I have added a new question to the `(a)dvanced` venv setup to handle misc 'future' tests better, and I added a new future test for two security patches for `setuptools` and `requests`:
-* A) `setuptools` is updated to 70.3.0 (from 69.1.1) to resolve a security issue related to downloading packages from bad places (don't think this would ever affect us, but we'll be good)
-* B) `requests` is updated to 2.32.3 (from 2.31.0) to resolve a security issue with verify=False (the specific problem doesn't matter for us, but we'll be good)
-* if you run from source and want to help me test, you might like to rebuild your venv this week and choose the new future choice. these version increments do not appear to be a big deal, so assuming no problems I will roll these new libraries into a 'future' test build next week, and then into the normal builds a week after
-
-### boring code cleanup
-
-* did a bunch more `super()` refactoring. I think all `__init__` is now converted across the program, and I cleared all the normal calls in the canvas and media results panel code too
-* refactored `ClientGUIResults` into four files for the core class, the loading, the thumbnails, and some menu gubbins. also unified the mish-mash of `Results` and `MediaPanel` nomenclature to `MediaResultsPanel`
diff --git a/docs/developer_api.md b/docs/developer_api.md
index 9ec6059fb..4917f973c 100644
--- a/docs/developer_api.md
+++ b/docs/developer_api.md
@@ -1559,8 +1559,8 @@ Wildcards and namespace searches are supported, so if you search for 'character:
* system:no audio
* system:has exif
* system:no exif
- * system:has human-readable embedded metadata
- * system:no human-readable embedded metadata
+ * system:has embedded metadata
+ * system:no embedded metadata
* system:has icc profile
* system:no icc profile
* system:has tags
diff --git a/docs/old_changelog.html b/docs/old_changelog.html
index 3cac07035..c2fecab2b 100644
--- a/docs/old_changelog.html
+++ b/docs/old_changelog.html
@@ -34,11 +34,65 @@
+ -
+
+
+ media viewer top hover file info line
+ - added four checkboxes to `options->media viewer` to alter what shows in the media viewer top hover window's file info summary line. you can say whether archived status is mentioned; and if it is, if there should be a timestamp; and you can say whether individual file services should be enumerated; and if so, whether they should have timestamps
+ - this same line is reflected in the main gui status bar when you have the new `options->thumbnails` checkbox set on--so if you missed seeing your current local file services on the status bar, it should be doable now, and in a more compact way
+ archived times
+ - there has been a bug for some time where if you import files with 'automatically archive' set in the file import options, an 'archive time' was not being recorded! this is now fixed going forward
+ - for the past instances of this happening, I have written a new `database->file maintenance->fix missing file archived times` job to fill these gaps with synthetic values. it can also fill in gaps from before archive times were recorded (v474, in 2022-02)
+ - on the v602 update, your client will scan for this. **if you have a large database, the update may take a couple minutes this week**. if you have either problem, it will point you to the new job
+ - the job itself looks for both problems and gives you the choice of what to fix. for files imported since 2022-02, it assumes these were 'archive on import' files and inserts an archive time the same as the import time. for files imported before 2022-02, the synthetic values will be 20% between the import time and (any known deletion time or 2022-02), which is imperfect but I think it'll do
+ system predicates
+ - the system tags for `duration`, `framerate`, `frames`, `width`, `height`, `notes`, `urls`, and `words` are now written `system: has/no xxxxx` rather than `system:xxxxx: has/no xxxxx`
+ - the `system:file properties` UI panel is now a two-column grid. 'has xxxxx' on the left, 'no xxxxx' on the right
+ - `system:has/no duration` added to `system:file properties` (it is also still in `system:duration`, but let's see how it goes having it in both places. I am 50% sold on the idea)
+ - the 'paste image!' button in the `system:similar files` edit panel now understands file paths when they are copied from something like Windows Explorer. previously it was only reading bitmaps or raw text, but when you hit ctrl+c on an actual file, it actually gets encoded as a URI, which is slightly different to raw text. should work now!
+ - fixed the `system:duration` predicate's string presentation around 0ms, where it would sometimes unhelpfully insert '+/- no duration' and other such weirdness
+ - fixed the `system:duration` edit predicate window initialising with sub-second values. previously, the ms amount on the main value or the +/- were being zeroed
+ - the various `NumberTest` objects across the program that have a +/- in absolute or percentage terms now test that boundary in an inclusive manner. previously it was doing `x < n < y`; now it does `x <= n <= y`, which satisfies 5 = 5 +/- 0, 6 = 4 +/- 50%, and 0 = 600 +/- 100%
+ - the 'has/no' system pred shortcuts now all parse in the system predicate parser. the old format will still parse too. I added unit tests to check this, and more to fill in gaps for 'framerate' and 'num frames'
+ rating predicates specifically
+ - system:rating search predicates are now edited separately, which means that in the edit panel, each rating widget has its own 'ok' button. you edit one at a time with a clearer workflow
+ - the radio buttons involved here are now less confusing. no 'do not search' nonsense or 'is = NULL' to search for 'no rating'--you now just say 'has rating', 'no rating', or set the specific rating
+ - the inc/dec system pred box now has quick-select choices for 'has count' and 'no count'
+ - rating predicates have slightly simpler and nicer natural language labels. 'is like' and 'more than 2/3' rather than '= like' and '> 2/3'. 'count' instead of 'rating' for the inc/dec ratings. inc/dec rating predicates will also now swap in the new 'has count' and 'no count' too, rather than saying "> 0" explicitly
+ - the system predicate parser is updated to handle the new 'count' labels (but the old format will still work, so nothing should break™). new unit tests check this
+ - these panels now recover better from a predicate that refers to a service that no longer exists. in general, if you try to edit a bad pred (e.g. from an old session) it'll try to just ignore it and give you a popup letting you know
+ deleted similar files search
+ - the similar files system no longer de-lists files from the search tree when they are deleted. phashes being disassociated from deleted files was not intentionally enforced before, but it was legacy policy from an old optimisation when the search tech was far more limited than today; now the file filtering tech is better, and we can handle it CPU wise, and I now intentionally want to keep them. it should be possible to search similar deleted files in future. the similar files search code can get pretty wew mode though, so I wouldn't be surprised if there are some bugs in odd 'all known files' situations
+ - I do not think this functionality is _too_ useful, but we might want to build a 'oh we have seen this file before and we deleted it then, so stop this import' auto-tech one day, maybe, if we combine it with multiple/better similar files hashes, so keeping deleted file data in our search trees will be the policy going forward. although I regret these perceptual hash disassociations, the good news is that pixel hashes were never removed, and this will be more useful for this objective
+ misc
+ - renamed 'human-readable embedded metadata' to 'embedded metadata' across the program, mostly just to stop it being so wide. I hate this system predicate, and with the jpeg stuff it is now a catch-all and basically useless as a discriminator. I have plans to replace it with a flexible all-in-one system that will integrate all the 'has icc profile' stuff together and bundle in fine search for 'jpeg:interlaced' or arbitrary EXIF data row search so you can search for what you actually want rather than the blanket 'uh some metadata I guess'
+ - the 'move files now' dialog in `database->move media files` has a new 'run for custom time' button so you don't have to do 10/30/60/indefinite. note I also hate this whole system and want to replace it in the middle-term future with a background migration job, so fingers crossed this whole mess will be gone before long
+ - when you manually lock the database with the Client API, the bottom-right status bar cell now says 'db locked'. when it is reading or writing, it now also just says 'db reading/writing' rather than 'db read/write locked'
+ - the way the database updates the status bar with 'db reading' and stuff is now a little overhead efficient (it only updates the db cell, not the whole status bar)
+ - clarified the text in `options->importing`
+ - moved the 'default export directory' option from 'files and trash' to the new 'exporting' panel
+ - if an SSLCertVerificationError occurs in a connection, the exception now has some extra info explaining the potential causes of the problem (issue #1642)
+ env stuff
+ - I added a DEBUG checkbox to `options->connection` that explicitly sets the `REQUESTS_CA_BUNDLE` environment variable to your `certifi`'s `cacert.pem` path, assuming that path exists and the env variable has not already been set. I'd like to test this a bit and see if it helps or breaks any situations, and maybe force it one day, or add some more options. also, I don't know much about `CURL_CA_BUNDLE`, but if you want me to defer to that rather than `certifi` if it is already set, or you have other thoughts, let me know what you think
+ - added `help->debug->data actions->show env`, which simply spams it to a popup and writes it to the log. it also splits up your various PATH vars for readability, but the list is usually giant so this is best actually read in the log rather than the popup atm. a couple other places where the env is spammed to screen also now uses this now format
+ Qt enum cleanup
+ - fixed up more Qt5 Enums to the new Qt6 locations, including those now under `Qt.ItemDataRole`, `Qt.ItemFlag`, `Qt.SortOrder`, `Qt.ContextMenuPolicy`, `Qt.Key`, `Qt.KeyboardModifier`, `Qt.MouseButton`, `Qt.DropAction`, `Qt.AlignmentFlag`, `Qt.LayoutDirection`, `Qt.Orientation`, `Qt.CursorShape`, `Qt.WidgetAttribute`, `Qt.WindowState`, `Qt.WindowType`, `Qt.GlobalColor`, `Qt.FocusReason`, `Qt.FocusPolicy`, `Qt.CheckState`, `Qt.TextElideMode`, `Qt.TextFormat`, `Qt.TextFlag`, `Qt.TextInteractionFlag`, `Qt.ShortcutContext`, `Qt.ScrollBarPolicy`, `Qt.TimerType`, `Qt.ToolButtonsTyle`, `Qt.PenStyle`, and `Qt.BrushStyle`
+ - and `QEvent.Type`
+ - and `QItemSelectionModel.SelectionFlag`
+ other code cleanup
+ - cleaned up the recent 'make width sort before height in most places' hacks into something nicer
+ - deleted the old 'Edit Multiple Predicates' panel py file, which was only handling the ratings stuff. was probably an interesting idea at some point, but it was too convoluted IRL, both for users and me to work with
+ - decoupled some rating-to-stars and stars-to-rating rating conversion code out of the rating services object
+ new macOS App
+ - sorry for no macOS App last week; github retired the old 'runner' that builds the App, and I missed the notifications. we are today updating from `macos-12` to `macos-13`, which appears to still work on intel machines
+ - `macos-14`, whose migration will presumably come in a few years, will likely require Apple Silicon (ARM), at which point I'll have to tell older mac users to run from source, but that's a problem for the future
+
+
-
this page is still importing
- - when you try to close the client or a page of pages and one of the sub-pages protests with a reason like "I am still importing", you now get a yes/no dialog with an extra 'no, but show me the pages' button that will spawn a window listing buttons for every page that protested. clicking a button takes you to that page. this window is a frame, not a dialog, and will not go away on a click. if a page is susequently closed, clicking the button greys it out
+ - when you try to close the client or a page of pages and one of the sub-pages protests with a reason like "I am still importing", you now get a yes/no dialog with an extra 'no, but show me the pages' button that will spawn a window listing buttons for every page that protested. clicking a button takes you to that page. this window is a frame, not a dialog, and will not go away on a click. if a page is subsequently closed, clicking the button greys it out
misc
- the 'archived time' pretty text string is no longer flagged as an 'uninteresting' line, which again, as intended, elevates it to the top hover window and main gui status bar if you have detailed info set to show
- `system:width` is now before `system:height` in the `system:dimensions` flesh-out panel. I also hacked this in the 'edit multiple preds' panel for existing fleshed-out predicates, but it is a whack implementation like I did for the sort stuff last week. as I've discussed with some others, the real answer here is probably a `system:resolution` that combines the two
@@ -73,7 +127,7 @@
- also figured out some nicer typing in my newer command-processing menu generation code, and filled in some places where the Command Processor Mixin was needed
- also fixed some bad test panel stuff in the ancient lookup script panels
Qt when running from source
- - **I no longer support Qt5!** it may run, depending on version, if you set up your own venv, but my `setup_venv` scripts no longer offer it as a choice and I will no longer fix any new non-trivial Qt5 bugs. if I didn't break it this week with all the Enum linting, then at some point I expect I will use a Qt6 technique for which there is no Qt5 equivalent and things will simply stop working
+ - **I no longer support Qt5!** it may run, depending on version, if you set up your own venv, but my `setup_venv` scripts no longer offer it as a choice and I will no longer fix any new non-trivial Qt5 bugs. at some point I expect I will use a Qt6 technique for which there is no Qt5 equivalent and things will simply stop working
- I cleaned up the Qt choice more in the `setup_venv` scripts, reducing it down to the one choice regarding Qt6 options and removing the '(m)iddle' choice in favour of simple old/new/test, and then a new '(q) for PyQt6' that just gets the latest PyQt6, and the '(w)rite your own'
- the 'setup_venv' scripts now tell you that Python 3.13 is probably not going to work. they also say, in prep for when it will, in the '(w)rite your own' Qt version step, that Python 3.13's earliest version is 6.8.0.2. this is actually later than our current 'test' version, which is 6.7.something. I've now set up a 3.13 dev environment and did get the program booting but there seem to be problems with numpy<2.0.0. too, and then with scikit for psd-tools, which seems to have no Windows wheel, so I'll keep working here and update everything once I figure out something that will work out of the box. for now, assume python 3.13 is a no-go unless you know how to use pip. probably best to just wait six months for all the base stuff here to catch up and settle
- I removed some Qt5 gubbins from the 'running from source' document
diff --git a/hydrus/client/ClientConstants.py b/hydrus/client/ClientConstants.py
index 9d3563ad8..2764e99a6 100644
--- a/hydrus/client/ClientConstants.py
+++ b/hydrus/client/ClientConstants.py
@@ -446,14 +446,23 @@
sort_type_string_lookup[ sort_type ] = s
+special_sort_sort_override = {
+ SORT_FILES_BY_WIDTH : 'dimensions 0',
+ SORT_FILES_BY_HEIGHT : 'dimensions 1'
+}
+
def magico_sort_sort( sort_type ):
# we just want to sort by sort_type_string_lookup values tbh, EXCEPT we want to put width above height in the dimensions list
- ms = system_sort_type_submetatype_string_lookup[ sort_type ]
- s = sort_type_basic_string_lookup[ sort_type ]
-
- return ( ms if ms is not None else s, 'width' not in s, s )
+ if sort_type in special_sort_sort_override:
+
+ return special_sort_sort_override[ sort_type ]
+
+ else:
+
+ return sort_type_basic_string_lookup[ sort_type ]
+
SYSTEM_SORT_TYPES_SORT_CONTROL_SORTED = sorted( SYSTEM_SORT_TYPES, key = lambda sst: magico_sort_sort( sst ) )
diff --git a/hydrus/client/ClientController.py b/hydrus/client/ClientController.py
index fb98fd5ab..929c12546 100644
--- a/hydrus/client/ClientController.py
+++ b/hydrus/client/ClientController.py
@@ -1233,6 +1233,13 @@ def InitModel( self ):
self.frame_splash_status.SetSubtext( 'network' )
+ if self.new_options.GetBoolean( 'set_requests_ca_bundle_env' ):
+
+ from hydrus.client import ClientEnvironment
+
+ ClientEnvironment.SetRequestsCABundleEnv()
+
+
if self.new_options.GetBoolean( 'boot_with_network_traffic_paused' ):
CG.client_controller.new_options.SetBoolean( 'pause_all_new_network_traffic', True )
diff --git a/hydrus/client/ClientEnvironment.py b/hydrus/client/ClientEnvironment.py
new file mode 100644
index 000000000..b46390a96
--- /dev/null
+++ b/hydrus/client/ClientEnvironment.py
@@ -0,0 +1,45 @@
+import os
+
+from hydrus.core import HydrusData
+
+def SetRequestsCABundleEnv( pem_path = None ):
+
+ # TODO: we could initialise this with a custom pem in launch args pretty easy if we wanted to!
+ # but tbh the user can already set it in the launch env anyway so maybe whatever
+
+ env_var_name = 'REQUESTS_CA_BUNDLE'
+
+ if env_var_name in os.environ:
+
+ HydrusData.Print( f'Custom REQUESTS_CA_BUNDLE: {os.environ[env_var_name]}')
+
+ return
+
+
+ # could say "If CURL_CA_BUNDLE exists, use that instead of certifi"
+
+ if pem_path is None:
+
+ try:
+
+ import certifi
+
+ pem_path = certifi.where()
+
+ except:
+
+ HydrusData.Print( 'No certifi, so cannot set REQUESTS_CA_BUNDLE.' )
+
+ return
+
+
+
+ if os.path.exists( pem_path ):
+
+ os.environ[ env_var_name ] = pem_path
+
+ else:
+
+ HydrusData.Print( f'The given CA Bundle at "{pem_path}" does not exist, so cannot set REQUESTS_CA_BUNDLE!' )
+
+
diff --git a/hydrus/client/ClientFiles.py b/hydrus/client/ClientFiles.py
index 1f4fd4cbf..6236eccf6 100644
--- a/hydrus/client/ClientFiles.py
+++ b/hydrus/client/ClientFiles.py
@@ -87,7 +87,7 @@
REGENERATE_FILE_DATA_JOB_FILE_MODIFIED_TIMESTAMP : 'regenerate file modified time',
REGENERATE_FILE_DATA_JOB_FILE_HAS_TRANSPARENCY: 'determine if the file has transparency',
REGENERATE_FILE_DATA_JOB_FILE_HAS_EXIF : 'determine if the file has EXIF metadata',
- REGENERATE_FILE_DATA_JOB_FILE_HAS_HUMAN_READABLE_EMBEDDED_METADATA : 'determine if the file has non-EXIF human-readable embedded metadata',
+ REGENERATE_FILE_DATA_JOB_FILE_HAS_HUMAN_READABLE_EMBEDDED_METADATA : 'determine if the file has non-EXIF embedded metadata',
REGENERATE_FILE_DATA_JOB_FILE_HAS_ICC_PROFILE : 'determine if the file has an icc profile',
REGENERATE_FILE_DATA_JOB_PIXEL_HASH : 'regenerate pixel hashes',
REGENERATE_FILE_DATA_JOB_BLURHASH: 'regenerate blurhash'
@@ -155,7 +155,7 @@
REGENERATE_FILE_DATA_JOB_FILE_MODIFIED_TIMESTAMP : '''This rechecks the file's modified timestamp and saves it to the database.''',
REGENERATE_FILE_DATA_JOB_FILE_HAS_TRANSPARENCY : '''This loads the file to see if it has an alpha channel with useful data (completely opaque/transparency alpha channels are discarded). Only works for images and animated gif.''',
REGENERATE_FILE_DATA_JOB_FILE_HAS_EXIF : '''This loads the file to see if it has EXIF metadata, which can be shown in the media viewer and searched with "system:image has exif".''',
- REGENERATE_FILE_DATA_JOB_FILE_HAS_HUMAN_READABLE_EMBEDDED_METADATA : '''This loads the file to see if it has non-EXIF human-readable metadata, which can be shown in the media viewer and searched with "system:image has human-readable embedded metadata".''',
+ REGENERATE_FILE_DATA_JOB_FILE_HAS_HUMAN_READABLE_EMBEDDED_METADATA : '''This loads the file to see if it has non-EXIF human-readable embedded metadata, which can be shown in the media viewer and searched with "system:image has human-readable embedded metadata".''',
REGENERATE_FILE_DATA_JOB_FILE_HAS_ICC_PROFILE : '''This loads the file to see if it has an ICC profile, which is used in "system:has icc profile" search.''',
REGENERATE_FILE_DATA_JOB_PIXEL_HASH : '''This generates a fast unique identifier for the pixels in a still image, which is used in duplicate pixel searches.''',
REGENERATE_FILE_DATA_JOB_BLURHASH : '''This generates a very small version of the file's thumbnail that can be used as a placeholder while the thumbnail loads.'''
diff --git a/hydrus/client/ClientOptions.py b/hydrus/client/ClientOptions.py
index 4b00503f7..d3970136e 100644
--- a/hydrus/client/ClientOptions.py
+++ b/hydrus/client/ClientOptions.py
@@ -271,7 +271,12 @@ def _InitialiseDefaults( self ):
'use_listbook_for_tag_service_panels' : False,
'open_files_to_duplicate_filter_uses_all_my_files' : True,
'show_extended_single_file_info_in_status_bar' : False,
- 'hide_duplicates_needs_work_message_when_reasonably_caught_up' : True
+ 'hide_duplicates_needs_work_message_when_reasonably_caught_up' : True,
+ 'file_info_line_consider_archived_interesting' : True,
+ 'file_info_line_consider_archived_time_interesting' : True,
+ 'file_info_line_consider_file_services_interesting' : False,
+ 'file_info_line_consider_file_services_import_times_interesting' : False,
+ 'set_requests_ca_bundle_env' : False
}
#
diff --git a/hydrus/client/ClientRendering.py b/hydrus/client/ClientRendering.py
index 8f1f820da..cc75bbd96 100644
--- a/hydrus/client/ClientRendering.py
+++ b/hydrus/client/ClientRendering.py
@@ -316,7 +316,7 @@ def _InitialiseErrorImage( self, e: Exception ):
painter = QG.QPainter( qt_image )
- painter.setBackground( QG.QBrush( QC.Qt.white ) )
+ painter.setBackground( QG.QBrush( QC.Qt.GlobalColor.white ) )
painter.eraseRect( painter.viewport() )
@@ -325,7 +325,7 @@ def _InitialiseErrorImage( self, e: Exception ):
pen.setWidth( 5 )
painter.setPen( pen )
- painter.setBrush( QC.Qt.NoBrush )
+ painter.setBrush( QC.Qt.BrushStyle.NoBrush )
painter.drawRect( 0, 0, width - 1, height - 1 )
@@ -343,7 +343,7 @@ def _InitialiseErrorImage( self, e: Exception ):
text += '\n'
text += 'Full info written to the log.'
- painter.drawText( QC.QRectF( 0, 0, width, height ), QC.Qt.AlignCenter, text )
+ painter.drawText( QC.QRectF( 0, 0, width, height ), QC.Qt.AlignmentFlag.AlignCenter, text )
del painter
@@ -468,7 +468,7 @@ def GetQtPixmap( self, clip_rect = None, target_resolution = None ):
pixmap = QG.QPixmap( target_resolution )
- pixmap.fill( QC.Qt.black )
+ pixmap.fill( QC.Qt.GlobalColor.black )
return pixmap
diff --git a/hydrus/client/ClientSVGHandling.py b/hydrus/client/ClientSVGHandling.py
index 408c908f1..b38d2ea43 100644
--- a/hydrus/client/ClientSVGHandling.py
+++ b/hydrus/client/ClientSVGHandling.py
@@ -45,7 +45,7 @@ def GenerateThumbnailNumPyFromSVGPath( path: str, target_resolution: typing.Tupl
qt_image = QG.QImage( target_width, target_height, QG.QImage.Format_RGBA8888 )
- qt_image.fill( QC.Qt.transparent )
+ qt_image.fill( QC.Qt.GlobalColor.transparent )
painter = QG.QPainter( qt_image )
diff --git a/hydrus/client/ClientSerialisable.py b/hydrus/client/ClientSerialisable.py
index 1e53cd78a..e9fa671e3 100644
--- a/hydrus/client/ClientSerialisable.py
+++ b/hydrus/client/ClientSerialisable.py
@@ -89,7 +89,7 @@ def CreateTopImage( width, title, payload_description, text ):
painter = QG.QPainter( top_qt_image )
- painter.setBackground( QG.QBrush( QC.Qt.white ) )
+ painter.setBackground( QG.QBrush( QC.Qt.GlobalColor.white ) )
painter.eraseRect( painter.viewport() )
diff --git a/hydrus/client/ClientServices.py b/hydrus/client/ClientServices.py
index 48f40bbb7..258568fc8 100644
--- a/hydrus/client/ClientServices.py
+++ b/hydrus/client/ClientServices.py
@@ -724,45 +724,12 @@ def ConvertRatingStateAndRatingToString( self, rating_state: int, rating: float
def ConvertRatingToStars( self, rating: float ) -> int:
- if self._allow_zero:
-
- stars = int( round( rating * self._num_stars ) )
-
- else:
-
- stars = int( round( rating * ( self._num_stars - 1 ) ) ) + 1
-
-
- return stars
+ return ClientRatings.ConvertRatingToStars( self._num_stars, self._allow_zero, rating )
- def ConvertStarsToRating( self, stars: int ) -> float:
-
- if stars > self._num_stars:
-
- stars = self._num_stars
-
-
- if self._allow_zero:
-
- if stars < 0:
-
- stars = 0
-
-
- rating = stars / self._num_stars
-
- else:
-
- if stars < 1:
-
- stars = 1
-
-
- rating = ( stars - 1 ) / ( self._num_stars - 1 )
-
+ def ConvertStarsToRating( self, stars: int ):
- return rating
+ return ClientRatings.ConvertStarsToRating( self._num_stars, self._allow_zero, stars )
def GetNumStars( self ):
diff --git a/hydrus/client/db/ClientDB.py b/hydrus/client/db/ClientDB.py
index 92e65d4f7..0427c5c9b 100644
--- a/hydrus/client/db/ClientDB.py
+++ b/hydrus/client/db/ClientDB.py
@@ -4997,6 +4997,11 @@ def _ImportFile( self, file_import_job: ClientImportFiles.FileImportJob ):
HydrusData.ShowText( 'File import job archiving new file' )
+ if hash_id not in self.modules_files_inbox.inbox_hash_ids:
+
+ self.modules_files_inbox.InboxFiles( ( hash_id, ) )
+
+
self.modules_files_inbox.ArchiveFiles( ( hash_id, ) )
content_update = ClientContentUpdates.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, ( hash, ) )
@@ -5128,6 +5133,8 @@ def _InitCommandsToMethods( self ):
'ideal_client_files_locations' : self.modules_files_physical_storage.GetIdealClientFilesLocations,
'last_shutdown_work_time' : self.modules_db_maintenance.GetLastShutdownWorkTime,
'media_predicates' : self.modules_tag_display.GetMediaPredicates,
+ 'missing_archive_timestamps_import_test' : self.modules_files_inbox.WeHaveMissingImportArchiveTimestamps,
+ 'missing_archive_timestamps_legacy_test' : self.modules_files_inbox.WeHaveMissingLegacyArchiveTimestamps,
'missing_repository_update_hashes' : self.modules_repositories.GetRepositoryUpdateHashesIDoNotHave,
'num_deferred_file_deletes' : self.modules_files_storage.GetDeferredPhysicalDeleteCounts,
'recent_tags' : self.modules_recent_tags.GetRecentTags,
@@ -5226,6 +5233,8 @@ def _InitCommandsToMethods( self ):
'ideal_client_files_locations' : self.modules_files_physical_storage.SetIdealClientFilesLocations,
'maintain_hashed_serialisables' : self.modules_serialisable.MaintainHashedStorage,
'maintain_similar_files_tree' : self.modules_similar_files.MaintainTree,
+ 'missing_archive_timestamps_import_fillin' : self.modules_files_inbox.FillInMissingImportArchiveTimestamps,
+ 'missing_archive_timestamps_legacy_fillin' : self.modules_files_inbox.FillInMissingLegacyArchiveTimestamps,
'process_repository_definitions' : self.modules_repositories.ProcessRepositoryDefinitions,
'push_recent_tags' : self.modules_recent_tags.PushRecentTags,
'regenerate_similar_files' : self.modules_similar_files.RegenerateTree,
@@ -5359,7 +5368,7 @@ def _LoadModules( self ):
#
- self.modules_files_inbox = ClientDBFilesInbox.ClientDBFilesInbox( self._c, self.modules_files_storage, self.modules_files_timestamps )
+ self.modules_files_inbox = ClientDBFilesInbox.ClientDBFilesInbox( self._c, self.modules_services, self.modules_files_storage, self.modules_files_timestamps )
self._modules.append( self.modules_files_inbox )
@@ -5995,7 +6004,7 @@ def _PerceptualHashesSearchForPotentialDuplicates( self, search_distance, mainte
group_of_hash_ids = self._STL( self._Execute( 'SELECT hash_id FROM shape_search_cache WHERE searched_distance IS NULL or searched_distance < ?;', ( search_distance, ) ).fetchmany( 10 ) )
while len( group_of_hash_ids ) > 0:
-
+
text = 'searching potential duplicates: {}'.format( HydrusNumbers.ToHumanInt( num_done ) )
CG.client_controller.frame_splash_status.SetSubtext( text )
@@ -11392,6 +11401,40 @@ def ask_what_to_do_false_positive_modified_dates():
+ if version == 601:
+
+ try:
+
+ self._controller.frame_splash_status.SetText( f'scanning for missing import archive timestamps' )
+
+ we_have_missing_import_archive_timestamps = self.modules_files_inbox.WeHaveMissingImportArchiveTimestamps()
+
+ if not we_have_missing_import_archive_timestamps:
+
+ self._controller.frame_splash_status.SetText( f'scanning for missing legacy archive timestamps' )
+
+ we_have_missing_legacy_archive_timestamps = self.modules_files_inbox.WeHaveMissingLegacyArchiveTimestamps()
+
+ else:
+
+ we_have_missing_legacy_archive_timestamps = False
+
+
+ if we_have_missing_import_archive_timestamps or we_have_missing_legacy_archive_timestamps:
+
+ self.pub_initial_message( 'Hey, I discovered you have some missing file archived times, which we can now fill in with synthetic values. Hit up the new "database->file maintenance->fix missing file archived times" job to review your options!' )
+
+
+ except Exception as e:
+
+ HydrusData.PrintException( e )
+
+ message = 'Trying to check for archived time gaps failed! Please let hydrus dev know!'
+
+ self.pub_initial_message( message )
+
+
+
self._controller.frame_splash_status.SetTitleText( 'updated db to v{}'.format( HydrusNumbers.ToHumanInt( version + 1 ) ) )
self._Execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
@@ -11890,7 +11933,7 @@ def pub_service_updates_after_commit( self, service_keys_to_service_updates ):
def publish_status_update( self ):
- self._controller.pub( 'set_status_bar_dirty' )
+ self._controller.pub( 'set_status_bar_db_dirty' )
def GetInitialMessages( self ):
diff --git a/hydrus/client/db/ClientDBFilesInbox.py b/hydrus/client/db/ClientDBFilesInbox.py
index dfdb4a8ab..4fdcd7da9 100644
--- a/hydrus/client/db/ClientDBFilesInbox.py
+++ b/hydrus/client/db/ClientDBFilesInbox.py
@@ -2,23 +2,34 @@
import typing
from hydrus.core import HydrusConstants as HC
+from hydrus.core import HydrusData
+from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTime
from hydrus.client import ClientConstants as CC
+from hydrus.client import ClientGlobals as CG
from hydrus.client import ClientLocation
+from hydrus.client import ClientThreading
from hydrus.client.db import ClientDBFilesTimestamps
from hydrus.client.db import ClientDBFilesStorage
from hydrus.client.db import ClientDBModule
+from hydrus.client.db import ClientDBServices
+
+# obviously some user might have updated three months later, but this is the rough v474 release time (2022-02-16)
+TIMESTAMP_MS_WHEN_WE_STARTED_TRACKING_ARCHIVED_TIMES = 1644991200000
class ClientDBFilesInbox( ClientDBModule.ClientDBModule ):
def __init__(
self,
cursor: sqlite3.Cursor,
+ modules_services: ClientDBServices.ClientDBMasterServices,
modules_files_storage: ClientDBFilesStorage.ClientDBFilesStorage,
modules_files_metadata_timestamps: ClientDBFilesTimestamps.ClientDBFilesTimestamps
):
+ self.modules_services = modules_services
self.modules_files_storage = modules_files_storage
self.modules_files_metadata_timestamps = modules_files_metadata_timestamps
@@ -45,6 +56,8 @@ def _GetInitialTableGenerationDict( self ) -> dict:
def _InitCaches( self ):
+ # TODO: see about making this guy a 'property' or whatever and initialising on first request?
+ # this, otherwise, is asking it on every reconnection, which is not ideal
if self._Execute( 'SELECT 1 FROM sqlite_master WHERE name = ?;', ( 'file_inbox', ) ).fetchone() is not None:
self.inbox_hash_ids = self._STS( self._Execute( 'SELECT hash_id FROM file_inbox;' ) )
@@ -120,3 +133,275 @@ def InboxFiles( self, hash_ids: typing.Collection[ int ] ):
+ def FillInMissingImportArchiveTimestamps( self, job_status: typing.Optional[ ClientThreading.JobStatus ] = None ):
+
+ if job_status is not None:
+
+ job_status.SetStatusText( 'missing import archive timestamps' )
+
+
+ import_lambda = lambda timestamp: timestamp > TIMESTAMP_MS_WHEN_WE_STARTED_TRACKING_ARCHIVED_TIMES
+
+ num_fixed = 0
+
+ for ( hash_id, imported_timestamp_ms, deleted_timestamp_ms ) in self._IterateMissingArchiveTimestampData( import_lambda, job_status = job_status ):
+
+ if imported_timestamp_ms is None:
+
+ continue
+
+
+ self.modules_files_metadata_timestamps.SetSimpleTimestampsMS( HC.TIMESTAMP_TYPE_ARCHIVED, [ ( hash_id, imported_timestamp_ms ) ] )
+
+ HydrusData.Print( f'Filling in import archive time for {hash_id}: {imported_timestamp_ms}!' )
+
+ num_fixed += 1
+
+ if job_status is not None:
+
+ job_status.SetStatusText( f'missing import archive timestamps: {HydrusNumbers.ToHumanInt(num_fixed)} fixed' )
+
+
+
+ if num_fixed > 0:
+
+ HydrusData.ShowText( f'{HydrusNumbers.ToHumanInt( num_fixed )} missing import archive times fixed!' )
+
+
+ if job_status is not None:
+
+ job_status.DeleteStatusText()
+
+
+
+ def FillInMissingLegacyArchiveTimestamps( self, job_status: typing.Optional[ ClientThreading.JobStatus ] = None ):
+
+ if job_status is not None:
+
+ job_status.SetStatusText( 'missing legacy archive timestamps' )
+
+
+ legacy_lambda = lambda timestamp: timestamp < TIMESTAMP_MS_WHEN_WE_STARTED_TRACKING_ARCHIVED_TIMES
+
+ num_fixed = 0
+
+ for ( hash_id, imported_timestamp_ms, deleted_timestamp_ms ) in self._IterateMissingArchiveTimestampData( legacy_lambda, job_status = job_status ):
+
+ if imported_timestamp_ms is None:
+
+ continue
+
+
+ if deleted_timestamp_ms is None:
+
+ endpoint_timestamp_ms = TIMESTAMP_MS_WHEN_WE_STARTED_TRACKING_ARCHIVED_TIMES
+
+ else:
+
+ endpoint_timestamp_ms = deleted_timestamp_ms
+
+
+ if imported_timestamp_ms > endpoint_timestamp_ms:
+
+ continue
+
+
+ archive_time_ms = int( imported_timestamp_ms + ( ( endpoint_timestamp_ms - imported_timestamp_ms ) / 5 ) )
+
+ self.modules_files_metadata_timestamps.SetSimpleTimestampsMS( HC.TIMESTAMP_TYPE_ARCHIVED, [ ( hash_id, archive_time_ms ) ] )
+
+ HydrusData.Print( f'Filling in legacy archive time for {hash_id}: {archive_time_ms}!' )
+
+ num_fixed += 1
+
+ if job_status is not None:
+
+ job_status.SetStatusText( f'missing legacy archive timestamps: {HydrusNumbers.ToHumanInt(num_fixed)} fixed' )
+
+
+
+ if num_fixed > 0:
+
+ HydrusData.ShowText( f'{HydrusNumbers.ToHumanInt( num_fixed )} missing legacy archive times fixed!' )
+
+
+ if job_status is not None:
+
+ job_status.DeleteStatusText()
+
+
+
+ def _IterateMissingArchiveTimestampData( self, import_timestamp_lambda = None, job_status: typing.Optional[ ClientThreading.JobStatus ] = None ):
+
+ try:
+
+ # are there any non-inbox local files or any deleted files for which we have an import time (actual or deletion memory) before the magic time for which there is no accompanying archive time?
+
+ # current media PLUS current trash
+ current_hash_ids = set( self.modules_files_storage.GetCurrentHashIdsList( self.modules_services.combined_local_media_service_id ) )
+ current_hash_ids.update( self.modules_files_storage.GetCurrentHashIdsList( self.modules_services.trash_service_id ) )
+
+ current_archived_hash_ids = current_hash_ids.difference( self.inbox_hash_ids )
+
+ num_to_do = len( current_archived_hash_ids )
+
+ BLOCK_SIZE = 4096
+
+ for ( i, batch_of_hash_ids ) in enumerate( HydrusLists.SplitListIntoChunks( current_archived_hash_ids, 4096 ) ):
+
+ num_done = i * BLOCK_SIZE
+
+ message = f'Searching current files: {HydrusNumbers.ValueRangeToPrettyString( num_done, num_to_do )}'
+
+ CG.client_controller.frame_splash_status.SetSubtext( message )
+
+ if job_status is not None:
+
+ job_status.SetStatusText( message, level = 2 )
+ job_status.SetVariable( 'popup_gauge_2', ( num_done, num_to_do ) )
+
+ if job_status.IsCancelled():
+
+ return
+
+
+
+ batch_of_hash_ids_to_current_timestamps_ms = self.modules_files_storage.GetCurrentHashIdsToTimestampsMS( self.modules_services.combined_local_file_service_id, batch_of_hash_ids )
+
+ batch_of_hash_ids_to_current_timestamps_ms = { hash_id : timestamp for ( hash_id, timestamp ) in batch_of_hash_ids_to_current_timestamps_ms.items() if timestamp is not None }
+
+ if import_timestamp_lambda is not None:
+
+ batch_of_hash_ids_to_current_timestamps_ms = { hash_id : timestamp for ( hash_id, timestamp ) in batch_of_hash_ids_to_current_timestamps_ms.items() if import_timestamp_lambda( timestamp ) }
+
+
+ filtered_batch_of_hash_ids = set( batch_of_hash_ids_to_current_timestamps_ms.keys() )
+
+ hash_ids_to_archived_timestamps = self.modules_files_metadata_timestamps.GetHashIdsToArchivedTimestampsMS( filtered_batch_of_hash_ids )
+
+ for ( hash_id, current_timestamp_ms ) in batch_of_hash_ids_to_current_timestamps_ms.items():
+
+ if hash_ids_to_archived_timestamps[ hash_id ] is None:
+
+ yield ( hash_id, current_timestamp_ms, None )
+
+
+
+
+ #
+
+ # deleted from my media EX current trash. these are all archived
+ deleted_hash_ids = set( self.modules_files_storage.GetDeletedHashIdsList( self.modules_services.combined_local_media_service_id ) )
+ deleted_hash_ids.difference_update( self.modules_files_storage.GetCurrentHashIdsList( self.modules_services.trash_service_id ) )
+
+ num_to_do = len( deleted_hash_ids )
+
+ BLOCK_SIZE = 4096
+
+ for ( i, batch_of_hash_ids ) in enumerate( HydrusLists.SplitListIntoChunks( deleted_hash_ids, BLOCK_SIZE ) ):
+
+ num_done = i * BLOCK_SIZE
+
+ message = f'Searching deleted files: {HydrusNumbers.ValueRangeToPrettyString( num_done, num_to_do )}'
+
+ CG.client_controller.frame_splash_status.SetSubtext( message )
+
+ if job_status is not None:
+
+ job_status.SetStatusText( message, level = 2 )
+ job_status.SetVariable( 'popup_gauge_2', ( num_done, num_to_do ) )
+
+ if job_status.IsCancelled():
+
+ return
+
+
+
+ batch_of_hash_ids_to_deleted_timestamps_ms = self.modules_files_storage.GetDeletedHashIdsToTimestampsMS( self.modules_services.combined_local_media_service_id, batch_of_hash_ids )
+
+ batch_of_hash_ids_to_deleted_timestamps_ms = { hash_id : ( deleted_timestamp_ms, original_import_timestamp_ms ) for ( hash_id, ( deleted_timestamp_ms, original_import_timestamp_ms ) ) in batch_of_hash_ids_to_deleted_timestamps_ms.items() if original_import_timestamp_ms is not None }
+
+ if import_timestamp_lambda is not None:
+
+ batch_of_hash_ids_to_deleted_timestamps_ms = { hash_id : ( deleted_timestamp_ms, original_import_timestamp_ms ) for ( hash_id, ( deleted_timestamp_ms, original_import_timestamp_ms ) ) in batch_of_hash_ids_to_deleted_timestamps_ms.items() if import_timestamp_lambda( original_import_timestamp_ms ) }
+
+
+ filtered_batch_of_hash_ids = set( batch_of_hash_ids_to_deleted_timestamps_ms.keys() )
+
+ hash_ids_to_archived_timestamps = self.modules_files_metadata_timestamps.GetHashIdsToArchivedTimestampsMS( filtered_batch_of_hash_ids )
+
+ for ( hash_id, ( deleted_timestamp_ms, original_import_timestamp_ms ) ) in batch_of_hash_ids_to_deleted_timestamps_ms.items():
+
+ if hash_ids_to_archived_timestamps[ hash_id ] is None:
+
+ yield ( hash_id, original_import_timestamp_ms, deleted_timestamp_ms )
+
+
+
+
+ finally:
+
+ CG.client_controller.frame_splash_status.SetSubtext( '' )
+
+ if job_status is not None:
+
+ job_status.DeleteStatusText( level = 2 )
+ job_status.DeleteVariable( 'popup_gauge_2' )
+
+
+
+
+ def WeHaveMissingImportArchiveTimestamps( self, job_status: typing.Optional[ ClientThreading.JobStatus ] = None ) -> bool:
+
+ if job_status is not None:
+
+ job_status.SetStatusText( 'scanning for missing import archive timestamps' )
+
+
+ try:
+
+ import_lambda = lambda timestamp: timestamp > TIMESTAMP_MS_WHEN_WE_STARTED_TRACKING_ARCHIVED_TIMES
+
+ for item in self._IterateMissingArchiveTimestampData( import_lambda, job_status = job_status ):
+
+ return True
+
+
+ return False
+
+ finally:
+
+ if job_status is not None:
+
+ job_status.DeleteStatusText()
+
+
+
+
+ def WeHaveMissingLegacyArchiveTimestamps( self, job_status: typing.Optional[ ClientThreading.JobStatus ] = None ) -> bool:
+
+ if job_status is not None:
+
+ job_status.SetStatusText( 'scanning for missing legacy archive timestamps' )
+
+
+ try:
+
+ legacy_lambda = lambda timestamp: timestamp < TIMESTAMP_MS_WHEN_WE_STARTED_TRACKING_ARCHIVED_TIMES
+
+ for item in self._IterateMissingArchiveTimestampData( legacy_lambda, job_status = job_status ):
+
+ return True
+
+
+ return False
+
+ finally:
+
+ if job_status is not None:
+
+ job_status.DeleteStatusText()
+
+
+
+
diff --git a/hydrus/client/db/ClientDBFilesStorage.py b/hydrus/client/db/ClientDBFilesStorage.py
index 2004f35a2..041bfa7fe 100644
--- a/hydrus/client/db/ClientDBFilesStorage.py
+++ b/hydrus/client/db/ClientDBFilesStorage.py
@@ -862,10 +862,10 @@ def GetCurrentHashIdsToTimestampsMS( self, service_id, hash_ids ):
with self._MakeTemporaryIntegerTable( hash_ids, 'hash_id' ) as temp_hash_ids_table_name:
- rows = dict( self._Execute( 'SELECT hash_id, timestamp_ms FROM {} CROSS JOIN {} USING ( hash_id );'.format( temp_hash_ids_table_name, current_files_table_name ) ) )
+ results_dict = dict( self._Execute( 'SELECT hash_id, timestamp_ms FROM {} CROSS JOIN {} USING ( hash_id );'.format( temp_hash_ids_table_name, current_files_table_name ) ) )
- return rows
+ return results_dict
def GetDeferredPhysicalDelete( self ):
diff --git a/hydrus/client/db/ClientDBFilesTimestamps.py b/hydrus/client/db/ClientDBFilesTimestamps.py
index 5e77cb4ce..eed43c28a 100644
--- a/hydrus/client/db/ClientDBFilesTimestamps.py
+++ b/hydrus/client/db/ClientDBFilesTimestamps.py
@@ -176,6 +176,28 @@ def GetHashIdsInRange( self, timestamp_type: int, timestamp_ranges_ms, job_statu
return set()
+ def GetHashIdsToArchivedTimestampsMS( self, hash_ids: typing.Collection[ int ] ):
+
+ # TODO: generalise this to any timestamp_data stub, but it is a slight pain!
+
+ with self._MakeTemporaryIntegerTable( hash_ids, 'hash_id' ) as temp_table_name:
+
+ ( table_name, column_name ) = GetSimpleTimestampTableNames( HC.TIMESTAMP_TYPE_ARCHIVED )
+
+ result = dict( self._Execute( f'SELECT hash_id, {column_name} FROM {temp_table_name} CROSS JOIN {table_name} USING ( hash_id );' ) )
+
+
+ for hash_id in hash_ids:
+
+ if hash_id not in result:
+
+ result[ hash_id ] = None
+
+
+
+ return result
+
+
def GetHashIdsToHalfInitialisedTimesManagers( self, hash_ids: typing.Collection[ int ], hash_ids_table_name: str ) -> typing.Dict[ int, ClientMediaManagers.TimesManager ]:
# note that this doesn't fetch everything, just the stuff this module handles directly and can fetch efficiently
diff --git a/hydrus/client/db/ClientDBSimilarFiles.py b/hydrus/client/db/ClientDBSimilarFiles.py
index d49de6d8c..47281025b 100644
--- a/hydrus/client/db/ClientDBSimilarFiles.py
+++ b/hydrus/client/db/ClientDBSimilarFiles.py
@@ -624,6 +624,7 @@ def AssociatePerceptualHashes( self, hash_id, perceptual_hashes ):
if self._GetRowCount() > 0:
+ # yes, replace--these files' phashes have just changed, so we want to search again with this new data
self._Execute( 'REPLACE INTO shape_search_cache ( hash_id, searched_distance ) VALUES ( ?, ? );', ( hash_id, None ) )
@@ -797,12 +798,6 @@ def RegenerateTree( self ):
CG.client_controller.pub( 'modal_message', job_status )
- job_status.SetStatusText( 'purging search info of orphans' )
-
- ( current_files_table_name, deleted_files_table_name, pending_files_table_name, petitioned_files_table_name ) = ClientDBFilesStorage.GenerateFilesTableNames( self.modules_services.combined_local_file_service_id )
-
- self._Execute( 'DELETE FROM shape_perceptual_hash_map WHERE hash_id NOT IN ( SELECT hash_id FROM {} );'.format( current_files_table_name ) )
-
job_status.SetStatusText( 'gathering all leaves' )
self._Execute( 'DELETE FROM shape_vptree;' )
@@ -1169,10 +1164,6 @@ def SetPerceptualHashes( self, hash_id, perceptual_hashes ):
def StopSearchingFile( self, hash_id ):
- perceptual_hash_ids = self._STS( self._Execute( 'SELECT phash_id FROM shape_perceptual_hash_map WHERE hash_id = ?;', ( hash_id, ) ) )
-
- self.DisassociatePerceptualHashes( hash_id, perceptual_hash_ids )
-
self._Execute( 'DELETE FROM shape_search_cache WHERE hash_id = ?;', ( hash_id, ) )
diff --git a/hydrus/client/gui/ClientGUI.py b/hydrus/client/gui/ClientGUI.py
index 71b6ac36c..c2e89cc0c 100644
--- a/hydrus/client/gui/ClientGUI.py
+++ b/hydrus/client/gui/ClientGUI.py
@@ -19,6 +19,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusEncryption
+from hydrus.core import HydrusEnvironment
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusMemory
@@ -485,6 +486,7 @@ def __init__( self, controller: "CG.ClientController.Controller" ):
self.setStatusBar( self._statusbar )
self._statusbar_thread_updater = ClientGUIAsync.FastThreadToGUIUpdater( self._statusbar, self.RefreshStatusBar )
+ self._statusbar_db_thread_updater = ClientGUIAsync.FastThreadToGUIUpdater( self._statusbar, self.RefreshStatusBarDB )
self._canvas_frames = [] # Keep references to canvas frames so they won't get garbage collected (canvas frames don't have a parent)
@@ -550,6 +552,7 @@ def __init__( self, controller: "CG.ClientController.Controller" ):
self._controller.sub( self, 'PresentImportedFilesToPage', 'imported_files_to_page' )
self._controller.sub( self, 'SetDBLockedStatus', 'db_locked_status' )
self._controller.sub( self, 'SetStatusBarDirty', 'set_status_bar_dirty' )
+ self._controller.sub( self, 'SetStatusBarDirtyDB', 'set_status_bar_db_dirty' )
self._controller.sub( self, 'TryToOpenManageServicesForAutoAccountCreation', 'open_manage_services_and_try_to_auto_create_account' )
vbox = QP.VBoxLayout()
@@ -578,7 +581,7 @@ def __init__( self, controller: "CG.ClientController.Controller" ):
self._ui_update_windows = set()
self._animation_update_timer = QC.QTimer( self )
- self._animation_update_timer.setTimerType( QC.Qt.PreciseTimer )
+ self._animation_update_timer.setTimerType( QC.Qt.TimerType.PreciseTimer )
self._animation_update_timer.timeout.connect( self.TIMEREventAnimationUpdate )
self._animation_update_windows = set()
@@ -635,8 +638,8 @@ def __init__( self, controller: "CG.ClientController.Controller" ):
)
self._locator_widget.setDefaultStylingEnabled( False )
self._locator_widget.setLocator( self._locator )
- self._locator_widget.setAlignment( QC.Qt.AlignCenter )
- self._locator_widget.setEscapeShortcuts( [ QG.QKeySequence( QC.Qt.Key_Escape ) ] )
+ self._locator_widget.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
+ self._locator_widget.setEscapeShortcuts( [ QG.QKeySequence( QC.Qt.Key.Key_Escape ) ] )
# self._locator_widget.setQueryTimeout( 100 ) # how much to wait before starting a search after user edit. default 0
#
@@ -1964,6 +1967,117 @@ def _FixLogicallyInconsistentMappings( self ):
+ def _FixMissingArchiveTimes( self ):
+
+ def do_it_scan_step( job_status ):
+
+ we_are_missing_legacy = self._controller.Read( 'missing_archive_timestamps_legacy_test', job_status )
+
+ if job_status.IsCancelled():
+
+ return
+
+
+ we_are_missing_import = self._controller.Read( 'missing_archive_timestamps_import_test', job_status )
+
+ if job_status.IsCancelled():
+
+ return
+
+
+ CG.client_controller.CallAfterQtSafe( self, 'missing archive times reporter', qt_present_results, job_status, we_are_missing_legacy, we_are_missing_import )
+
+
+ def qt_present_results( job_status, we_are_missing_legacy, we_are_missing_import ):
+
+ if we_are_missing_legacy or we_are_missing_import:
+
+ message = 'It looks like there are some missing archive times. You have:'
+
+ yes_tuples = []
+
+ if we_are_missing_legacy:
+
+ message += '\n\n--Missing Legacy Times--'
+ message += '\n\nThese are files that were archived before hydrus started tracking archive time (2022-02). If you select to fill these in, hydrus will insert a synthetic time that is import time + 20% of the time to 2022-02 or any file deletion time.'
+
+ yes_tuples.append( ( 'do legacy times', [ 'legacy' ] ) )
+
+
+ if we_are_missing_import:
+
+ message += '\n\n--Missing Import Times--'
+ message += '\n\nThese are most likely files that were imported with "automatically archive", which for some period until 2024-12 were not recording archive times due to a bug. It may include a few other instances of missing archived files (e.g. you manually deleted one). If you select to fill these in, hydrus will insert a synthetic time that is the same as the import time.'
+
+ yes_tuples.append( ( 'do import times', [ 'import' ] ) )
+
+
+ if we_are_missing_legacy and we_are_missing_import:
+
+ yes_tuples.append( ( 'do both', [ 'legacy', 'import' ] ) )
+
+
+ try:
+
+ jobs = ClientGUIDialogsQuick.GetYesYesNo( self, message, yes_tuples = yes_tuples, no_label = 'forget it' )
+
+ except HydrusExceptions.CancelledException:
+
+ job_status.FinishAndDismiss()
+
+ return
+
+
+ self._controller.CallToThread( do_it_fix_step, job_status, jobs )
+
+ else:
+
+ job_status.SetStatusText( 'No missing archive times found!' )
+
+ job_status.Finish()
+
+
+
+ def do_it_fix_step( job_status, jobs ):
+
+ for job in jobs:
+
+ if job == 'legacy':
+
+ self._controller.WriteSynchronous( 'missing_archive_timestamps_legacy_fillin', job_status )
+
+ elif job == 'import':
+
+ self._controller.WriteSynchronous( 'missing_archive_timestamps_import_fillin', job_status )
+
+
+ if job_status.IsCancelled():
+
+ return
+
+
+
+ job_status.SetStatusText( 'Done!' )
+ job_status.Finish()
+
+
+ message = 'There are a couple of ways your client may be missing archive times for your files. This will scan for missing times and then present you with the results and a choice on what to do.'
+ message += '\n' * 2
+ message += 'The scan may take a while. It will have a popup showing its work, but it may lock up your client for a bit while it works.'
+
+ result = ClientGUIDialogsQuick.GetYesNo( self, message, yes_label = 'start the scan', no_label = 'forget it' )
+
+ if result == QW.QDialog.DialogCode.Accepted:
+
+ job_status = ClientThreading.JobStatus( cancellable = True )
+
+ job_status.SetStatusTitle( 'missing archive times work' )
+ CG.client_controller.pub( 'message', job_status )
+
+ self._controller.CallToThread( do_it_scan_step, job_status )
+
+
+
def _FleshOutSessionWithCleanDataIfNeeded( self, notebook: ClientGUIPages.PagesNotebook, name: str, session: ClientGUISession.GUISessionContainer ):
unchanged_page_data_hashes = session.GetUnchangedPageDataHashes()
@@ -3262,6 +3376,10 @@ def _InitialiseMenuInfoDatabase( self ):
ClientGUIMenus.AppendMenuItem( file_maintenance_menu, 'clear orphan files' + HC.UNICODE_ELLIPSIS, 'Clear out surplus files that have found their way into the file structure.', self._ClearOrphanFiles )
+ ClientGUIMenus.AppendSeparator( file_maintenance_menu )
+
+ ClientGUIMenus.AppendMenuItem( file_maintenance_menu, 'fix missing file archived times' + HC.UNICODE_ELLIPSIS, 'Search for and fill-in missing file archive times.', self._FixMissingArchiveTimes )
+
ClientGUIMenus.AppendMenu( menu, file_maintenance_menu, 'file maintenance' )
#
@@ -3577,6 +3695,7 @@ def flip_macos_antiflicker():
ClientGUIMenus.AppendMenuItem( data_actions, 'flush log', 'Command the log to write any buffered contents to hard drive.', HydrusData.DebugPrint, 'Flushing log' )
ClientGUIMenus.AppendMenuItem( data_actions, 'force database commit', 'Command the database to flush all pending changes to disk.', CG.client_controller.ForceDatabaseCommit )
ClientGUIMenus.AppendMenuItem( data_actions, 'review threads', 'Show current threads and what they are doing.', self._ReviewThreads )
+ ClientGUIMenus.AppendMenuItem( data_actions, 'show env', 'Print your current environment variables.', HydrusEnvironment.DumpEnv )
ClientGUIMenus.AppendMenuItem( data_actions, 'show scheduled jobs', 'Print some information about the currently scheduled jobs log.', self._DebugShowScheduledJobs )
ClientGUIMenus.AppendMenuItem( data_actions, 'subscription manager snapshot', 'Have the subscription system show what it is doing.', self._controller.subscriptions_manager.ShowSnapshot )
ClientGUIMenus.AppendSeparator( data_actions )
@@ -5293,6 +5412,16 @@ def _RefreshStatusBar( self ):
busy_tooltip = None
+ self._statusbar.SetStatusText( media_status, 0 )
+ self._statusbar.SetStatusText( idle_status, 2, tooltip = idle_tooltip )
+ self._statusbar.SetStatusText( hydrus_busy_status, 3, tooltip = hydrus_busy_tooltip )
+ self._statusbar.SetStatusText( busy_status, 4, tooltip = busy_tooltip )
+
+ self._RefreshStatusBarDB()
+
+
+ def _RefreshStatusBarDB( self ):
+
( db_status, job_name ) = CG.client_controller.GetDBStatus()
if job_name is not None and job_name != '':
@@ -5304,12 +5433,6 @@ def _RefreshStatusBar( self ):
db_tooltip = None
- self._statusbar.setToolTip( ClientGUIFunctions.WrapToolTip( job_name ) )
-
- self._statusbar.SetStatusText( media_status, 0 )
- self._statusbar.SetStatusText( idle_status, 2, tooltip = idle_tooltip )
- self._statusbar.SetStatusText( hydrus_busy_status, 3, tooltip = hydrus_busy_tooltip )
- self._statusbar.SetStatusText( busy_status, 4, tooltip = busy_tooltip )
self._statusbar.SetStatusText( db_status, 5, tooltip = db_tooltip )
@@ -6358,31 +6481,31 @@ def qt_test_ac():
t += 0.01
- CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
+ CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key.Key_Return )
t += SYS_PRED_REFRESH
- CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
+ CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key.Key_Return )
t += SYS_PRED_REFRESH
- CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Down )
+ CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key.Key_Down )
t += 0.05
- CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
+ CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key.Key_Return )
t += SYS_PRED_REFRESH
- CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Down )
+ CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key.Key_Down )
t += 0.05
- CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
+ CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key.Key_Return )
t += SYS_PRED_REFRESH
- CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
+ CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key.Key_Return )
for i in range( 20 ):
@@ -6390,25 +6513,25 @@ def qt_test_ac():
for j in range( i + 1 ):
- CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Down )
+ CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key.Key_Down )
t += 0.1
- CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
+ CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key.Key_Return )
t += SYS_PRED_REFRESH
- CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, None, QC.Qt.Key_Return )
+ CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, None, QC.Qt.Key.Key_Return )
t += 1.0
- CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Down )
+ CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key.Key_Down )
t += 0.05
- CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
+ CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key.Key_Return )
t += 1.0
@@ -7355,9 +7478,9 @@ def eventFilter( self, watched, event ):
if watched == self:
- if event.type() == QC.QEvent.WindowStateChange:
+ if event.type() == QC.QEvent.Type.WindowStateChange:
- was_minimised = event.oldState() == QC.Qt.WindowMinimized
+ was_minimised = event.oldState() == QC.Qt.WindowState.WindowMinimized
is_minimised = self.isMinimized()
if was_minimised != is_minimised:
@@ -7369,7 +7492,7 @@ def eventFilter( self, watched, event ):
if is_minimised:
- self._was_maximised = event.oldState() == QC.Qt.WindowMaximized
+ self._was_maximised = event.oldState() == QC.Qt.WindowState.WindowMaximized
if not self._currently_minimised_to_system_tray and self._controller.new_options.GetBoolean( 'minimise_client_to_system_tray' ):
@@ -8181,6 +8304,11 @@ def RefreshStatusBar( self ):
self._RefreshStatusBar()
+ def RefreshStatusBarDB( self ):
+
+ self._RefreshStatusBarDB()
+
+
def RegisterAnimationUpdateWindow( self, window ):
self._animation_update_windows.add( window )
@@ -8625,6 +8753,11 @@ def SetStatusBarDirty( self ):
self._statusbar_thread_updater.Update()
+ def SetStatusBarDirtyDB( self ):
+
+ self._statusbar_db_thread_updater.Update()
+
+
def ShowPage( self, page_key ):
page = self._notebook.GetPageFromPageKey( page_key )
diff --git a/hydrus/client/gui/ClientGUICharts.py b/hydrus/client/gui/ClientGUICharts.py
index d2f7ca779..b075ac036 100644
--- a/hydrus/client/gui/ClientGUICharts.py
+++ b/hydrus/client/gui/ClientGUICharts.py
@@ -56,8 +56,8 @@ def __init__( self, parent, monthly_usage ):
chart = QCh.QtCharts.QChart()
chart.addSeries( bar_series )
- chart.addAxis( x_category_axis, QC.Qt.AlignBottom )
- chart.addAxis( y_value_axis, QC.Qt.AlignLeft )
+ chart.addAxis( x_category_axis, QC.Qt.AlignmentFlag.AlignBottom )
+ chart.addAxis( y_value_axis, QC.Qt.AlignmentFlag.AlignLeft )
chart.legend().setVisible( False )
@@ -161,8 +161,8 @@ def __init__( self, parent, file_history: dict, show_deleted: bool ):
self._chart.addSeries( self._deleted_files_series )
- self._chart.addAxis( self._x_datetime_axis, QC.Qt.AlignBottom )
- self._chart.addAxis( self._y_value_axis, QC.Qt.AlignLeft )
+ self._chart.addAxis( self._x_datetime_axis, QC.Qt.AlignmentFlag.AlignBottom )
+ self._chart.addAxis( self._y_value_axis, QC.Qt.AlignmentFlag.AlignLeft )
self._current_files_series.attachAxis( self._x_datetime_axis )
self._current_files_series.attachAxis( self._y_value_axis )
diff --git a/hydrus/client/gui/ClientGUICoreMenuDebug.py b/hydrus/client/gui/ClientGUICoreMenuDebug.py
index b744b136b..cb7218f97 100644
--- a/hydrus/client/gui/ClientGUICoreMenuDebug.py
+++ b/hydrus/client/gui/ClientGUICoreMenuDebug.py
@@ -47,7 +47,7 @@ def ShowMenuDialog( window: QW.QWidget, menu: QW.QMenu ):
twi.setText( 0, action.text() )
- twi.setData( 0, QC.Qt.UserRole, action )
+ twi.setData( 0, QC.Qt.ItemDataRole.UserRole, action )
if isinstance( job_parent, QW.QTreeWidget ):
@@ -79,7 +79,7 @@ def ShowMenuDialog( window: QW.QWidget, menu: QW.QMenu ):
item = selected_items[0]
- action = item.data( 0, QC.Qt.UserRole )
+ action = item.data( 0, QC.Qt.ItemDataRole.UserRole )
if action is not None:
diff --git a/hydrus/client/gui/ClientGUIDialogs.py b/hydrus/client/gui/ClientGUIDialogs.py
index 42b8b0914..40e92b09e 100644
--- a/hydrus/client/gui/ClientGUIDialogs.py
+++ b/hydrus/client/gui/ClientGUIDialogs.py
@@ -22,7 +22,7 @@
class Dialog( QP.Dialog ):
- def __init__( self, parent, title, style = QC.Qt.Dialog, position = 'topleft' ):
+ def __init__( self, parent, title, style = QC.Qt.WindowType.Dialog, position = 'topleft' ):
super().__init__( parent )
@@ -39,7 +39,7 @@ def __init__( self, parent, title, style = QC.Qt.Dialog, position = 'topleft' ):
self.move( pos )
- self.setWindowFlag( QC.Qt.WindowContextHelpButtonHint, on = False )
+ self.setWindowFlag( QC.Qt.WindowType.WindowContextHelpButtonHint, on = False )
self._new_options = CG.client_controller.new_options
@@ -109,7 +109,7 @@ def __init__( self, parent ):
st = ClientGUICommon.BetterStaticText( self, '-or-' )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._setup, CC.FLAGS_EXPAND_PERPENDICULAR )
@@ -537,7 +537,7 @@ def __init__( self, parent, url_tree ):
root_item = QW.QTreeWidgetItem()
root_item.setText( 0, root_name )
- root_item.setCheckState( 0, QC.Qt.Checked )
+ root_item.setCheckState( 0, QC.Qt.CheckState.Checked )
self._tree.addTopLevelItem( root_item )
self._AddDirectory( root_item, children )
@@ -577,7 +577,7 @@ def _AddDirectory( self, root, children ):
item = QW.QTreeWidgetItem()
item.setText( 0, item_name )
item.setCheckState( 0, root.checkState( 0 ) )
- item.setData( 0, QC.Qt.UserRole, data )
+ item.setData( 0, QC.Qt.ItemDataRole.UserRole, data )
root.addChild( item )
else:
@@ -600,9 +600,9 @@ def _GetSelectedChildrenData( self, parent_item ):
child_item = parent_item.child( i )
- if child_item.checkState( 0 ) == QC.Qt.Checked:
+ if child_item.checkState( 0 ) == QC.Qt.CheckState.Checked:
- data = child_item.data( 0, QC.Qt.UserRole )
+ data = child_item.data( 0, QC.Qt.ItemDataRole.UserRole )
if data is not None:
@@ -723,7 +723,7 @@ def __init__( self, parent, message, default = '', placeholder = None, allow_bla
QP.SetInitialSize( self, size_hint )
- self._text.setFocus( QC.Qt.OtherFocusReason )
+ self._text.setFocus( QC.Qt.FocusReason.OtherFocusReason )
def _CheckText( self ):
diff --git a/hydrus/client/gui/ClientGUIDragDrop.py b/hydrus/client/gui/ClientGUIDragDrop.py
index ec8048bbe..c8ab6169c 100644
--- a/hydrus/client/gui/ClientGUIDragDrop.py
+++ b/hydrus/client/gui/ClientGUIDragDrop.py
@@ -111,11 +111,11 @@ def DoFileExportDragDrop( window, page_key, media, alt_down ):
if make_it_a_move_flag:
- flags = QC.Qt.MoveAction
+ flags = QC.Qt.DropAction.MoveAction
else:
- flags = QC.Qt.MoveAction | QC.Qt.CopyAction
+ flags = QC.Qt.DropAction.MoveAction | QC.Qt.DropAction.CopyAction
seen_export_filenames = set()
@@ -167,7 +167,7 @@ def DoFileExportDragDrop( window, page_key, media, alt_down ):
else:
dnd_paths = original_paths
- flags = QC.Qt.CopyAction
+ flags = QC.Qt.DropAction.CopyAction
uri_list = []
@@ -208,7 +208,7 @@ def DoFileExportDragDrop( window, page_key, media, alt_down ):
drop_source.setMimeData( data_object )
- result = drop_source.exec_( flags, QC.Qt.CopyAction )
+ result = drop_source.exec_( flags, QC.Qt.DropAction.CopyAction )
return result
@@ -234,7 +234,7 @@ def eventFilter( self, watched, event ):
try:
- if event.type() == QC.QEvent.Drop:
+ if event.type() == QC.QEvent.Type.Drop:
if self.OnDrop( event.position().toPoint().x(), event.position().toPoint().y() ):
@@ -243,7 +243,7 @@ def eventFilter( self, watched, event ):
event.accept()
- elif event.type() == QC.QEvent.DragEnter:
+ elif event.type() == QC.QEvent.Type.DragEnter:
event.accept()
@@ -279,7 +279,7 @@ def OnData( self, mime_data, result ):
- result = QC.Qt.MoveAction
+ result = QC.Qt.DropAction.MoveAction
# old way of doing it that messed up discord et al
'''
@@ -301,7 +301,7 @@ def OnData( self, mime_data, result ):
QP.CallAfter( self._media_callable, page_key, hashes ) # callafter so we can terminate dnd event now
- result = QC.Qt.MoveAction
+ result = QC.Qt.DropAction.MoveAction
'''
elif urls_dnd or text_dnd:
@@ -373,11 +373,11 @@ def OnData( self, mime_data, result ):
- result = QC.Qt.IgnoreAction
+ result = QC.Qt.DropAction.IgnoreAction
else:
- result = QC.Qt.IgnoreAction
+ result = QC.Qt.DropAction.IgnoreAction
return result
diff --git a/hydrus/client/gui/ClientGUIFunctions.py b/hydrus/client/gui/ClientGUIFunctions.py
index f072c804d..8546f1ba7 100644
--- a/hydrus/client/gui/ClientGUIFunctions.py
+++ b/hydrus/client/gui/ClientGUIFunctions.py
@@ -265,7 +265,7 @@ def GetTextSizeFromPainter( painter: QG.QPainter, text: str ):
try:
- text_size = painter.fontMetrics().size( QC.Qt.TextSingleLine, text )
+ text_size = painter.fontMetrics().size( QC.Qt.TextFlag.TextSingleLine, text )
except ValueError:
@@ -287,7 +287,7 @@ def GetTextSizeFromPainter( painter: QG.QPainter, text: str ):
text = '*****INVALID, UNDISPLAYABLE TAG, RUN DATABASE REPAIR NOW*****'
- text_size = painter.fontMetrics().size( QC.Qt.TextSingleLine, text )
+ text_size = painter.fontMetrics().size( QC.Qt.TextFlag.TextSingleLine, text )
return ( text_size, text )
@@ -407,7 +407,7 @@ def SetBitmapButtonBitmap( button, bitmap ):
def SetFocusLater( win: QW.QWidget ):
- CG.client_controller.CallAfterQtSafe( win, 'set focus to a window', win.setFocus, QC.Qt.OtherFocusReason )
+ CG.client_controller.CallAfterQtSafe( win, 'set focus to a window', win.setFocus, QC.Qt.FocusReason.OtherFocusReason )
def TLWIsActive( window ):
diff --git a/hydrus/client/gui/ClientGUIMenus.py b/hydrus/client/gui/ClientGUIMenus.py
index 1b9b08fa1..8f3d1ab20 100644
--- a/hydrus/client/gui/ClientGUIMenus.py
+++ b/hydrus/client/gui/ClientGUIMenus.py
@@ -199,7 +199,7 @@ def eventFilter( self, watched, event ):
try:
- if event.type() == QC.QEvent.StatusTip:
+ if event.type() == QC.QEvent.Type.StatusTip:
QW.QApplication.instance().sendEvent( CG.client_controller.gui, event )
diff --git a/hydrus/client/gui/ClientGUIOptionsPanels.py b/hydrus/client/gui/ClientGUIOptionsPanels.py
index 87e2d6eee..6e891045c 100644
--- a/hydrus/client/gui/ClientGUIOptionsPanels.py
+++ b/hydrus/client/gui/ClientGUIOptionsPanels.py
@@ -58,9 +58,9 @@ def __init__( self, parent, selectable_mimes ):
general_mime_item = QW.QTreeWidgetItem()
general_mime_item.setText( 0, HC.mime_string_lookup[ general_mime_type ] )
- general_mime_item.setFlags( general_mime_item.flags() | QC.Qt.ItemIsUserCheckable )
- general_mime_item.setCheckState( 0, QC.Qt.Unchecked )
- general_mime_item.setData( 0, QC.Qt.UserRole, general_mime_type )
+ general_mime_item.setFlags( general_mime_item.flags() | QC.Qt.ItemFlag.ItemIsUserCheckable )
+ general_mime_item.setCheckState( 0, QC.Qt.CheckState.Unchecked )
+ general_mime_item.setData( 0, QC.Qt.ItemDataRole.UserRole, general_mime_type )
self._my_tree.addTopLevelItem( general_mime_item )
self._general_mime_types_to_items[ general_mime_type ] = general_mime_item
@@ -69,8 +69,8 @@ def __init__( self, parent, selectable_mimes ):
mime_item = QW.QTreeWidgetItem()
mime_item.setText( 0, HC.mime_string_lookup[ mime ] )
- mime_item.setFlags( mime_item.flags() | QC.Qt.ItemIsUserCheckable )
- mime_item.setData( 0, QC.Qt.UserRole, mime )
+ mime_item.setFlags( mime_item.flags() | QC.Qt.ItemFlag.ItemIsUserCheckable )
+ mime_item.setData( 0, QC.Qt.ItemDataRole.UserRole, mime )
general_mime_item.addChild( mime_item )
self._mimes_to_items[ mime ] = mime_item
@@ -97,7 +97,7 @@ def _GetMimesForGeneralMimeType( self, general_mime_type ):
def GetValue( self ) -> typing.Tuple[ int ]:
- mimes = tuple( [ mime for ( mime, item ) in self._mimes_to_items.items() if item.checkState( 0 ) == QC.Qt.Checked ] )
+ mimes = tuple( [ mime for ( mime, item ) in self._mimes_to_items.items() if item.checkState( 0 ) == QC.Qt.CheckState.Checked ] )
return mimes
@@ -110,11 +110,11 @@ def SetValue( self, checked_mimes: typing.Collection[ int ] ):
if mime in checked_mimes:
- check_state = QC.Qt.Checked
+ check_state = QC.Qt.CheckState.Checked
else:
- check_state = QC.Qt.Unchecked
+ check_state = QC.Qt.CheckState.Unchecked
item.setCheckState( 0, check_state )
diff --git a/hydrus/client/gui/ClientGUIPopupMessages.py b/hydrus/client/gui/ClientGUIPopupMessages.py
index 091fd01b6..aa356aad2 100644
--- a/hydrus/client/gui/ClientGUIPopupMessages.py
+++ b/hydrus/client/gui/ClientGUIPopupMessages.py
@@ -68,7 +68,7 @@ def __init__( self, parent, job_status: ClientThreading.JobStatus ):
vbox = QP.VBoxLayout( vbox_margin )
self._title = ClientGUICommon.BetterStaticText( self )
- self._title.setAlignment( QC.Qt.AlignHCenter | QC.Qt.AlignVCenter )
+ self._title.setAlignment( QC.Qt.AlignmentFlag.AlignHCenter | QC.Qt.AlignmentFlag.AlignVCenter )
font = self._title.font()
font.setBold( True )
@@ -1227,7 +1227,7 @@ def eventFilter( self, watched, event ):
if watched == self.parentWidget():
- if event.type() in ( QC.QEvent.Resize, QC.QEvent.Move, QC.QEvent.WindowStateChange ):
+ if event.type() in ( QC.QEvent.Type.Resize, QC.QEvent.Type.Move, QC.QEvent.Type.WindowStateChange ):
if self._OKToAlterUI():
diff --git a/hydrus/client/gui/ClientGUIRatings.py b/hydrus/client/gui/ClientGUIRatings.py
index 1d86c9dd8..c1b989374 100644
--- a/hydrus/client/gui/ClientGUIRatings.py
+++ b/hydrus/client/gui/ClientGUIRatings.py
@@ -101,7 +101,7 @@ def DrawIncDec( painter: QG.QPainter, x, y, service_key, rating_state, rating ):
text_rect = QC.QRect( QC.QPoint( x + 1, y + 1 ), INCDEC_SIZE - QC.QSize( 4, 4 ) )
- painter.drawText( text_rect, QC.Qt.AlignRight | QC.Qt.AlignVCenter, text )
+ painter.drawText( text_rect, QC.Qt.AlignmentFlag.AlignRight | QC.Qt.AlignmentFlag.AlignVCenter, text )
painter.setFont( original_font )
@@ -331,7 +331,7 @@ def mousePressEvent( self, event ):
button = event.button()
- if button == QC.Qt.LeftButton:
+ if button == QC.Qt.MouseButton.LeftButton:
self._SetRating( self._rating + 1 )
@@ -339,7 +339,7 @@ def mousePressEvent( self, event ):
return
- elif button == QC.Qt.RightButton:
+ elif button == QC.Qt.MouseButton.RightButton:
if self._rating > 0:
@@ -350,7 +350,7 @@ def mousePressEvent( self, event ):
return
- elif button == QC.Qt.MiddleButton:
+ elif button == QC.Qt.MouseButton.MiddleButton:
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui.panels import ClientGUIScrolledPanels
@@ -490,8 +490,6 @@ def _UpdateTooltip( self ):
if self.isEnabled():
- text = CG.client_controller.services_manager.GetName( self._service_key )
-
try:
service = CG.client_controller.services_manager.GetService( self._service_key )
@@ -609,10 +607,18 @@ def __init__( self, parent, service_key ):
self._service_key = service_key
- self._service = CG.client_controller.services_manager.GetService( self._service_key )
-
- self._num_stars = self._service.GetNumStars()
- self._allow_zero = self._service.AllowZero()
+ try:
+
+ service = CG.client_controller.services_manager.GetService( self._service_key )
+
+ self._num_stars = service.GetNumStars()
+ self._allow_zero = service.AllowZero()
+
+ except HydrusExceptions.DataMissing:
+
+ self._num_stars = 5
+ self._allow_zero = False
+
my_width = GetNumericalWidth( self._service_key )
@@ -680,7 +686,7 @@ def _GetRatingStateAndRatingFromClickEvent( self, event ):
- rating = self._service.ConvertStarsToRating( stars )
+ rating = ClientRatings.ConvertStarsToRating( self._num_stars, self._allow_zero, stars )
return ( ClientRatings.SET, rating )
@@ -766,7 +772,7 @@ def GetServiceKey( self ):
def mouseMoveEvent( self, event ):
- if event.buttons() & QC.Qt.LeftButton:
+ if event.buttons() & QC.Qt.MouseButton.LeftButton:
( rating_state, rating ) = self._GetRatingStateAndRatingFromClickEvent( event )
@@ -796,6 +802,7 @@ def setEnabled( self, value: bool ):
self._UpdateTooltip()
+
class RatingNumericalDialog( RatingNumerical ):
def _ClearRating( self ):
diff --git a/hydrus/client/gui/ClientGUIShortcutControls.py b/hydrus/client/gui/ClientGUIShortcutControls.py
index 6edb36b2f..b684c2b56 100644
--- a/hydrus/client/gui/ClientGUIShortcutControls.py
+++ b/hydrus/client/gui/ClientGUIShortcutControls.py
@@ -883,7 +883,7 @@ def __init__( self, parent ):
def _ProcessMouseEvent( self, event ):
- self.setFocus( QC.Qt.OtherFocusReason )
+ self.setFocus( QC.Qt.FocusReason.OtherFocusReason )
shortcut = ClientGUIShortcuts.ConvertMouseEventToShortcut( event )
diff --git a/hydrus/client/gui/ClientGUIShortcuts.py b/hydrus/client/gui/ClientGUIShortcuts.py
index be9215a62..e3c12a067 100644
--- a/hydrus/client/gui/ClientGUIShortcuts.py
+++ b/hydrus/client/gui/ClientGUIShortcuts.py
@@ -79,53 +79,53 @@
if HC.PLATFORM_MACOS:
- DELETE_KEYS_QT = ( QC.Qt.Key_Backspace, QC.Qt.Key_Delete )
+ DELETE_KEYS_QT = ( QC.Qt.Key.Key_Backspace, QC.Qt.Key.Key_Delete )
DELETE_KEYS_HYDRUS = ( SHORTCUT_KEY_SPECIAL_BACKSPACE, SHORTCUT_KEY_SPECIAL_DELETE )
else:
- DELETE_KEYS_QT = ( QC.Qt.Key_Delete, )
+ DELETE_KEYS_QT = ( QC.Qt.Key.Key_Delete, )
DELETE_KEYS_HYDRUS = ( SHORTCUT_KEY_SPECIAL_DELETE, )
special_key_shortcut_enum_lookup = {
- QC.Qt.Key_Space : SHORTCUT_KEY_SPECIAL_SPACE,
- QC.Qt.Key_Backspace : SHORTCUT_KEY_SPECIAL_BACKSPACE,
- QC.Qt.Key_Tab : SHORTCUT_KEY_SPECIAL_TAB,
- QC.Qt.Key_Return : SHORTCUT_KEY_SPECIAL_RETURN,
- QC.Qt.Key_Enter : SHORTCUT_KEY_SPECIAL_ENTER,
- QC.Qt.Key_Pause : SHORTCUT_KEY_SPECIAL_PAUSE,
- QC.Qt.Key_Escape : SHORTCUT_KEY_SPECIAL_ESCAPE,
- QC.Qt.Key_Insert : SHORTCUT_KEY_SPECIAL_INSERT,
- QC.Qt.Key_Delete : SHORTCUT_KEY_SPECIAL_DELETE,
- QC.Qt.Key_Up : SHORTCUT_KEY_SPECIAL_UP,
- QC.Qt.Key_Down : SHORTCUT_KEY_SPECIAL_DOWN,
- QC.Qt.Key_Left : SHORTCUT_KEY_SPECIAL_LEFT,
- QC.Qt.Key_Right : SHORTCUT_KEY_SPECIAL_RIGHT,
- QC.Qt.Key_Home : SHORTCUT_KEY_SPECIAL_HOME,
- QC.Qt.Key_End : SHORTCUT_KEY_SPECIAL_END,
- QC.Qt.Key_PageUp : SHORTCUT_KEY_SPECIAL_PAGE_UP,
- QC.Qt.Key_PageDown : SHORTCUT_KEY_SPECIAL_PAGE_DOWN,
- QC.Qt.Key_F1 : SHORTCUT_KEY_SPECIAL_F1,
- QC.Qt.Key_F2 : SHORTCUT_KEY_SPECIAL_F2,
- QC.Qt.Key_F3 : SHORTCUT_KEY_SPECIAL_F3,
- QC.Qt.Key_F4 : SHORTCUT_KEY_SPECIAL_F4,
- QC.Qt.Key_F5 : SHORTCUT_KEY_SPECIAL_F5,
- QC.Qt.Key_F6 : SHORTCUT_KEY_SPECIAL_F6,
- QC.Qt.Key_F7 : SHORTCUT_KEY_SPECIAL_F7,
- QC.Qt.Key_F8 : SHORTCUT_KEY_SPECIAL_F8,
- QC.Qt.Key_F9 : SHORTCUT_KEY_SPECIAL_F9,
- QC.Qt.Key_F10 : SHORTCUT_KEY_SPECIAL_F10,
- QC.Qt.Key_F11 : SHORTCUT_KEY_SPECIAL_F11,
- QC.Qt.Key_F12 : SHORTCUT_KEY_SPECIAL_F12,
- QC.Qt.Key_MediaTogglePlayPause : SHORTCUT_KEY_SPECIAL_MEDIA_PLAY_PAUSE,
- QC.Qt.Key_MediaPlay : SHORTCUT_KEY_SPECIAL_MEDIA_PLAY_PAUSE,
- QC.Qt.Key_MediaPause : SHORTCUT_KEY_SPECIAL_MEDIA_PLAY_PAUSE,
- QC.Qt.Key_MediaPrevious : SHORTCUT_KEY_SPECIAL_MEDIA_PREVIOUS,
- QC.Qt.Key_MediaNext : SHORTCUT_KEY_SPECIAL_MEDIA_NEXT,
- QC.Qt.Key_VolumeDown : SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_DOWN,
- QC.Qt.Key_VolumeUp : SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_UP,
- QC.Qt.Key_VolumeMute : SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_MUTE_UNMUTE
+ QC.Qt.Key.Key_Space : SHORTCUT_KEY_SPECIAL_SPACE,
+ QC.Qt.Key.Key_Backspace : SHORTCUT_KEY_SPECIAL_BACKSPACE,
+ QC.Qt.Key.Key_Tab : SHORTCUT_KEY_SPECIAL_TAB,
+ QC.Qt.Key.Key_Return : SHORTCUT_KEY_SPECIAL_RETURN,
+ QC.Qt.Key.Key_Enter : SHORTCUT_KEY_SPECIAL_ENTER,
+ QC.Qt.Key.Key_Pause : SHORTCUT_KEY_SPECIAL_PAUSE,
+ QC.Qt.Key.Key_Escape : SHORTCUT_KEY_SPECIAL_ESCAPE,
+ QC.Qt.Key.Key_Insert : SHORTCUT_KEY_SPECIAL_INSERT,
+ QC.Qt.Key.Key_Delete : SHORTCUT_KEY_SPECIAL_DELETE,
+ QC.Qt.Key.Key_Up : SHORTCUT_KEY_SPECIAL_UP,
+ QC.Qt.Key.Key_Down : SHORTCUT_KEY_SPECIAL_DOWN,
+ QC.Qt.Key.Key_Left : SHORTCUT_KEY_SPECIAL_LEFT,
+ QC.Qt.Key.Key_Right : SHORTCUT_KEY_SPECIAL_RIGHT,
+ QC.Qt.Key.Key_Home : SHORTCUT_KEY_SPECIAL_HOME,
+ QC.Qt.Key.Key_End : SHORTCUT_KEY_SPECIAL_END,
+ QC.Qt.Key.Key_PageUp : SHORTCUT_KEY_SPECIAL_PAGE_UP,
+ QC.Qt.Key.Key_PageDown : SHORTCUT_KEY_SPECIAL_PAGE_DOWN,
+ QC.Qt.Key.Key_F1 : SHORTCUT_KEY_SPECIAL_F1,
+ QC.Qt.Key.Key_F2 : SHORTCUT_KEY_SPECIAL_F2,
+ QC.Qt.Key.Key_F3 : SHORTCUT_KEY_SPECIAL_F3,
+ QC.Qt.Key.Key_F4 : SHORTCUT_KEY_SPECIAL_F4,
+ QC.Qt.Key.Key_F5 : SHORTCUT_KEY_SPECIAL_F5,
+ QC.Qt.Key.Key_F6 : SHORTCUT_KEY_SPECIAL_F6,
+ QC.Qt.Key.Key_F7 : SHORTCUT_KEY_SPECIAL_F7,
+ QC.Qt.Key.Key_F8 : SHORTCUT_KEY_SPECIAL_F8,
+ QC.Qt.Key.Key_F9 : SHORTCUT_KEY_SPECIAL_F9,
+ QC.Qt.Key.Key_F10 : SHORTCUT_KEY_SPECIAL_F10,
+ QC.Qt.Key.Key_F11 : SHORTCUT_KEY_SPECIAL_F11,
+ QC.Qt.Key.Key_F12 : SHORTCUT_KEY_SPECIAL_F12,
+ QC.Qt.Key.Key_MediaTogglePlayPause : SHORTCUT_KEY_SPECIAL_MEDIA_PLAY_PAUSE,
+ QC.Qt.Key.Key_MediaPlay : SHORTCUT_KEY_SPECIAL_MEDIA_PLAY_PAUSE,
+ QC.Qt.Key.Key_MediaPause : SHORTCUT_KEY_SPECIAL_MEDIA_PLAY_PAUSE,
+ QC.Qt.Key.Key_MediaPrevious : SHORTCUT_KEY_SPECIAL_MEDIA_PREVIOUS,
+ QC.Qt.Key.Key_MediaNext : SHORTCUT_KEY_SPECIAL_MEDIA_NEXT,
+ QC.Qt.Key.Key_VolumeDown : SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_DOWN,
+ QC.Qt.Key.Key_VolumeUp : SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_UP,
+ QC.Qt.Key.Key_VolumeMute : SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_MUTE_UNMUTE
}
special_key_shortcut_str_lookup = {
@@ -180,12 +180,12 @@
SHORTCUT_MOUSE_CLICKS = { SHORTCUT_MOUSE_LEFT, SHORTCUT_MOUSE_MIDDLE, SHORTCUT_MOUSE_RIGHT, SHORTCUT_MOUSE_BACK, SHORTCUT_MOUSE_FORWARD, SHORTCUT_MOUSE_TASK }
qt_mouse_buttons_to_hydrus_mouse_buttons = {
- QC.Qt.LeftButton : SHORTCUT_MOUSE_LEFT,
- QC.Qt.MiddleButton : SHORTCUT_MOUSE_MIDDLE,
- QC.Qt.RightButton : SHORTCUT_MOUSE_RIGHT,
- QC.Qt.BackButton : SHORTCUT_MOUSE_BACK,
- QC.Qt.ForwardButton : SHORTCUT_MOUSE_FORWARD,
- QC.Qt.TaskButton : SHORTCUT_MOUSE_TASK
+ QC.Qt.MouseButton.LeftButton : SHORTCUT_MOUSE_LEFT,
+ QC.Qt.MouseButton.MiddleButton : SHORTCUT_MOUSE_MIDDLE,
+ QC.Qt.MouseButton.RightButton : SHORTCUT_MOUSE_RIGHT,
+ QC.Qt.MouseButton.BackButton : SHORTCUT_MOUSE_BACK,
+ QC.Qt.MouseButton.ForwardButton : SHORTCUT_MOUSE_FORWARD,
+ QC.Qt.MouseButton.TaskButton : SHORTCUT_MOUSE_TASK
}
shortcut_mouse_string_lookup = {
@@ -507,32 +507,32 @@ def ConvertKeyEventToShortcut( event ):
modifiers = []
- if event.modifiers() & QC.Qt.AltModifier:
+ if event.modifiers() & QC.Qt.KeyboardModifier.AltModifier:
modifiers.append( SHORTCUT_MODIFIER_ALT )
- if event.modifiers() & QC.Qt.ControlModifier:
+ if event.modifiers() & QC.Qt.KeyboardModifier.ControlModifier:
modifiers.append( SHORTCUT_MODIFIER_CTRL )
- if event.modifiers() & QC.Qt.MetaModifier:
+ if event.modifiers() & QC.Qt.KeyboardModifier.MetaModifier:
modifiers.append( SHORTCUT_MODIFIER_META )
- if event.modifiers() & QC.Qt.ShiftModifier:
+ if event.modifiers() & QC.Qt.KeyboardModifier.ShiftModifier:
modifiers.append( SHORTCUT_MODIFIER_SHIFT )
- if event.modifiers() & QC.Qt.GroupSwitchModifier:
+ if event.modifiers() & QC.Qt.KeyboardModifier.GroupSwitchModifier:
modifiers.append( SHORTCUT_MODIFIER_GROUP_SWITCH )
- if event.modifiers() & QC.Qt.KeypadModifier:
+ if event.modifiers() & QC.Qt.KeyboardModifier.KeypadModifier:
do_it = True
@@ -568,31 +568,31 @@ def ConvertKeyEventToShortcut( event ):
def ConvertKeyEventToSimpleTuple( event ):
- modifier = QC.Qt.NoModifier
+ modifier = QC.Qt.KeyboardModifier.NoModifier
- if event.modifiers() & QC.Qt.AltModifier:
+ if event.modifiers() & QC.Qt.KeyboardModifier.AltModifier:
- modifier = QC.Qt.AltModifier
+ modifier = QC.Qt.KeyboardModifier.AltModifier
- elif event.modifiers() & QC.Qt.ControlModifier:
+ elif event.modifiers() & QC.Qt.KeyboardModifier.ControlModifier:
- modifier = QC.Qt.ControlModifier
+ modifier = QC.Qt.KeyboardModifier.ControlModifier
- elif event.modifiers() & QC.Qt.ShiftModifier:
+ elif event.modifiers() & QC.Qt.KeyboardModifier.ShiftModifier:
- modifier = QC.Qt.ShiftModifier
+ modifier = QC.Qt.KeyboardModifier.ShiftModifier
- elif event.modifiers() & QC.Qt.KeypadModifier:
+ elif event.modifiers() & QC.Qt.KeyboardModifier.KeypadModifier:
- modifier = QC.Qt.KeypadModifier
+ modifier = QC.Qt.KeyboardModifier.KeypadModifier
- elif event.modifiers() & QC.Qt.GroupSwitchModifier:
+ elif event.modifiers() & QC.Qt.KeyboardModifier.GroupSwitchModifier:
- modifier = QC.Qt.GroupSwitchModifier
+ modifier = QC.Qt.KeyboardModifier.GroupSwitchModifier
- elif event.modifiers() & QC.Qt.MetaModifier:
+ elif event.modifiers() & QC.Qt.KeyboardModifier.MetaModifier:
- modifier = QC.Qt.MetaModifier
+ modifier = QC.Qt.KeyboardModifier.MetaModifier
key = event.key()
@@ -609,7 +609,7 @@ def ConvertMouseEventToShortcut( event: typing.Union[ QG.QMouseEvent, QG.QWheelE
shortcut_press_type = SHORTCUT_PRESS_TYPE_PRESS
- if event.type() == QC.QEvent.MouseButtonPress:
+ if event.type() == QC.QEvent.Type.MouseButtonPress:
for ( qt_button, hydrus_button ) in qt_mouse_buttons_to_hydrus_mouse_buttons.items():
@@ -621,13 +621,13 @@ def ConvertMouseEventToShortcut( event: typing.Union[ QG.QMouseEvent, QG.QWheelE
- elif event.type() in ( QC.QEvent.MouseButtonDblClick, QC.QEvent.MouseButtonRelease ):
+ elif event.type() in ( QC.QEvent.Type.MouseButtonDblClick, QC.QEvent.Type.MouseButtonRelease ):
- if event.type() == QC.QEvent.MouseButtonRelease:
+ if event.type() == QC.QEvent.Type.MouseButtonRelease:
shortcut_press_type = SHORTCUT_PRESS_TYPE_RELEASE
- elif event.type() == QC.QEvent.MouseButtonDblClick:
+ elif event.type() == QC.QEvent.Type.MouseButtonDblClick:
shortcut_press_type = SHORTCUT_PRESS_TYPE_DOUBLE_CLICK
@@ -642,7 +642,7 @@ def ConvertMouseEventToShortcut( event: typing.Union[ QG.QMouseEvent, QG.QWheelE
- elif event.type() == QC.QEvent.Wheel:
+ elif event.type() == QC.QEvent.Type.Wheel:
angle_delta_point = event.angleDelta()
@@ -689,27 +689,27 @@ def ConvertMouseEventToShortcut( event: typing.Union[ QG.QMouseEvent, QG.QWheelE
modifiers = []
- if event.modifiers() & QC.Qt.AltModifier:
+ if event.modifiers() & QC.Qt.KeyboardModifier.AltModifier:
modifiers.append( SHORTCUT_MODIFIER_ALT )
- if event.modifiers() & QC.Qt.ControlModifier:
+ if event.modifiers() & QC.Qt.KeyboardModifier.ControlModifier:
modifiers.append( SHORTCUT_MODIFIER_CTRL )
- if event.modifiers() & QC.Qt.MetaModifier:
+ if event.modifiers() & QC.Qt.KeyboardModifier.MetaModifier:
modifiers.append( SHORTCUT_MODIFIER_META )
- if event.modifiers() & QC.Qt.ShiftModifier:
+ if event.modifiers() & QC.Qt.KeyboardModifier.ShiftModifier:
modifiers.append( SHORTCUT_MODIFIER_SHIFT )
- if event.modifiers() & QC.Qt.GroupSwitchModifier:
+ if event.modifiers() & QC.Qt.KeyboardModifier.GroupSwitchModifier:
modifiers.append( SHORTCUT_MODIFIER_GROUP_SWITCH )
@@ -776,13 +776,13 @@ def IShouldCatchShortcutEvent( event_handler_owner: QC.QObject, event_catcher: Q
if event_handler_owner != event_catcher:
# don't pass clicks up
- if event.type() in ( QC.QEvent.MouseButtonPress, QC.QEvent.MouseButtonRelease, QC.QEvent.MouseButtonDblClick ):
+ if event.type() in ( QC.QEvent.Type.MouseButtonPress, QC.QEvent.Type.MouseButtonRelease, QC.QEvent.Type.MouseButtonDblClick ):
return False
# don't pass wheels that happen to legit controls that want to eat it, like a list, when the catcher is a window
- if event.type() == QC.QEvent.Wheel:
+ if event.type() == QC.QEvent.Type.Wheel:
widget_under_mouse = event_catcher.childAt( event_catcher.mapFromGlobal( QG.QCursor.pos() ) )
@@ -798,7 +798,7 @@ def IShouldCatchShortcutEvent( event_handler_owner: QC.QObject, event_catcher: Q
- if event.type() == QC.QEvent.Wheel:
+ if event.type() == QC.QEvent.Type.Wheel:
do_focus_test = False
@@ -1475,7 +1475,7 @@ def eventFilter( self, watched, event ):
try:
- if event.type() == QC.QEvent.KeyPress:
+ if event.type() == QC.QEvent.Type.KeyPress:
i_should_catch_shortcut_event = IShouldCatchShortcutEvent( self._filter_target, watched, event = event )
@@ -1514,20 +1514,20 @@ def eventFilter( self, watched, event ):
elif self._catch_mouse:
- if event.type() in ( QC.QEvent.MouseButtonPress, QC.QEvent.MouseButtonRelease, QC.QEvent.MouseButtonDblClick, QC.QEvent.Wheel ):
+ if event.type() in ( QC.QEvent.Type.MouseButtonPress, QC.QEvent.Type.MouseButtonRelease, QC.QEvent.Type.MouseButtonDblClick, QC.QEvent.Type.Wheel ):
global CUMULATIVE_MOUSEWARP_MANHATTAN_LENGTH
- if event.type() == QC.QEvent.MouseButtonPress:
+ if event.type() == QC.QEvent.Type.MouseButtonPress:
self._last_click_down_position = event.globalPosition().toPoint()
CUMULATIVE_MOUSEWARP_MANHATTAN_LENGTH = 0
- #if event.type() != QC.QEvent.Wheel and self._ignore_activating_mouse_click and not HydrusTime.TimeHasPassedPrecise( self._frame_activated_time + 0.017 ):
- if event.type() != QC.QEvent.Wheel and self._ignore_activating_mouse_click and not self._parent_currently_activated:
+ #if event.type() != QC.QEvent.Type.Wheel and self._ignore_activating_mouse_click and not HydrusTime.TimeHasPassedPrecise( self._frame_activated_time + 0.017 ):
+ if event.type() != QC.QEvent.Type.Wheel and self._ignore_activating_mouse_click and not self._parent_currently_activated:
- if event.type() == QC.QEvent.MouseButtonRelease and self._activating_wait_job is not None:
+ if event.type() == QC.QEvent.Type.MouseButtonRelease and self._activating_wait_job is not None:
# first completed click in the time window sets us active instantly
self._activating_wait_job.Cancel()
@@ -1538,7 +1538,7 @@ def eventFilter( self, watched, event ):
return False
- if event.type() == QC.QEvent.MouseButtonRelease:
+ if event.type() == QC.QEvent.Type.MouseButtonRelease:
release_press_pos = event.globalPosition().toPoint()
@@ -1704,11 +1704,11 @@ def eventFilter( self, watched, event ):
try:
- if event.type() == QC.QEvent.WindowActivate:
+ if event.type() == QC.QEvent.Type.WindowActivate:
self._shortcuts_handler.FrameActivated()
- elif event.type() == QC.QEvent.WindowDeactivate:
+ elif event.type() == QC.QEvent.Type.WindowDeactivate:
self._shortcuts_handler.FrameDeactivated()
diff --git a/hydrus/client/gui/ClientGUISplash.py b/hydrus/client/gui/ClientGUISplash.py
index b07ad8ae3..7c827cfb4 100644
--- a/hydrus/client/gui/ClientGUISplash.py
+++ b/hydrus/client/gui/ClientGUISplash.py
@@ -44,15 +44,15 @@ def __init__( self, parent, controller: "CG.ClientController.Controller", frame_
self._image_label.setPixmap( self._hydrus_pixmap )
- self._image_label.setAlignment( QC.Qt.AlignCenter )
+ self._image_label.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
self._title_label = ClientGUICommon.BetterStaticText( self, label = ' ' )
self._status_label = ClientGUICommon.BetterStaticText( self, label = ' ' )
self._status_sub_label = ClientGUICommon.BetterStaticText( self, label = ' ' )
- self._title_label.setAlignment( QC.Qt.AlignCenter )
- self._status_label.setAlignment( QC.Qt.AlignCenter )
- self._status_sub_label.setAlignment( QC.Qt.AlignCenter )
+ self._title_label.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
+ self._status_label.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
+ self._status_sub_label.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
vbox = QP.VBoxLayout()
@@ -70,7 +70,7 @@ def __init__( self, parent, controller: "CG.ClientController.Controller", frame_
def mouseMoveEvent( self, event ):
- if ( event.buttons() & QC.Qt.LeftButton ) and self._drag_last_pos is not None:
+ if ( event.buttons() & QC.Qt.MouseButton.LeftButton ) and self._drag_last_pos is not None:
mouse_pos = QG.QCursor.pos()
@@ -92,7 +92,7 @@ def mouseMoveEvent( self, event ):
def mousePressEvent( self, event ):
- if event.button() == QC.Qt.LeftButton:
+ if event.button() == QC.Qt.MouseButton.LeftButton:
self._drag_last_pos = QG.QCursor.pos()
@@ -106,7 +106,7 @@ def mousePressEvent( self, event ):
def mouseReleaseEvent( self, event ):
- if event.button() == QC.Qt.LeftButton:
+ if event.button() == QC.Qt.MouseButton.LeftButton:
self._drag_last_pos = None
@@ -237,11 +237,11 @@ def __init__( self, controller: "CG.ClientController.Controller", title, frame_s
super().__init__( None )
- self.setWindowFlag( QC.Qt.CustomizeWindowHint )
- self.setWindowFlag( QC.Qt.WindowContextHelpButtonHint, on = False )
- self.setWindowFlag( QC.Qt.WindowCloseButtonHint, on = False )
- self.setWindowFlag( QC.Qt.WindowMaximizeButtonHint, on = False )
- self.setAttribute( QC.Qt.WA_DeleteOnClose )
+ self.setWindowFlag( QC.Qt.WindowType.CustomizeWindowHint )
+ self.setWindowFlag( QC.Qt.WindowType.WindowContextHelpButtonHint, on = False )
+ self.setWindowFlag( QC.Qt.WindowType.WindowCloseButtonHint, on = False )
+ self.setWindowFlag( QC.Qt.WindowType.WindowMaximizeButtonHint, on = False )
+ self.setAttribute( QC.Qt.WidgetAttribute.WA_DeleteOnClose )
self.setWindowTitle( title )
diff --git a/hydrus/client/gui/ClientGUIStringPanels.py b/hydrus/client/gui/ClientGUIStringPanels.py
index 9e3d1fe80..7b5619bee 100644
--- a/hydrus/client/gui/ClientGUIStringPanels.py
+++ b/hydrus/client/gui/ClientGUIStringPanels.py
@@ -66,7 +66,7 @@ def __init__( self, parent: QW.QWidget, string_processor: ClientStrings.StringPr
def _GetStartingTexts( self ):
- return [ self._test_data.item( i ).data( QC.Qt.UserRole ) for i in range( self._test_data.count() ) ]
+ return [ self._test_data.item( i ).data( QC.Qt.ItemDataRole.UserRole ) for i in range( self._test_data.count() ) ]
def _UpdateResults( self ):
@@ -89,7 +89,7 @@ def _UpdateResults( self ):
item = QW.QListWidgetItem()
item.setText( result )
- item.setData( QC.Qt.UserRole, result )
+ item.setData( QC.Qt.ItemDataRole.UserRole, result )
self._result_data.insertItem( insertion_index, item )
@@ -103,7 +103,7 @@ def EventSelection( self ):
( list_widget_item, ) = items
- text = list_widget_item.data( QC.Qt.UserRole )
+ text = list_widget_item.data( QC.Qt.ItemDataRole.UserRole )
self.textSelected.emit( text )
@@ -141,7 +141,7 @@ def SetTestData( self, test_data: ClientParsing.ParsingTestData ):
item = QW.QListWidgetItem()
item.setText( text )
- item.setData( QC.Qt.UserRole, text )
+ item.setData( QC.Qt.ItemDataRole.UserRole, text )
self._test_data.insertItem( insertion_index, item )
@@ -153,7 +153,7 @@ def SetTestData( self, test_data: ClientParsing.ParsingTestData ):
self._test_data.item( 0 ).setSelected( False )
self._test_data.item( 0 ).setSelected( True )
- #self.textSelected.emit( self._test_data.item( 0 ).data( QC.Qt.UserRole ) )
+ #self.textSelected.emit( self._test_data.item( 0 ).data( QC.Qt.ItemDataRole.UserRole ) )
diff --git a/hydrus/client/gui/ClientGUITagSuggestions.py b/hydrus/client/gui/ClientGUITagSuggestions.py
index 17838d9e3..91a089f73 100644
--- a/hydrus/client/gui/ClientGUITagSuggestions.py
+++ b/hydrus/client/gui/ClientGUITagSuggestions.py
@@ -114,7 +114,7 @@ def TakeFocusForUser( self ):
self.SelectTopItem()
- self.setFocus( QC.Qt.OtherFocusReason )
+ self.setFocus( QC.Qt.FocusReason.OtherFocusReason )
class ListBoxTagsSuggestionsRelated( ClientGUIListBoxes.ListBoxTagsPredicates ):
@@ -164,7 +164,7 @@ def TakeFocusForUser( self ):
self.SelectTopItem()
- self.setFocus( QC.Qt.OtherFocusReason )
+ self.setFocus( QC.Qt.FocusReason.OtherFocusReason )
class FavouritesTagsPanel( QW.QWidget ):
@@ -748,7 +748,7 @@ def TakeFocusForUser( self ):
else:
- self._fetch_button.setFocus( QC.Qt.OtherFocusReason )
+ self._fetch_button.setFocus( QC.Qt.FocusReason.OtherFocusReason )
diff --git a/hydrus/client/gui/ClientGUITags.py b/hydrus/client/gui/ClientGUITags.py
index 317c29263..866f92585 100644
--- a/hydrus/client/gui/ClientGUITags.py
+++ b/hydrus/client/gui/ClientGUITags.py
@@ -766,7 +766,7 @@ def __init__( self, parent, tag_filter, only_show_blacklist = False, namespaces
self._current_filter_st = ClientGUICommon.BetterStaticText( self, 'current filter: ', ellipsize_end = True )
self._test_result_st = ClientGUICommon.BetterStaticText( self, self.TEST_RESULT_DEFAULT )
- self._test_result_st.setAlignment( QC.Qt.AlignVCenter | QC.Qt.AlignRight )
+ self._test_result_st.setAlignment( QC.Qt.AlignmentFlag.AlignVCenter | QC.Qt.AlignmentFlag.AlignRight )
self._test_result_st.setWordWrap( True )
@@ -3463,7 +3463,7 @@ def SetMedia( self, media ):
def SetTagBoxFocus( self ):
- self._add_tag_box.setFocus( QC.Qt.OtherFocusReason )
+ self._add_tag_box.setFocus( QC.Qt.FocusReason.OtherFocusReason )
@@ -4081,11 +4081,11 @@ def SetTagBoxFocus( self ):
if len( self._children.GetTags() ) == 0:
- self._children_input.setFocus( QC.Qt.OtherFocusReason )
+ self._children_input.setFocus( QC.Qt.FocusReason.OtherFocusReason )
else:
- self._parents_input.setFocus( QC.Qt.OtherFocusReason )
+ self._parents_input.setFocus( QC.Qt.FocusReason.OtherFocusReason )
@@ -4893,11 +4893,11 @@ def SetTagBoxFocus( self ):
if len( self._old_siblings.GetTags() ) == 0:
- self._old_input.setFocus( QC.Qt.OtherFocusReason )
+ self._old_input.setFocus( QC.Qt.FocusReason.OtherFocusReason )
else:
- self._new_input.setFocus( QC.Qt.OtherFocusReason )
+ self._new_input.setFocus( QC.Qt.FocusReason.OtherFocusReason )
diff --git a/hydrus/client/gui/ClientGUITopLevelWindows.py b/hydrus/client/gui/ClientGUITopLevelWindows.py
index 12e797884..8f99ae06c 100644
--- a/hydrus/client/gui/ClientGUITopLevelWindows.py
+++ b/hydrus/client/gui/ClientGUITopLevelWindows.py
@@ -427,7 +427,7 @@ def __init__( self, parent, title, do_not_activate = False ):
if do_not_activate:
- self.setAttribute( QC.Qt.WA_ShowWithoutActivating )
+ self.setAttribute( QC.Qt.WidgetAttribute.WA_ShowWithoutActivating )
self.setWindowTitle( title )
@@ -643,10 +643,10 @@ def __init__( self, parent, title ):
self.setWindowTitle( title )
- self.setWindowFlags( QC.Qt.Window )
- self.setWindowFlag( QC.Qt.WindowContextHelpButtonHint, on = False )
+ self.setWindowFlags( QC.Qt.WindowType.Window )
+ self.setWindowFlag( QC.Qt.WindowType.WindowContextHelpButtonHint, on = False )
- self.setAttribute( QC.Qt.WA_DeleteOnClose )
+ self.setAttribute( QC.Qt.WidgetAttribute.WA_DeleteOnClose )
self._new_options = CG.client_controller.new_options
diff --git a/hydrus/client/gui/QLocator.py b/hydrus/client/gui/QLocator.py
index 2e4b503d3..f0ba8c344 100644
--- a/hydrus/client/gui/QLocator.py
+++ b/hydrus/client/gui/QLocator.py
@@ -61,7 +61,7 @@ def __init__(self, parent = None):
def eventFilter(self, watched, event) -> bool:
try:
- if event.type() == QC.QEvent.FocusIn:
+ if event.type() == QC.QEvent.Type.FocusIn:
self.focused.emit()
@@ -95,16 +95,16 @@ def __init__(self, title: str, iconPath: str, height: int, shouldRemainHidden: b
self.iconLabel.setPixmap(self.icon.pixmap(self.iconHeight, self.iconHeight))
self.titleLabel = QW.QLabel()
self.titleLabel.setText(title)
- self.titleLabel.setTextFormat(QC.Qt.RichText)
+ self.titleLabel.setTextFormat(QC.Qt.TextFormat.RichText)
self.countLabel = QW.QLabel()
self.layout().setContentsMargins(4, 1, 4, 1)
self.layout().addWidget(self.iconLabel)
self.layout().addWidget(self.titleLabel)
self.layout().addStretch(1)
self.layout().addWidget(self.countLabel)
- self.layout().setAlignment(self.countLabel, QC.Qt.AlignVCenter)
- self.layout().setAlignment(self.iconLabel, QC.Qt.AlignVCenter)
- self.layout().setAlignment(self.titleLabel, QC.Qt.AlignVCenter)
+ self.layout().setAlignment(self.countLabel, QC.Qt.AlignmentFlag.AlignVCenter)
+ self.layout().setAlignment(self.iconLabel, QC.Qt.AlignmentFlag.AlignVCenter)
+ self.layout().setAlignment(self.titleLabel, QC.Qt.AlignmentFlag.AlignVCenter)
titleFont = self.titleLabel.font()
titleFont.setBold(True)
self.titleLabel.setFont(titleFont)
@@ -136,31 +136,31 @@ def __init__(self, keyEventTarget: QW.QWidget, height: int, primaryTextWidth: in
self.mainTextLabel = QW.QLabel(self)
self.primaryTextWidth = primaryTextWidth
self.mainTextLabel.setMinimumWidth(primaryTextWidth)
- self.mainTextLabel.setTextFormat(QC.Qt.RichText)
- self.mainTextLabel.setTextInteractionFlags(QC.Qt.NoTextInteraction)
+ self.mainTextLabel.setTextFormat(QC.Qt.TextFormat.RichText)
+ self.mainTextLabel.setTextInteractionFlags(QC.Qt.TextInteractionFlag.NoTextInteraction)
self.secondaryTextLabel = QW.QLabel(self)
self.secondaryTextWidth = secondaryTextWidth
self.secondaryTextLabel.setMaximumWidth(secondaryTextWidth)
- self.secondaryTextLabel.setTextFormat(QC.Qt.RichText)
- self.secondaryTextLabel.setTextInteractionFlags(QC.Qt.NoTextInteraction)
+ self.secondaryTextLabel.setTextFormat(QC.Qt.TextFormat.RichText)
+ self.secondaryTextLabel.setTextInteractionFlags(QC.Qt.TextInteractionFlag.NoTextInteraction)
self.layout().setContentsMargins(4, 1, 4, 1)
self.layout().addWidget(self.iconLabel)
self.layout().addWidget(self.mainTextLabel)
self.layout().addStretch(1)
self.layout().addWidget(self.secondaryTextLabel)
- self.layout().setAlignment(self.mainTextLabel, QC.Qt.AlignVCenter)
- self.layout().setAlignment(self.iconLabel, QC.Qt.AlignVCenter)
- self.layout().setAlignment(self.secondaryTextLabel, QC.Qt.AlignVCenter)
+ self.layout().setAlignment(self.mainTextLabel, QC.Qt.AlignmentFlag.AlignVCenter)
+ self.layout().setAlignment(self.iconLabel, QC.Qt.AlignmentFlag.AlignVCenter)
+ self.layout().setAlignment(self.secondaryTextLabel, QC.Qt.AlignmentFlag.AlignVCenter)
self.setFixedHeight(height)
self.setSizePolicy(QW.QSizePolicy.Policy.Expanding, QW.QSizePolicy.Policy.Fixed)
- self.activateEnterShortcut = QW.QShortcut(QG.QKeySequence(QC.Qt.Key_Enter), self)
- self.activateEnterShortcut.setContext(QC.Qt.WidgetShortcut)
- self.activateReturnShortcut = QW.QShortcut(QG.QKeySequence(QC.Qt.Key_Return), self)
- self.activateReturnShortcut.setContext(QC.Qt.WidgetShortcut)
- self.upShortcut = QW.QShortcut(QG.QKeySequence(QC.Qt.Key_Up), self)
- self.upShortcut.setContext(QC.Qt.WidgetShortcut)
- self.downShortcut = QW.QShortcut(QG.QKeySequence(QC.Qt.Key_Down), self)
- self.downShortcut.setContext(QC.Qt.WidgetShortcut)
+ self.activateEnterShortcut = QW.QShortcut(QG.QKeySequence(QC.Qt.Key.Key_Enter), self)
+ self.activateEnterShortcut.setContext(QC.Qt.ShortcutContext.WidgetShortcut)
+ self.activateReturnShortcut = QW.QShortcut(QG.QKeySequence(QC.Qt.Key.Key_Return), self)
+ self.activateReturnShortcut.setContext(QC.Qt.ShortcutContext.WidgetShortcut)
+ self.upShortcut = QW.QShortcut(QG.QKeySequence(QC.Qt.Key.Key_Up), self)
+ self.upShortcut.setContext(QC.Qt.ShortcutContext.WidgetShortcut)
+ self.downShortcut = QW.QShortcut(QG.QKeySequence(QC.Qt.Key.Key_Down), self)
+ self.downShortcut.setContext(QC.Qt.ShortcutContext.WidgetShortcut)
self.selectedPalette = self.palette()
self.selectedPalette.setColor(QG.QPalette.Window, QG.QPalette().color(QG.QPalette.WindowText))
@@ -244,7 +244,7 @@ def setSelected(self, selected: bool):
self.iconLabel.setPixmap(iconToUse.pixmap(self.iconHeight, self.iconHeight, QG.QIcon.Mode.Selected if self.selected else QG.QIcon.Mode.Normal))
def keyPressEvent(self, ev: QG.QKeyEvent):
- if ev.key() != QC.Qt.Key_Up and ev.key() != QC.Qt.Key_Down and ev.key() != QC.Qt.Key_Enter and ev.key() != QC.Qt.Key_Return:
+ if ev.key() != QC.Qt.Key.Key_Up and ev.key() != QC.Qt.Key.Key_Down and ev.key() != QC.Qt.Key.Key_Enter and ev.key() != QC.Qt.Key.Key_Return:
QW.QApplication.postEvent(self.keyEventTarget, QG.QKeyEvent(ev.type(), ev.key(), ev.modifiers(), ev.text(), ev.isAutoRepeat()))
self.keyEventTarget.setFocus()
else:
@@ -373,7 +373,7 @@ class QLocatorWidget(QW.QWidget):
finished = QC.Signal()
def __init__(self, parent = None, width: int = 600, resultHeight: int = 36, titleHeight: int = 36, primaryTextWidth: int = 320, secondaryTextWidth: int = 200, maxVisibleItemCount: int = 8):
super().__init__(parent)
- self.alignment = QC.Qt.AlignCenter
+ self.alignment = QC.Qt.AlignmentFlag.AlignCenter
self.resultHeight = resultHeight
self.titleHeight = titleHeight
self.primaryTextWidth = primaryTextWidth
@@ -406,12 +406,12 @@ def __init__(self, parent = None, width: int = 600, resultHeight: int = 36, titl
self.resultLayout.setContentsMargins(0, 0, 0, 0)
self.resultLayout.setSpacing(0)
self.setFixedWidth(width)
- self.setWindowFlags(QC.Qt.FramelessWindowHint | QC.Qt.WindowStaysOnTopHint | QC.Qt.CustomizeWindowHint | QC.Qt.Popup)
+ self.setWindowFlags(QC.Qt.WindowType.FramelessWindowHint | QC.Qt.WindowType.WindowStaysOnTopHint | QC.Qt.WindowType.CustomizeWindowHint | QC.Qt.WindowType.Popup)
self.resultList.setSizeAdjustPolicy(QW.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents)
self.setSizePolicy(QW.QSizePolicy.Policy.Fixed, QW.QSizePolicy.Policy.Maximum)
- self.setEscapeShortcuts([QC.Qt.Key_Escape])
- self.editorDownShortcut = QW.QShortcut(QG.QKeySequence(QC.Qt.Key_Down), self.searchEdit)
- self.editorDownShortcut.setContext(QC.Qt.WidgetShortcut)
+ self.setEscapeShortcuts([QC.Qt.Key.Key_Escape])
+ self.editorDownShortcut = QW.QShortcut(QG.QKeySequence(QC.Qt.Key.Key_Down), self.searchEdit)
+ self.editorDownShortcut.setContext(QC.Qt.ShortcutContext.WidgetShortcut)
self.editorDownShortcut.activated.connect(self.handleEditorDown)
def handleTextEdited():
@@ -446,10 +446,10 @@ def handleQueryTimeout():
self.updateResultListHeight()
def setAlignment( self, alignment ):
- if alignment == QC.Qt.AlignCenter:
+ if alignment == QC.Qt.AlignmentFlag.AlignCenter:
self.alignment = alignment
self.updateAlignment()
- elif alignment == QC.Qt.AlignTop:
+ elif alignment == QC.Qt.AlignmentFlag.AlignTop:
self.alignment = alignment
self.updateAlignment()
@@ -466,12 +466,12 @@ def updateAlignment( self ):
if widget != self: # there is a parent
screenRect = widget.geometry()
- if self.alignment == QC.Qt.AlignCenter:
- centerRect = QW.QStyle.alignedRect(QC.Qt.LeftToRight, QC.Qt.AlignCenter, self.size(), screenRect)
+ if self.alignment == QC.Qt.AlignmentFlag.AlignCenter:
+ centerRect = QW.QStyle.alignedRect(QC.Qt.LayoutDirection.LeftToRight, QC.Qt.AlignmentFlag.AlignCenter, self.size(), screenRect)
centerRect.setY(max(0, centerRect.y() - self.resultHeight * 4))
self.setGeometry(centerRect)
- elif self.alignment == QC.Qt.AlignTop:
- rect = QW.QStyle.alignedRect(QC.Qt.LeftToRight, QC.Qt.AlignHCenter | QC.Qt.AlignTop, self.size(), screenRect)
+ elif self.alignment == QC.Qt.AlignmentFlag.AlignTop:
+ rect = QW.QStyle.alignedRect(QC.Qt.LayoutDirection.LeftToRight, QC.Qt.AlignmentFlag.AlignHCenter | QC.Qt.AlignmentFlag.AlignTop, self.size(), screenRect)
self.setGeometry(rect)
def paintEvent(self, event):
diff --git a/hydrus/client/gui/QtPorting.py b/hydrus/client/gui/QtPorting.py
index bca22ec2a..38944c689 100644
--- a/hydrus/client/gui/QtPorting.py
+++ b/hydrus/client/gui/QtPorting.py
@@ -86,7 +86,7 @@ def __init__( self, parent = None ):
self._max_label = QW.QLabel()
self._value_label = QW.QLabel()
self._slider = QW.QSlider()
- self._slider.setOrientation( QC.Qt.Horizontal )
+ self._slider.setOrientation( QC.Qt.Orientation.Horizontal )
self._slider.setTickInterval( 1 )
self._slider.setTickPosition( QW.QSlider.TickPosition.TicksBothSides )
@@ -96,8 +96,8 @@ def __init__( self, parent = None ):
self.layout().addLayout( top_layout )
self.layout().addWidget( self._value_label )
- self._value_label.setAlignment( QC.Qt.AlignVCenter | QC.Qt.AlignHCenter )
- self.layout().setAlignment( self._value_label, QC.Qt.AlignHCenter )
+ self._value_label.setAlignment( QC.Qt.AlignmentFlag.AlignVCenter | QC.Qt.AlignmentFlag.AlignHCenter )
+ self.layout().setAlignment( self._value_label, QC.Qt.AlignmentFlag.AlignHCenter )
self._slider.valueChanged.connect( self._UpdateLabels )
@@ -370,7 +370,7 @@ def mousePressEvent( self, event ):
index = self.tabAt( event.position().toPoint() )
- if event.button() == QC.Qt.LeftButton:
+ if event.button() == QC.Qt.MouseButton.LeftButton:
self._last_clicked_tab_index = index
@@ -386,7 +386,7 @@ def mouseReleaseEvent( self, event ):
index = self.tabAt( event.position().toPoint() )
- if event.button() == QC.Qt.MiddleButton:
+ if event.button() == QC.Qt.MouseButton.MiddleButton:
if index != -1:
@@ -403,7 +403,7 @@ def mouseDoubleClickEvent( self, event ):
index = self.tabAt( event.position().toPoint() )
- if event.button() == QC.Qt.LeftButton:
+ if event.button() == QC.Qt.MouseButton.LeftButton:
if index == -1:
@@ -416,7 +416,7 @@ def mouseDoubleClickEvent( self, event ):
return
- elif event.button() == QC.Qt.MiddleButton:
+ elif event.button() == QC.Qt.MouseButton.MiddleButton:
if index == -1:
@@ -453,7 +453,7 @@ def dragMoveEvent( self, event ):
if tab_index != -1:
- shift_down = event.modifiers() & QC.Qt.ShiftModifier
+ shift_down = event.modifiers() & QC.Qt.KeyboardModifier.ShiftModifier
if shift_down:
@@ -622,7 +622,7 @@ def mouseMoveEvent( self, e ):
return
- if e.buttons() != QC.Qt.LeftButton:
+ if e.buttons() != QC.Qt.MouseButton.LeftButton:
return
@@ -673,16 +673,16 @@ def mouseMoveEvent( self, e ):
drag.setPixmap( pixmap )
- cursor = QG.QCursor( QC.Qt.OpenHandCursor )
+ cursor = QG.QCursor( QC.Qt.CursorShape.OpenHandCursor )
drag.setHotSpot( QC.QPoint( 0, 0 ) )
# this puts the tab pixmap exactly where we picked it up, but it looks bad
# drag.setHotSpot( tab_bar_mouse_pos - tab_rect.topLeft() )
- drag.setDragCursor( cursor.pixmap(), QC.Qt.MoveAction )
+ drag.setDragCursor( cursor.pixmap(), QC.Qt.DropAction.MoveAction )
- drag.exec_( QC.Qt.MoveAction )
+ drag.exec_( QC.Qt.DropAction.MoveAction )
def dragEnterEvent( self, e: QG.QDragEnterEvent ):
@@ -721,7 +721,7 @@ def dragMoveEvent( self, event: QG.QDragMoveEvent ):
if tab_index != -1:
- shift_down = event.modifiers() & QC.Qt.ShiftModifier
+ shift_down = event.modifiers() & QC.Qt.KeyboardModifier.ShiftModifier
if shift_down:
@@ -809,7 +809,7 @@ def dropEvent( self, e: QG.QDropEvent ):
w = w.parentWidget()
- e.setDropAction( QC.Qt.MoveAction )
+ e.setDropAction( QC.Qt.DropAction.MoveAction )
e.accept()
@@ -887,7 +887,7 @@ def dropEvent( self, e: QG.QDropEvent ):
self.insertTab( insert_index, source_page, source_name )
- shift_down = e.modifiers() & QC.Qt.ShiftModifier
+ shift_down = e.modifiers() & QC.Qt.KeyboardModifier.ShiftModifier
follow_dropped_page = not shift_down
@@ -1100,25 +1100,25 @@ def AddToLayout( layout, item, flag = None, alignment = None ):
if flag in ( CC.FLAGS_CENTER, CC.FLAGS_SIZER_CENTER ):
- alignment = QC.Qt.AlignVCenter | QC.Qt.AlignHCenter
+ alignment = QC.Qt.AlignmentFlag.AlignVCenter | QC.Qt.AlignmentFlag.AlignHCenter
if flag == CC.FLAGS_ON_LEFT:
- alignment = QC.Qt.AlignLeft | QC.Qt.AlignVCenter
+ alignment = QC.Qt.AlignmentFlag.AlignLeft | QC.Qt.AlignmentFlag.AlignVCenter
elif flag == CC.FLAGS_ON_RIGHT:
- alignment = QC.Qt.AlignRight | QC.Qt.AlignVCenter
+ alignment = QC.Qt.AlignmentFlag.AlignRight | QC.Qt.AlignmentFlag.AlignVCenter
elif flag in ( CC.FLAGS_CENTER_PERPENDICULAR, CC.FLAGS_CENTER_PERPENDICULAR_EXPAND_DEPTH ):
if isinstance( layout, QW.QHBoxLayout ):
- alignment = QC.Qt.AlignVCenter
+ alignment = QC.Qt.AlignmentFlag.AlignVCenter
else:
- alignment = QC.Qt.AlignHCenter
+ alignment = QC.Qt.AlignmentFlag.AlignHCenter
@@ -1230,7 +1230,7 @@ def AdjustOpacity( image: QG.QImage, opacity_factor ):
new_image.setDevicePixelRatio( image.devicePixelRatio() )
- new_image.fill( QC.Qt.transparent )
+ new_image.fill( QC.Qt.GlobalColor.transparent )
painter = QG.QPainter( new_image )
@@ -1245,11 +1245,12 @@ def ToKeySequence( modifiers, key ):
if QtInit.WE_ARE_QT5:
+ # noinspection PyUnresolvedReferences
if isinstance( modifiers, QC.Qt.KeyboardModifiers ):
seq_str = ''
- for modifier in [ QC.Qt.ShiftModifier, QC.Qt.ControlModifier, QC.Qt.AltModifier, QC.Qt.MetaModifier, QC.Qt.KeypadModifier, QC.Qt.GroupSwitchModifier ]:
+ for modifier in [ QC.Qt.KeyboardModifier.ShiftModifier, QC.Qt.KeyboardModifier.ControlModifier, QC.Qt.KeyboardModifier.AltModifier, QC.Qt.KeyboardModifier.MetaModifier, QC.Qt.KeyboardModifier.KeypadModifier, QC.Qt.KeyboardModifier.GroupSwitchModifier ]:
if modifiers & modifier: seq_str += QG.QKeySequence( modifier ).toString()
@@ -1275,7 +1276,7 @@ def AddShortcut( widget, modifier, key, func: typing.Callable, *args ):
shortcut.setKey( ToKeySequence( modifier, key ) )
- shortcut.setContext( QC.Qt.WidgetWithChildrenShortcut )
+ shortcut.setContext( QC.Qt.ShortcutContext.WidgetWithChildrenShortcut )
shortcut.activated.connect( lambda: func( *args ) )
@@ -1387,16 +1388,16 @@ def GetClientData( widget, idx ):
if isinstance( widget, QW.QComboBox ):
- return widget.itemData( idx, QC.Qt.UserRole )
+ return widget.itemData( idx, QC.Qt.ItemDataRole.UserRole )
elif isinstance( widget, QW.QTreeWidget ):
- return widget.topLevelItem( idx ).data( 0, QC.Qt.UserRole )
+ return widget.topLevelItem( idx ).data( 0, QC.Qt.ItemDataRole.UserRole )
elif isinstance( widget, QW.QListWidget ):
- return widget.item( idx ).data( QC.Qt.UserRole )
+ return widget.item( idx ).data( QC.Qt.ItemDataRole.UserRole )
else:
@@ -1574,8 +1575,8 @@ def Char( self, widget, key, text = None ):
widget = QW.QApplication.focusWidget()
- ev1 = QG.QKeyEvent( QC.QEvent.KeyPress, key, QC.Qt.NoModifier, text = text )
- ev2 = QG.QKeyEvent( QC.QEvent.KeyRelease, key, QC.Qt.NoModifier, text = text )
+ ev1 = QG.QKeyEvent( QC.QEvent.Type.KeyPress, key, QC.Qt.KeyboardModifier.NoModifier, text = text )
+ ev2 = QG.QKeyEvent( QC.QEvent.Type.KeyRelease, key, QC.Qt.KeyboardModifier.NoModifier, text = text )
QW.QApplication.instance().postEvent( widget, ev1 )
QW.QApplication.instance().postEvent( widget, ev2 )
@@ -1662,7 +1663,7 @@ def paintEvent( self, event ):
for text_line in text_lines:
- elided_line = fontMetrics.elidedText( text_line, QC.Qt.ElideRight, my_width )
+ elided_line = fontMetrics.elidedText( text_line, QC.Qt.TextElideMode.ElideRight, my_width )
x = 0
width = my_width
@@ -1702,7 +1703,7 @@ def paintEvent( self, event ):
last_line = text_line[ line.textStart(): ]
- elided_last_line = fontMetrics.elidedText( last_line, QC.Qt.ElideRight, self.width() )
+ elided_last_line = fontMetrics.elidedText( last_line, QC.Qt.TextElideMode.ElideRight, self.width() )
painter.drawText( QC.QPoint( 0, y + fontMetrics.ascent() ), elided_last_line )
@@ -1734,7 +1735,7 @@ def __init__( self, parent = None, **kwargs ):
super().__init__( parent, **kwargs )
- self.setWindowFlag( QC.Qt.WindowContextHelpButtonHint, on = False )
+ self.setWindowFlag( QC.Qt.WindowType.WindowContextHelpButtonHint, on = False )
if title is not None:
@@ -1985,17 +1986,17 @@ def _UpdateParentCheckState( self, item: QW.QTreeWidgetItem ):
all_values = { child.checkState( 0 ) for child in self._GetChildren( parent ) }
- if all_values == { QC.Qt.Checked }:
+ if all_values == { QC.Qt.CheckState.Checked }:
- end_state = QC.Qt.Checked
+ end_state = QC.Qt.CheckState.Checked
- elif all_values == { QC.Qt.Unchecked }:
+ elif all_values == { QC.Qt.CheckState.Unchecked }:
- end_state = QC.Qt.Unchecked
+ end_state = QC.Qt.CheckState.Unchecked
else:
- end_state = QC.Qt.PartiallyChecked
+ end_state = QC.Qt.CheckState.PartiallyChecked
if end_state != parent.checkState( 0 ):
@@ -2061,36 +2062,36 @@ def eventFilter( self, watched, event ):
event_killed = False
- if type == QC.QEvent.WindowStateChange:
+ if type == QC.QEvent.Type.WindowStateChange:
if isValid( self._parent_widget ):
- if self._parent_widget.isMaximized() or (event.oldState() & QC.Qt.WindowMaximized): event_killed = event_killed or self._ExecuteCallbacks( 'EVT_MAXIMIZE', event )
+ if self._parent_widget.isMaximized() or (event.oldState() & QC.Qt.WindowState.WindowMaximized): event_killed = event_killed or self._ExecuteCallbacks( 'EVT_MAXIMIZE', event )
- elif type == QC.QEvent.MouseButtonDblClick:
+ elif type == QC.QEvent.Type.MouseButtonDblClick:
- if event.button() == QC.Qt.LeftButton:
+ if event.button() == QC.Qt.MouseButton.LeftButton:
event_killed = event_killed or self._ExecuteCallbacks( 'EVT_LEFT_DCLICK', event )
- elif event.button() == QC.Qt.RightButton:
+ elif event.button() == QC.Qt.MouseButton.RightButton:
event_killed = event_killed or self._ExecuteCallbacks( 'EVT_RIGHT_DCLICK', event )
- elif type == QC.QEvent.MouseButtonPress:
+ elif type == QC.QEvent.Type.MouseButtonPress:
- if event.buttons() & QC.Qt.LeftButton: event_killed = event_killed or self._ExecuteCallbacks( 'EVT_LEFT_DOWN', event )
+ if event.buttons() & QC.Qt.MouseButton.LeftButton: event_killed = event_killed or self._ExecuteCallbacks( 'EVT_LEFT_DOWN', event )
- if event.buttons() & QC.Qt.MiddleButton: event_killed = event_killed or self._ExecuteCallbacks( 'EVT_MIDDLE_DOWN', event )
+ if event.buttons() & QC.Qt.MouseButton.MiddleButton: event_killed = event_killed or self._ExecuteCallbacks( 'EVT_MIDDLE_DOWN', event )
- if event.buttons() & QC.Qt.RightButton: event_killed = event_killed or self._ExecuteCallbacks( 'EVT_RIGHT_DOWN', event )
+ if event.buttons() & QC.Qt.MouseButton.RightButton: event_killed = event_killed or self._ExecuteCallbacks( 'EVT_RIGHT_DOWN', event )
- elif type == QC.QEvent.MouseButtonRelease:
+ elif type == QC.QEvent.Type.MouseButtonRelease:
- if event.buttons() & QC.Qt.LeftButton: event_killed = event_killed or self._ExecuteCallbacks( 'EVT_LEFT_UP', event )
+ if event.buttons() & QC.Qt.MouseButton.LeftButton: event_killed = event_killed or self._ExecuteCallbacks( 'EVT_LEFT_UP', event )
- elif type == QC.QEvent.Move:
+ elif type == QC.QEvent.Type.Move:
event_killed = event_killed or self._ExecuteCallbacks( 'EVT_MOVE', event )
@@ -2098,15 +2099,15 @@ def eventFilter( self, watched, event ):
self._user_moved_window = True
- elif type == QC.QEvent.Resize:
+ elif type == QC.QEvent.Type.Resize:
event_killed = event_killed or self._ExecuteCallbacks( 'EVT_SIZE', event )
- elif type == QC.QEvent.NonClientAreaMouseButtonPress:
+ elif type == QC.QEvent.Type.NonClientAreaMouseButtonPress:
self._user_moved_window = False
- elif type == QC.QEvent.NonClientAreaMouseButtonRelease:
+ elif type == QC.QEvent.Type.NonClientAreaMouseButtonRelease:
if self._user_moved_window:
@@ -2137,7 +2138,7 @@ def _AddCallback( self, evt_name, callback ):
if evt_name in self._strong_focus_required:
- self._parent_widget.setFocusPolicy( QC.Qt.StrongFocus )
+ self._parent_widget.setFocusPolicy( QC.Qt.FocusPolicy.StrongFocus )
self._callback_map[ evt_name ].append( callback )
diff --git a/hydrus/client/gui/canvas/ClientGUICanvas.py b/hydrus/client/gui/canvas/ClientGUICanvas.py
index 586d19df2..9e08a4c4d 100644
--- a/hydrus/client/gui/canvas/ClientGUICanvas.py
+++ b/hydrus/client/gui/canvas/ClientGUICanvas.py
@@ -297,7 +297,7 @@ def eventFilter( self, watched, event ):
try:
- if watched == self.parent() and event.type() == QC.QEvent.LayoutRequest:
+ if watched == self.parent() and event.type() == QC.QEvent.Type.LayoutRequest:
return True
@@ -531,7 +531,7 @@ def _ManageTags( self ):
# take any focus away from hover window, which will mess up window order when it hides due to the new frame
- self.setFocus( QC.Qt.OtherFocusReason )
+ self.setFocus( QC.Qt.FocusReason.OtherFocusReason )
title = 'manage tags'
frame_key = 'manage_tags_frame'
@@ -1429,11 +1429,11 @@ def eventFilter( self, watched, event ):
try:
- if event.type() == QC.QEvent.MouseButtonPress and event.button() == QC.Qt.LeftButton:
+ if event.type() == QC.QEvent.Type.MouseButtonPress and event.button() == QC.Qt.MouseButton.LeftButton:
self._canvas.BeginDrag()
- elif event.type() == QC.QEvent.MouseButtonRelease and event.button() == QC.Qt.LeftButton:
+ elif event.type() == QC.QEvent.Type.MouseButtonRelease and event.button() == QC.Qt.MouseButton.LeftButton:
self._canvas.EndDrag()
@@ -1470,7 +1470,7 @@ def __init__( self, parent, page_key, location_context: ClientLocation.LocationC
def mouseReleaseEvent( self, event ):
- if event.button() != QC.Qt.RightButton:
+ if event.button() != QC.Qt.MouseButton.RightButton:
Canvas.mouseReleaseEvent( self, event )
@@ -1855,7 +1855,7 @@ def _DrawNotes( self, painter: QG.QPainter, current_y: int ):
if draw_a_test_rect:
painter.setPen( QG.QPen( QG.QColor( 20, 20, 20 ) ) )
- painter.setBrush( QC.Qt.NoBrush )
+ painter.setBrush( QC.Qt.BrushStyle.NoBrush )
painter.drawRect( left_x, current_y, notes_width, 100 )
@@ -1864,9 +1864,9 @@ def _DrawNotes( self, painter: QG.QPainter, current_y: int ):
painter.setFont( name_font )
- text_rect = painter.fontMetrics().boundingRect( left_x, current_y, notes_width, 100, QC.Qt.AlignHCenter | QC.Qt.TextWordWrap, name )
+ text_rect = painter.fontMetrics().boundingRect( left_x, current_y, notes_width, 100, QC.Qt.AlignmentFlag.AlignHCenter | QC.Qt.TextFlag.TextWordWrap, name )
- painter.drawText( text_rect, QC.Qt.AlignHCenter | QC.Qt.TextWordWrap, name )
+ painter.drawText( text_rect, QC.Qt.AlignmentFlag.AlignHCenter | QC.Qt.TextFlag.TextWordWrap, name )
current_y += text_rect.height() + PADDING
@@ -1876,9 +1876,9 @@ def _DrawNotes( self, painter: QG.QPainter, current_y: int ):
note = notes_manager.GetNote( name )
- text_rect = painter.fontMetrics().boundingRect( left_x, current_y, notes_width, 100, QC.Qt.AlignJustify | QC.Qt.TextWordWrap, note )
+ text_rect = painter.fontMetrics().boundingRect( left_x, current_y, notes_width, 100, QC.Qt.AlignmentFlag.AlignJustify | QC.Qt.TextFlag.TextWordWrap, note )
- painter.drawText( text_rect, QC.Qt.AlignJustify | QC.Qt.TextWordWrap, note )
+ painter.drawText( text_rect, QC.Qt.AlignmentFlag.AlignJustify | QC.Qt.TextFlag.TextWordWrap, note )
current_y += text_rect.height() + PADDING
@@ -2279,7 +2279,7 @@ def _HideCursorCheck( self ):
if can_hide:
- self.setCursor( QG.QCursor( QC.Qt.BlankCursor ) )
+ self.setCursor( QG.QCursor( QC.Qt.CursorShape.BlankCursor ) )
elif can_check_again:
@@ -2316,7 +2316,7 @@ def _TryToCloseWindow( self ):
def CleanBeforeDestroy( self ):
- self.setCursor( QG.QCursor( QC.Qt.ArrowCursor ) )
+ self.setCursor( QG.QCursor( QC.Qt.CursorShape.ArrowCursor ) )
super().CleanBeforeDestroy()
@@ -2355,10 +2355,10 @@ def mouseMoveEvent( self, event ):
# due to the mouse setPos below, the event pos can get funky I think due to out of order coordinate setting events, so we'll poll current value directly
event_pos = self.mapFromGlobal( QG.QCursor.pos() )
- mouse_currently_shown = self.cursor().shape() == QC.Qt.ArrowCursor
+ mouse_currently_shown = self.cursor().shape() == QC.Qt.CursorShape.ArrowCursor
show_mouse = mouse_currently_shown
- is_dragging = event.buttons() & QC.Qt.LeftButton and self._last_drag_pos is not None
+ is_dragging = event.buttons() & QC.Qt.MouseButton.LeftButton and self._last_drag_pos is not None
has_moved = event_pos != self._last_motion_pos
if is_dragging:
@@ -2417,7 +2417,7 @@ def mouseMoveEvent( self, event ):
if not mouse_currently_shown:
- self.setCursor( QG.QCursor( QC.Qt.ArrowCursor ) )
+ self.setCursor( QG.QCursor( QC.Qt.CursorShape.ArrowCursor ) )
self._RestartCursorHideWait()
@@ -2426,7 +2426,7 @@ def mouseMoveEvent( self, event ):
if mouse_currently_shown:
- self.setCursor( QG.QCursor( QC.Qt.BlankCursor ) )
+ self.setCursor( QG.QCursor( QC.Qt.CursorShape.BlankCursor ) )
diff --git a/hydrus/client/gui/canvas/ClientGUICanvasFrame.py b/hydrus/client/gui/canvas/ClientGUICanvasFrame.py
index 36d7207aa..9ee865cab 100644
--- a/hydrus/client/gui/canvas/ClientGUICanvasFrame.py
+++ b/hydrus/client/gui/canvas/ClientGUICanvasFrame.py
@@ -176,13 +176,13 @@ def SetCanvas( self, canvas_window: ClientGUICanvas.CanvasWithDetails ):
self.show()
# just to reinforce, as Qt sometimes sets none focus for this window until it goes off and back on
- self._canvas_window.setFocus( QC.Qt.OtherFocusReason )
+ self._canvas_window.setFocus( QC.Qt.FocusReason.OtherFocusReason )
def TakeFocusForUser( self ):
self.activateWindow()
- self._canvas_window.setFocus( QC.Qt.OtherFocusReason )
+ self._canvas_window.setFocus( QC.Qt.FocusReason.OtherFocusReason )
diff --git a/hydrus/client/gui/canvas/ClientGUICanvasHoverFrames.py b/hydrus/client/gui/canvas/ClientGUICanvasHoverFrames.py
index d000ac855..3ec26a1ab 100644
--- a/hydrus/client/gui/canvas/ClientGUICanvasHoverFrames.py
+++ b/hydrus/client/gui/canvas/ClientGUICanvasHoverFrames.py
@@ -362,7 +362,7 @@ def SetMedia( self, media ):
-# Note that I go setFocusPolicy( QC.Qt.TabFocus ) on all the icon buttons in the hover windows
+# Note that I go setFocusPolicy( QC.Qt.FocusPolicy.TabFocus ) on all the icon buttons in the hover windows
# this means that a user can click a button and not give it focus, allowing the arrow keys and space to still propagate up to the main canvas
TOP_HOVER_PROPORTION = 0.6
@@ -380,7 +380,7 @@ class CanvasHoverFrame( QW.QFrame ):
def __init__( self, parent: QW.QWidget, my_canvas, canvas_key ):
# TODO: Clean up old references to window stuff, decide on lower/hide/show/raise options
- # OK, so I converted these from this "self.setWindowFlags( QC.Qt.FramelessWindowHint | QC.Qt.Tool )" to normal raise/lower widgets embedded in the canvas
+ # OK, so I converted these from this "self.setWindowFlags( QC.Qt.WindowType.FramelessWindowHint | QC.Qt.Tool )" to normal raise/lower widgets embedded in the canvas
# this took some hacks, and there is still a bunch of focus and TLW checking code going on here that needs to be cleaned up
# note I tried to have them just lower rather than hide and it looked really stupid, so that thought is dead for the current moment. atm I just want to do the same thing as before with no graphics errors
@@ -405,7 +405,7 @@ def __init__( self, parent: QW.QWidget, my_canvas, canvas_key ):
self.hide()
self._is_currently_up = False
- self.setCursor( QG.QCursor( QC.Qt.ArrowCursor ) )
+ self.setCursor( QG.QCursor( QC.Qt.CursorShape.ArrowCursor ) )
self._position_initialised_since_last_media = False
@@ -430,7 +430,7 @@ def _LowerHover( self ):
self._is_currently_up = False
- self.parentWidget().setFocus( QC.Qt.OtherFocusReason )
+ self.parentWidget().setFocus( QC.Qt.FocusReason.OtherFocusReason )
@@ -491,7 +491,7 @@ def eventFilter( self, watched, event ):
try:
- if event.type() == QC.QEvent.Resize:
+ if event.type() == QC.QEvent.Type.Resize:
self._SizeAndPosition()
@@ -725,8 +725,8 @@ def __init__( self, parent, my_canvas, canvas_key ):
self._title_text = ClientGUICommon.BetterStaticText( self, 'title', ellipsize_end = True )
self._info_text = ClientGUICommon.BetterStaticText( self, 'info', ellipsize_end = True )
- self._title_text.setAlignment( QC.Qt.AlignHCenter | QC.Qt.AlignVCenter )
- self._info_text.setAlignment( QC.Qt.AlignHCenter | QC.Qt.AlignVCenter )
+ self._title_text.setAlignment( QC.Qt.AlignmentFlag.AlignHCenter | QC.Qt.AlignmentFlag.AlignVCenter )
+ self._info_text.setAlignment( QC.Qt.AlignmentFlag.AlignHCenter | QC.Qt.AlignmentFlag.AlignVCenter )
self._PopulateLeftButtons()
self._PopulateCenterButtons()
@@ -794,19 +794,19 @@ def _GetIdealSizeAndPosition( self ):
def _PopulateCenterButtons( self ):
self._archive_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().archive, self._Archive )
- self._archive_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._archive_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
self._trash_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().delete, CG.client_controller.pub, 'canvas_delete', self._canvas_key )
self._trash_button.setToolTip( ClientGUIFunctions.WrapToolTip( 'send to trash' ) )
- self._trash_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._trash_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
self._delete_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().trash_delete, CG.client_controller.pub, 'canvas_delete', self._canvas_key )
self._delete_button.setToolTip( ClientGUIFunctions.WrapToolTip( 'delete completely' ) )
- self._delete_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._delete_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
self._undelete_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().undelete, CG.client_controller.pub, 'canvas_undelete', self._canvas_key )
self._undelete_button.setToolTip( ClientGUIFunctions.WrapToolTip( 'undelete' ) )
- self._undelete_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._undelete_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
QP.AddToLayout( self._top_center_hbox, self._archive_button, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( self._top_center_hbox, self._trash_button, CC.FLAGS_CENTER_PERPENDICULAR )
@@ -827,15 +827,15 @@ def _PopulateRightButtons( self ):
zoom_in = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().zoom_in, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ZOOM_IN_VIEWER_CENTER ) )
zoom_in.SetToolTipWithShortcuts( 'zoom in', CAC.SIMPLE_ZOOM_IN )
- zoom_in.setFocusPolicy( QC.Qt.TabFocus )
+ zoom_in.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
zoom_out = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().zoom_out, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ZOOM_OUT_VIEWER_CENTER ) )
zoom_out.SetToolTipWithShortcuts( 'zoom out', CAC.SIMPLE_ZOOM_OUT )
- zoom_out.setFocusPolicy( QC.Qt.TabFocus )
+ zoom_out.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
zoom_switch = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().zoom_switch, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SWITCH_BETWEEN_100_PERCENT_AND_CANVAS_ZOOM_VIEWER_CENTER ) )
zoom_switch.SetToolTipWithShortcuts( 'zoom switch', CAC.SIMPLE_SWITCH_BETWEEN_100_PERCENT_AND_CANVAS_ZOOM )
- zoom_switch.setFocusPolicy( QC.Qt.TabFocus )
+ zoom_switch.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
self._volume_control = ClientGUIMediaControls.VolumeControl( self, CC.CANVAS_MEDIA_VIEWER )
@@ -846,18 +846,18 @@ def _PopulateRightButtons( self ):
shortcuts = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().keyboard, self._ShowShortcutMenu )
shortcuts.setToolTip( ClientGUIFunctions.WrapToolTip( 'shortcuts' ) )
- shortcuts.setFocusPolicy( QC.Qt.TabFocus )
+ shortcuts.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
self._show_embedded_metadata_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().listctrl, self._ShowFileEmbeddedMetadata )
- self._show_embedded_metadata_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._show_embedded_metadata_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
view_options = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().eye, self._ShowViewOptionsMenu )
view_options.setToolTip( ClientGUIFunctions.WrapToolTip( 'view options' ) )
- view_options.setFocusPolicy( QC.Qt.TabFocus )
+ view_options.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
fullscreen_switch = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().fullscreen_switch, CG.client_controller.pub, 'canvas_fullscreen_switch', self._canvas_key )
fullscreen_switch.setToolTip( ClientGUIFunctions.WrapToolTip( 'fullscreen switch' ) )
- fullscreen_switch.setFocusPolicy( QC.Qt.TabFocus )
+ fullscreen_switch.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
if HC.PLATFORM_MACOS:
@@ -866,18 +866,18 @@ def _PopulateRightButtons( self ):
open_externally = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().open_externally, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM ) )
open_externally.SetToolTipWithShortcuts( 'open externally', CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM )
- open_externally.setFocusPolicy( QC.Qt.TabFocus )
+ open_externally.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
drag_button = QW.QPushButton( self )
drag_button.setIcon( QG.QIcon( CC.global_pixmaps().drag ) )
drag_button.setIconSize( CC.global_pixmaps().drag.size() )
drag_button.setToolTip( ClientGUIFunctions.WrapToolTip( 'drag from here to export file' ) )
drag_button.pressed.connect( self.DragButtonHit )
- drag_button.setFocusPolicy( QC.Qt.TabFocus )
+ drag_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
close = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().stop, CG.client_controller.pub, 'canvas_close', self._canvas_key )
close.setToolTip( ClientGUIFunctions.WrapToolTip( 'close' ) )
- close.setFocusPolicy( QC.Qt.TabFocus )
+ close.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
QP.AddToLayout( self._top_right_hbox, self._zoom_text, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( self._top_right_hbox, zoom_in, CC.FLAGS_CENTER_PERPENDICULAR )
@@ -953,7 +953,7 @@ def _ResetButtons( self ):
if has_human_readable_embedded_metadata:
- tt_components.append( 'non-exif human-readable embedded metadata' )
+ tt_components.append( 'non-exif embedded metadata' )
if has_extra_rows:
@@ -1121,11 +1121,11 @@ def DragButtonHit( self ):
media = [ self._current_media ]
- alt_down = QW.QApplication.keyboardModifiers() & QC.Qt.AltModifier
+ alt_down = QW.QApplication.keyboardModifiers() & QC.Qt.KeyboardModifier.AltModifier
result = ClientGUIDragDrop.DoFileExportDragDrop( self, page_key, media, alt_down )
- if result != QC.Qt.IgnoreAction:
+ if result != QC.Qt.DropAction.IgnoreAction:
self.sendApplicationCommand.emit( CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_PAUSE_MEDIA ) )
@@ -1209,7 +1209,7 @@ def _PopulateLeftButtons( self ):
self._back_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().previous, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_BACK ) )
self._back_button.SetToolTipWithShortcuts( 'back', CAC.SIMPLE_ARCHIVE_DELETE_FILTER_BACK )
- self._back_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._back_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
QP.AddToLayout( self._top_left_hbox, self._back_button, CC.FLAGS_CENTER_PERPENDICULAR )
@@ -1217,7 +1217,7 @@ def _PopulateLeftButtons( self ):
self._skip_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().next_bmp, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_SKIP ) )
self._skip_button.SetToolTipWithShortcuts( 'skip', CAC.SIMPLE_ARCHIVE_DELETE_FILTER_SKIP )
- self._skip_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._skip_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
QP.AddToLayout( self._top_left_hbox, self._skip_button, CC.FLAGS_CENTER_PERPENDICULAR )
@@ -1234,13 +1234,13 @@ def _PopulateLeftButtons( self ):
self._previous_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().previous, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_PREVIOUS ) )
self._previous_button.SetToolTipWithShortcuts( 'previous', CAC.SIMPLE_VIEW_PREVIOUS )
- self._previous_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._previous_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
self._index_text = ClientGUICommon.BetterStaticText( self, 'index' )
self._next_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().next_bmp, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_NEXT ) )
self._next_button.SetToolTipWithShortcuts( 'next', CAC.SIMPLE_VIEW_NEXT )
- self._next_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._next_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
QP.AddToLayout( self._top_left_hbox, self._previous_button, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( self._top_left_hbox, self._index_text, CC.FLAGS_CENTER_PERPENDICULAR )
@@ -1253,7 +1253,7 @@ def _PopulateLeftButtons( self ):
self._first_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().first, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_BACK ) )
self._first_button.SetToolTipWithShortcuts( 'go back a pair', CAC.SIMPLE_DUPLICATE_FILTER_BACK )
- self._first_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._first_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
QP.AddToLayout( self._top_left_hbox, self._first_button, CC.FLAGS_CENTER_PERPENDICULAR )
@@ -1261,7 +1261,7 @@ def _PopulateLeftButtons( self ):
self._last_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().last, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_SKIP ) )
self._last_button.SetToolTipWithShortcuts( 'show a different pair', CAC.SIMPLE_DUPLICATE_FILTER_SKIP )
- self._last_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._last_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
QP.AddToLayout( self._top_left_hbox, self._last_button, CC.FLAGS_CENTER_PERPENDICULAR )
@@ -1272,7 +1272,7 @@ def _PopulateLeftButtons( self ):
self._first_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().first, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_FIRST ) )
self._first_button.SetToolTipWithShortcuts( 'first', CAC.SIMPLE_VIEW_FIRST )
- self._first_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._first_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
QP.AddToLayout( self._top_left_hbox, self._first_button, CC.FLAGS_CENTER_PERPENDICULAR )
@@ -1280,7 +1280,7 @@ def _PopulateLeftButtons( self ):
self._last_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().last, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_LAST ) )
self._last_button.SetToolTipWithShortcuts( 'last', CAC.SIMPLE_VIEW_LAST )
- self._last_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._last_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
QP.AddToLayout( self._top_left_hbox, self._last_button, CC.FLAGS_CENTER_PERPENDICULAR )
@@ -1313,7 +1313,7 @@ def __init__( self, parent, my_canvas, top_hover: CanvasHoverFrameTop, canvas_ke
self._location_strings = ClientGUICommon.BetterStaticText( self, '' )
- self._location_strings.setAlignment( QC.Qt.AlignRight | QC.Qt.AlignVCenter )
+ self._location_strings.setAlignment( QC.Qt.AlignmentFlag.AlignRight | QC.Qt.AlignmentFlag.AlignVCenter )
# urls
@@ -1360,7 +1360,7 @@ def __init__( self, parent, my_canvas, top_hover: CanvasHoverFrameTop, canvas_ke
QP.AddToLayout( vbox, control, CC.FLAGS_NONE )
- vbox.setAlignment( control, QC.Qt.AlignRight )
+ vbox.setAlignment( control, QC.Qt.AlignmentFlag.AlignRight )
# now incdec
@@ -1531,7 +1531,7 @@ def _ResetData( self ):
link = ClientGUICommon.BetterHyperLink( self, display_string, url )
- link.setAlignment( QC.Qt.AlignRight )
+ link.setAlignment( QC.Qt.AlignmentFlag.AlignRight )
QP.AddToLayout( self._urls_vbox, link, CC.FLAGS_EXPAND_PERPENDICULAR )
@@ -1595,7 +1595,7 @@ def __init__( self, parent: "CanvasHoverFrameRightNotes", name: str, note: str,
self._note_name = ClientGUICommon.BetterStaticText( self, label = name )
- self._note_name.setAlignment( QC.Qt.AlignHCenter )
+ self._note_name.setAlignment( QC.Qt.AlignmentFlag.AlignHCenter )
self._note_name.setWordWrap( True )
font = QG.QFont( self._note_name.font() )
@@ -1606,7 +1606,7 @@ def __init__( self, parent: "CanvasHoverFrameRightNotes", name: str, note: str,
self._note_text = ClientGUICommon.BetterStaticText( self, label = note )
- self._note_text.setAlignment( QC.Qt.AlignJustify )
+ self._note_text.setAlignment( QC.Qt.AlignmentFlag.AlignJustify )
self._note_text.setWordWrap( True )
vbox = QP.VBoxLayout( margin = 0 )
@@ -1626,9 +1626,9 @@ def eventFilter( self, watched, event ):
try:
- if event.type() == QC.QEvent.MouseButtonPress:
+ if event.type() == QC.QEvent.Type.MouseButtonPress:
- if event.button() == QC.Qt.LeftButton:
+ if event.button() == QC.Qt.MouseButton.LeftButton:
self.editNote.emit( self._name )
@@ -1915,11 +1915,11 @@ def __init__( self, parent: QW.QWidget, my_canvas: QW.QWidget, canvas_key: bytes
self._show_in_a_page_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().fullscreen_switch, self.showPairInPage.emit )
self._show_in_a_page_button.setToolTip( ClientGUIFunctions.WrapToolTip( 'send pair to the duplicates media page, for later processing' ) )
- self._show_in_a_page_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._show_in_a_page_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
self._trash_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().delete, CG.client_controller.pub, 'canvas_delete', self._canvas_key )
self._trash_button.setToolTip( ClientGUIFunctions.WrapToolTip( 'send to trash' ) )
- self._trash_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._trash_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
menu_items = []
@@ -1935,25 +1935,25 @@ def __init__( self, parent: QW.QWidget, my_canvas: QW.QWidget, canvas_key: bytes
menu_items.append( ( 'normal', 'edit background lighten/darken switch intensity', 'edit how much the background will brighten or darken as you switch between the pair', self._EditBackgroundSwitchIntensity ) )
self._cog_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().cog, menu_items )
- self._cog_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._cog_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
close_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().stop, CG.client_controller.pub, 'canvas_close', self._canvas_key )
close_button.setToolTip( ClientGUIFunctions.WrapToolTip( 'close filter' ) )
- close_button.setFocusPolicy( QC.Qt.TabFocus )
+ close_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
self._back_a_pair = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().first, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_BACK ) )
self._back_a_pair.SetToolTipWithShortcuts( 'go back a pair', CAC.SIMPLE_DUPLICATE_FILTER_BACK )
- self._back_a_pair.setFocusPolicy( QC.Qt.TabFocus )
+ self._back_a_pair.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
self._index_text = ClientGUICommon.BetterStaticText( self, 'index' )
self._next_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().pair, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_NEXT ) )
self._next_button.SetToolTipWithShortcuts( 'next', CAC.SIMPLE_VIEW_NEXT )
- self._next_button.setFocusPolicy( QC.Qt.TabFocus )
+ self._next_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
self._skip_a_pair = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().last, self.sendApplicationCommand.emit, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_SKIP ) )
self._skip_a_pair.SetToolTipWithShortcuts( 'show a different pair', CAC.SIMPLE_DUPLICATE_FILTER_SKIP )
- self._skip_a_pair.setFocusPolicy( QC.Qt.TabFocus )
+ self._skip_a_pair.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
command_button_vbox = QP.VBoxLayout()
@@ -1984,7 +1984,7 @@ def __init__( self, parent: QW.QWidget, my_canvas: QW.QWidget, canvas_key: bytes
for ( label, tooltip, command ) in dupe_commands:
command_button = ClientGUICommon.BetterButton( button_panel, label, self.sendApplicationCommand.emit, command )
- command_button.setFocusPolicy( QC.Qt.TabFocus )
+ command_button.setFocusPolicy( QC.Qt.FocusPolicy.TabFocus )
command_button.SetToolTipWithShortcuts( tooltip, command.GetSimpleAction() )
diff --git a/hydrus/client/gui/canvas/ClientGUICanvasMedia.py b/hydrus/client/gui/canvas/ClientGUICanvasMedia.py
index 419191de9..0f5741b9f 100644
--- a/hydrus/client/gui/canvas/ClientGUICanvasMedia.py
+++ b/hydrus/client/gui/canvas/ClientGUICanvasMedia.py
@@ -516,7 +516,7 @@ def _DrawABlankFrame( self, painter ):
painter.setBrush( QG.QBrush( dark_grey ) )
- painter.setPen( QG.QPen( QC.Qt.NoPen ) )
+ painter.setPen( QG.QPen( QC.Qt.PenStyle.NoPen ) )
for y_index in range( num_rows ):
@@ -918,7 +918,7 @@ def __init__( self, parent ):
self.setObjectName( 'HydrusAnimationBar' )
- self.setCursor( QG.QCursor( QC.Qt.ArrowCursor ) )
+ self.setCursor( QG.QCursor( QC.Qt.CursorShape.ArrowCursor ) )
self.setSizePolicy( QW.QSizePolicy.Policy.Fixed, QW.QSizePolicy.Policy.Fixed )
@@ -1126,7 +1126,7 @@ def _Redraw( self, painter ):
#
- painter.setBrush( QC.Qt.NoBrush )
+ painter.setBrush( QC.Qt.BrushStyle.NoBrush )
painter.setPen( QG.QPen( self._qss_colours[ 'hab_border' ] ) )
@@ -1190,7 +1190,7 @@ def mouseMoveEvent( self, event ):
if self._currently_in_a_drag:
- if event.buttons() == QC.Qt.NoButton:
+ if event.buttons() == QC.Qt.MouseButton.NoButton:
self._currently_in_a_drag = False
@@ -1388,7 +1388,7 @@ def __init__( self, parent, canvas_type, background_colour_generator, additional
# yes :^(
# try again with more layout tech on the full canvas
- self.setAttribute( QC.Qt.WA_OpaquePaintEvent, True )
+ self.setAttribute( QC.Qt.WidgetAttribute.WA_OpaquePaintEvent, True )
self._background_colour_generator = background_colour_generator
@@ -1430,7 +1430,7 @@ def __init__( self, parent, canvas_type, background_colour_generator, additional
self._animation_bar = AnimationBar( self._controls_bar )
self._volume_control = ClientGUIMediaControls.VolumeControl( self._controls_bar, self._canvas_type, direction = 'up' )
- self._volume_control.setCursor( QC.Qt.ArrowCursor )
+ self._volume_control.setCursor( QC.Qt.CursorShape.ArrowCursor )
#
@@ -2713,7 +2713,7 @@ def __init__( self, parent, background_colour_generator ):
self._thumbnail_qt_pixmap = None
- self.setCursor( QG.QCursor( QC.Qt.PointingHandCursor ) )
+ self.setCursor( QG.QCursor( QC.Qt.CursorShape.PointingHandCursor ) )
CG.client_controller.sub( self, 'update', 'notify_new_colourset' )
@@ -2778,7 +2778,7 @@ def _Redraw( self, painter ):
painter.setPen( QG.QPen( QG.QPalette().color( QG.QPalette.Shadow ) ) )
- painter.setBrush( QC.Qt.NoBrush )
+ painter.setBrush( QC.Qt.BrushStyle.NoBrush )
painter.drawRect( 0, 0, my_width, my_height )
@@ -2866,20 +2866,20 @@ def __init__( self, parent, media ):
button = QW.QPushButton( 'open {} externally'.format( m_text ), self )
- button.setFocusPolicy( QC.Qt.NoFocus )
+ button.setFocusPolicy( QC.Qt.FocusPolicy.NoFocus )
QP.AddToLayout( vbox, button, CC.FLAGS_EXPAND_BOTH_WAYS )
self.setLayout( vbox )
- self.setCursor( QG.QCursor( QC.Qt.PointingHandCursor ) )
+ self.setCursor( QG.QCursor( QC.Qt.CursorShape.PointingHandCursor ) )
button.clicked.connect( self.LaunchFile )
def mousePressEvent( self, event ):
- if not ( event.modifiers() & ( QC.Qt.ShiftModifier | QC.Qt.ControlModifier | QC.Qt.AltModifier ) ) and event.button() == QC.Qt.LeftButton:
+ if not ( event.modifiers() & ( QC.Qt.KeyboardModifier.ShiftModifier | QC.Qt.KeyboardModifier.ControlModifier | QC.Qt.KeyboardModifier.AltModifier ) ) and event.button() == QC.Qt.MouseButton.LeftButton:
self.LaunchFile()
@@ -3223,7 +3223,7 @@ def __init__( self, parent, canvas_type, background_colour_generator ):
if HC.PLATFORM_MACOS and not HG.macos_antiflicker_test:
- self.setAttribute( QC.Qt.WA_OpaquePaintEvent, True )
+ self.setAttribute( QC.Qt.WidgetAttribute.WA_OpaquePaintEvent, True )
# pass up un-button-pressed mouse moves to parent, which wants to do cursor show/hide
@@ -3368,7 +3368,7 @@ def _DrawBackground( self, painter, topLeftOffset = None ):
painter.setBrush( QG.QBrush( dark_grey ) )
- painter.setPen( QG.QPen( QC.Qt.NoPen ) )
+ painter.setPen( QG.QPen( QC.Qt.PenStyle.NoPen ) )
for y_index in range( num_rows ):
@@ -3418,7 +3418,7 @@ def _DrawTile( self, tile_coordinate ):
if HG.canvas_tile_outline_mode:
painter.setPen( QG.QPen( QG.QColor( 0, 127, 255 ) ) )
- painter.setBrush( QC.Qt.NoBrush )
+ painter.setBrush( QC.Qt.BrushStyle.NoBrush )
painter.drawRect( tile_pixmap.rect() )
@@ -3581,7 +3581,7 @@ def paintEvent( self, event ):
raw_pos_f = QC.QPointF( raw_pos )
- device_pos_f = raw_pos_f / my_dpr
+ device_pos_f = typing.cast( QC.QPointF, raw_pos_f / my_dpr )
tile.setDevicePixelRatio( my_dpr )
diff --git a/hydrus/client/gui/canvas/ClientGUIMPV.py b/hydrus/client/gui/canvas/ClientGUIMPV.py
index 1bd807424..1c72d8242 100644
--- a/hydrus/client/gui/canvas/ClientGUIMPV.py
+++ b/hydrus/client/gui/canvas/ClientGUIMPV.py
@@ -216,8 +216,8 @@ def __init__( self, parent ):
LOCALE_IS_SET = True
- self.setAttribute( QC.Qt.WA_DontCreateNativeAncestors )
- self.setAttribute( QC.Qt.WA_NativeWindow )
+ self.setAttribute( QC.Qt.WidgetAttribute.WA_DontCreateNativeAncestors )
+ self.setAttribute( QC.Qt.WidgetAttribute.WA_NativeWindow )
# loglevels: fatal, error, debug
loglevel = 'debug' if HG.mpv_report_mode else 'error'
@@ -250,7 +250,7 @@ def __init__( self, parent ):
# pass up un-button-pressed mouse moves to parent, which wants to do cursor show/hide
self.setMouseTracking( True )
- #self.setFocusPolicy(QC.Qt.StrongFocus)#Needed to get key events
+ #self.setFocusPolicy(QC.Qt.FocusPolicy.StrongFocus)#Needed to get key events
self._player.input_cursor = False#Disable mpv mouse move/click event capture
self._player.input_vo_keyboard = False#Disable mpv key event capture, might also need to set input_x11_keyboard
diff --git a/hydrus/client/gui/lists/ClientGUIListBook.py b/hydrus/client/gui/lists/ClientGUIListBook.py
index 741525042..176c6c2d2 100644
--- a/hydrus/client/gui/lists/ClientGUIListBook.py
+++ b/hydrus/client/gui/lists/ClientGUIListBook.py
@@ -135,7 +135,7 @@ def SelectPage( self, page: QW.QWidget ):
for list_item in [ self._page_list.item( i ) for i in range( self._page_list.count() ) ]:
- if list_item.data( QC.Qt.UserRole ) == page:
+ if list_item.data( QC.Qt.ItemDataRole.UserRole ) == page:
self._page_list.setCurrentItem( list_item )
@@ -163,6 +163,6 @@ def tabText( self, index ):
def widget( self, index: int ):
- return self._page_list.item( index ).data( QC.Qt.UserRole )
+ return self._page_list.item( index ).data( QC.Qt.ItemDataRole.UserRole )
diff --git a/hydrus/client/gui/lists/ClientGUIListBoxes.py b/hydrus/client/gui/lists/ClientGUIListBoxes.py
index c27afda7d..c501f93d8 100644
--- a/hydrus/client/gui/lists/ClientGUIListBoxes.py
+++ b/hydrus/client/gui/lists/ClientGUIListBoxes.py
@@ -104,7 +104,7 @@ def _GetListWidgetItems( self, only_selected = False ) -> typing.Collection[ QW.
def _GetRowData( self, list_widget_item: QW.QListWidgetItem ):
- return list_widget_item.data( QC.Qt.UserRole )
+ return list_widget_item.data( QC.Qt.ItemDataRole.UserRole )
def _GetSelectedIndices( self ):
@@ -138,7 +138,7 @@ def Append( self, text: str, data: object, select = False ):
item = QW.QListWidgetItem()
item.setText( text )
- item.setData( QC.Qt.UserRole, data )
+ item.setData( QC.Qt.ItemDataRole.UserRole, data )
self.addItem( item )
@@ -205,7 +205,7 @@ def keyPressEvent( self, event: QG.QKeyEvent ):
self._delete_callable()
- elif event.modifiers() & QC.Qt.ControlModifier and event.key() in ( QC.Qt.Key_C, QC.Qt.Key_Insert ):
+ elif event.modifiers() & QC.Qt.KeyboardModifier.ControlModifier and event.key() in ( QC.Qt.Key.Key_C, QC.Qt.Key.Key_Insert ):
event.accept()
@@ -215,7 +215,7 @@ def keyPressEvent( self, event: QG.QKeyEvent ):
for list_widget_item in self.selectedItems():
- user_role_data = list_widget_item.data( QC.Qt.UserRole )
+ user_role_data = list_widget_item.data( QC.Qt.ItemDataRole.UserRole )
if isinstance( user_role_data, str ):
@@ -456,7 +456,7 @@ def _Edit( self ):
for list_widget_item in self._listbox.selectedItems():
- data = list_widget_item.data( QC.Qt.UserRole )
+ data = list_widget_item.data( QC.Qt.ItemDataRole.UserRole )
try:
@@ -472,7 +472,7 @@ def _Edit( self ):
pretty_new_data = self._data_to_pretty_callable( new_data )
list_widget_item.setText( pretty_new_data )
- list_widget_item.setData( QC.Qt.UserRole, new_data )
+ list_widget_item.setData( QC.Qt.ItemDataRole.UserRole, new_data )
self.listBoxChanged.emit()
@@ -1011,7 +1011,7 @@ def _Edit( self ):
for list_widget_item in self._listbox.selectedItems():
- data = list_widget_item.data( QC.Qt.UserRole )
+ data = list_widget_item.data( QC.Qt.ItemDataRole.UserRole )
try:
@@ -1025,7 +1025,7 @@ def _Edit( self ):
pretty_new_data = self._data_to_pretty_callable( new_data )
list_widget_item.setText( pretty_new_data )
- list_widget_item.setData( QC.Qt.UserRole, new_data )
+ list_widget_item.setData( QC.Qt.ItemDataRole.UserRole, new_data )
self.listBoxChanged.emit()
@@ -1372,8 +1372,8 @@ def __init__( self, parent: QW.QWidget, terms_may_have_sibling_or_parent_info: b
super().__init__( parent )
self.setFrameStyle( QW.QFrame.Shape.Panel | QW.QFrame.Shadow.Sunken )
- self.setHorizontalScrollBarPolicy( QC.Qt.ScrollBarAlwaysOff )
- self.setVerticalScrollBarPolicy( QC.Qt.ScrollBarAsNeeded )
+ self.setHorizontalScrollBarPolicy( QC.Qt.ScrollBarPolicy.ScrollBarAlwaysOff )
+ self.setVerticalScrollBarPolicy( QC.Qt.ScrollBarPolicy.ScrollBarAsNeeded )
self.setWidget( ListBox._InnerWidget( self ) )
self.setWidgetResizable( True )
@@ -1671,7 +1671,7 @@ def _GetPositionalIndexUnderMouse( self, mouse_event ):
y = mouse_event.position().toPoint().y()
- if mouse_event.type() == QC.QEvent.MouseMove:
+ if mouse_event.type() == QC.QEvent.Type.MouseMove:
visible_rect = QP.ScrollAreaVisibleRect( self )
@@ -1788,8 +1788,8 @@ def _HandleClick( self, event ):
logical_index = self._GetLogicalIndexUnderMouse( event )
- shift = event.modifiers() & QC.Qt.ShiftModifier
- ctrl = event.modifiers() & QC.Qt.ControlModifier
+ shift = event.modifiers() & QC.Qt.KeyboardModifier.ShiftModifier
+ ctrl = event.modifiers() & QC.Qt.KeyboardModifier.ControlModifier
self._Hit( shift, ctrl, logical_index )
@@ -2390,8 +2390,8 @@ def keyPressEvent( self, event ):
return
- shift = event.modifiers() & QC.Qt.ShiftModifier
- ctrl = event.modifiers() & QC.Qt.ControlModifier
+ shift = event.modifiers() & QC.Qt.KeyboardModifier.ShiftModifier
+ ctrl = event.modifiers() & QC.Qt.KeyboardModifier.ControlModifier
key_code = event.key()
@@ -2401,7 +2401,7 @@ def keyPressEvent( self, event ):
self._DeleteActivate()
- elif has_focus and key_code == QC.Qt.Key_Escape:
+ elif has_focus and key_code == QC.Qt.Key.Key_Escape:
if len( self._selected_terms ) > 0:
@@ -2412,7 +2412,7 @@ def keyPressEvent( self, event ):
event.ignore()
- elif key_code in ( QC.Qt.Key_Enter, QC.Qt.Key_Return ):
+ elif key_code in ( QC.Qt.Key.Key_Enter, QC.Qt.Key.Key_Return ):
self._ActivateFromKeyboard( ctrl, shift )
@@ -2422,7 +2422,7 @@ def keyPressEvent( self, event ):
self._SelectAll()
- elif ctrl and key_code in ( ord( 'C' ), ord( 'c' ), QC.Qt.Key_Insert ):
+ elif ctrl and key_code in ( ord( 'C' ), ord( 'c' ), QC.Qt.Key.Key_Insert ):
self._CopySelectedTexts()
@@ -2432,11 +2432,11 @@ def keyPressEvent( self, event ):
if len( self._ordered_terms ) > 1:
- if key_code in ( QC.Qt.Key_Home, ):
+ if key_code in ( QC.Qt.Key.Key_Home, ):
hit_logical_index = 0
- elif key_code in ( QC.Qt.Key_End, ):
+ elif key_code in ( QC.Qt.Key.Key_End, ):
hit_logical_index = len( self._ordered_terms ) - 1
@@ -2456,19 +2456,19 @@ def keyPressEvent( self, event ):
hit_logical_index = ( self._last_hit_logical_index + 1 ) % len( self._ordered_terms )
- elif key_code in ( QC.Qt.Key_Up, ):
+ elif key_code in ( QC.Qt.Key.Key_Up, ):
hit_logical_index = ( self._last_hit_logical_index - 1 ) % len( self._ordered_terms )
- elif key_code in ( QC.Qt.Key_Down, ):
+ elif key_code in ( QC.Qt.Key.Key_Down, ):
hit_logical_index = ( self._last_hit_logical_index + 1 ) % len( self._ordered_terms )
- elif key_code in ( QC.Qt.Key_PageUp, QC.Qt.Key_PageDown ):
+ elif key_code in ( QC.Qt.Key.Key_PageUp, QC.Qt.Key.Key_PageDown ):
last_hit_positional_index = self._GetPositionalIndexFromLogicalIndex( self._last_hit_logical_index )
- if key_code == QC.Qt.Key_PageUp:
+ if key_code == QC.Qt.Key.Key_PageUp:
hit_positional_index = max( 0, last_hit_positional_index - self._num_rows_per_page )
@@ -2502,7 +2502,7 @@ def eventFilter( self, watched, event ):
if watched == self.widget() and self.isEnabled():
- if event.type() == QC.QEvent.MouseButtonPress:
+ if event.type() == QC.QEvent.Type.MouseButtonPress:
self._HandleClick( event )
@@ -2510,18 +2510,18 @@ def eventFilter( self, watched, event ):
return True
- elif event.type() == QC.QEvent.MouseButtonRelease:
+ elif event.type() == QC.QEvent.Type.MouseButtonRelease:
self._in_drag = False
event.ignore()
- elif event.type() == QC.QEvent.MouseButtonDblClick:
+ elif event.type() == QC.QEvent.Type.MouseButtonDblClick:
- if event.button() == QC.Qt.LeftButton:
+ if event.button() == QC.Qt.MouseButton.LeftButton:
- ctrl_down = event.modifiers() & QC.Qt.ControlModifier
- shift_down = event.modifiers() & QC.Qt.ShiftModifier
+ ctrl_down = event.modifiers() & QC.Qt.KeyboardModifier.ControlModifier
+ shift_down = event.modifiers() & QC.Qt.KeyboardModifier.ShiftModifier
if ctrl_down:
@@ -2565,7 +2565,7 @@ def eventFilter( self, watched, event ):
def mouseMoveEvent( self, event ):
- is_dragging = event.buttons() & QC.Qt.LeftButton
+ is_dragging = event.buttons() & QC.Qt.MouseButton.LeftButton
if is_dragging:
@@ -3149,9 +3149,9 @@ def eventFilter( self, watched, event ):
if watched == self.widget():
- if event.type() == QC.QEvent.MouseButtonPress:
+ if event.type() == QC.QEvent.Type.MouseButtonPress:
- if event.button() == QC.Qt.MiddleButton:
+ if event.button() == QC.Qt.MouseButton.MiddleButton:
self._HandleClick( event )
@@ -3161,7 +3161,7 @@ def eventFilter( self, watched, event ):
if len( predicates ) > 0:
- shift_down = event.modifiers() & QC.Qt.ShiftModifier
+ shift_down = event.modifiers() & QC.Qt.KeyboardModifier.ShiftModifier
if shift_down and or_predicate is not None:
@@ -3176,9 +3176,9 @@ def eventFilter( self, watched, event ):
return True
- elif event.type() == QC.QEvent.MouseButtonRelease:
+ elif event.type() == QC.QEvent.Type.MouseButtonRelease:
- if event.button() == QC.Qt.RightButton:
+ if event.button() == QC.Qt.MouseButton.RightButton:
self.ShowMenu()
diff --git a/hydrus/client/gui/lists/ClientGUIListCtrl.py b/hydrus/client/gui/lists/ClientGUIListCtrl.py
index 33eb54e54..0111234a9 100644
--- a/hydrus/client/gui/lists/ClientGUIListCtrl.py
+++ b/hydrus/client/gui/lists/ClientGUIListCtrl.py
@@ -118,14 +118,14 @@ def columnCount( self, parent = QC.QModelIndex() ):
return self._column_list_status.GetColumnCount()
- def data( self, index: QC.QModelIndex, role = QC.Qt.DisplayRole ):
+ def data( self, index: QC.QModelIndex, role = QC.Qt.ItemDataRole.DisplayRole ):
if not index.isValid():
return None
- if role in ( QC.Qt.DisplayRole, QC.Qt.ToolTipRole ):
+ if role in ( QC.Qt.ItemDataRole.DisplayRole, QC.Qt.ItemDataRole.ToolTipRole ):
column_type = self._ConvertCurrentColumnIntToColumnType( index.column() )
@@ -145,7 +145,7 @@ def data( self, index: QC.QModelIndex, role = QC.Qt.DisplayRole ):
text = self._data_to_display_tuples[ data ][ column_logical_position ]
# TODO: might be nice to maintain an optional tooltip dict for when the getfirstlinesummary differs, so we can tooltip the whole contents
- if role == QC.Qt.ToolTipRole:
+ if role == QC.Qt.ItemDataRole.ToolTipRole:
return ClientGUIFunctions.WrapToolTip( text )
@@ -155,7 +155,7 @@ def data( self, index: QC.QModelIndex, role = QC.Qt.DisplayRole ):
- elif role == QC.Qt.UserRole:
+ elif role == QC.Qt.ItemDataRole.UserRole:
return self._indices_to_data[ index.row() ] # same data no matter the column in this system!
@@ -209,10 +209,10 @@ def flags( self, index: QC.QModelIndex ):
if not index.isValid():
- return QC.Qt.NoItemFlags
+ return QC.Qt.ItemFlag.NoItemFlags
- return QC.Qt.ItemIsEnabled | QC.Qt.ItemIsSelectable | QC.Qt.ItemNeverHasChildren
+ return QC.Qt.ItemFlag.ItemIsEnabled | QC.Qt.ItemFlag.ItemIsSelectable | QC.Qt.ItemFlag.ItemNeverHasChildren
def GetColumnListType( self ) -> int:
@@ -267,7 +267,7 @@ def HasData( self, data: object ):
return data in self._data_to_indices
- def headerData( self, section: int, orientation: QC.Qt.Orientation, role = QC.Qt.DisplayRole ):
+ def headerData( self, section: int, orientation: QC.Qt.Orientation, role = QC.Qt.ItemDataRole.DisplayRole ):
if orientation != QC.Qt.Orientation.Horizontal:
@@ -276,7 +276,7 @@ def headerData( self, section: int, orientation: QC.Qt.Orientation, role = QC.Qt
column_type = self._ConvertCurrentColumnIntToColumnType( section )
- if role in ( QC.Qt.DisplayRole, QC.Qt.ToolTipRole ):
+ if role in ( QC.Qt.ItemDataRole.DisplayRole, QC.Qt.ItemDataRole.ToolTipRole ):
if column_type in self._column_types_to_name_overrides:
@@ -289,7 +289,7 @@ def headerData( self, section: int, orientation: QC.Qt.Orientation, role = QC.Qt
return name
- elif role == QC.Qt.UserRole:
+ elif role == QC.Qt.ItemDataRole.UserRole:
return column_type
@@ -350,7 +350,7 @@ def SetData( self, datas ):
- def sort( self, column_logical_position: int, order: QC.Qt.SortOrder = QC.Qt.AscendingOrder ):
+ def sort( self, column_logical_position: int, order: QC.Qt.SortOrder = QC.Qt.SortOrder.AscendingOrder ):
self.layoutAboutToBeChanged.emit()
@@ -359,9 +359,9 @@ def sort( self, column_logical_position: int, order: QC.Qt.SortOrder = QC.Qt.Asc
# it would also allow quick filtering
# note, important, however, that you need to be careful in the view or whatever to do mapFromSource and mapToSource when handling indices since they'll jump around via the proxy's sort/filtering
- self._sort_column_type = self.headerData( column_logical_position, QC.Qt.Orientation.Horizontal, QC.Qt.UserRole )
+ self._sort_column_type = self.headerData( column_logical_position, QC.Qt.Orientation.Horizontal, QC.Qt.ItemDataRole.UserRole )
- asc = order == QC.Qt.AscendingOrder
+ asc = order == QC.Qt.SortOrder.AscendingOrder
# anything with busted None sort data gets appended to the end
no_sort_data_magic_reverso_index_failure = 1 if asc else -1
@@ -489,7 +489,7 @@ def UpdateDatas( self, datas, check_for_changed_sort_data = False ):
top_left = self.index( index, 0 )
bottom_right = self.index( index, self.columnCount() - 1 )
- self.dataChanged.emit( top_left, bottom_right, [ QC.Qt.DisplayRole, QC.Qt.ToolTipRole ] )
+ self.dataChanged.emit( top_left, bottom_right, [ QC.Qt.ItemDataRole.DisplayRole, QC.Qt.ItemDataRole.ToolTipRole ] )
return sort_data_has_changed
@@ -628,7 +628,7 @@ def __init__( self, parent, height_num_chars, model: HydrusListItemModel, use_si
self.model().rowsInserted.connect( self._PreserveSelectionRestore )
self.model().rowsRemoved.connect( self._PreserveSelectionRestore )
- self.header().setContextMenuPolicy( QC.Qt.CustomContextMenu )
+ self.header().setContextMenuPolicy( QC.Qt.ContextMenuPolicy.CustomContextMenu )
self.header().customContextMenuRequested.connect( self._ShowHeaderMenu )
CG.client_controller.CallAfterQtSafe( self, 'initialising multi-column list widths', self._InitialiseColumnWidths )
@@ -704,7 +704,7 @@ def _GenerateCurrentStatus( self ) -> ClientGUIListStatus.ColumnListStatus:
logical_index = header.logicalIndex( visual_index )
- column_type = self.model().headerData( logical_index, QC.Qt.Orientation.Horizontal, QC.Qt.UserRole )
+ column_type = self.model().headerData( logical_index, QC.Qt.Orientation.Horizontal, QC.Qt.ItemDataRole.UserRole )
width_pixels = header.sectionSize( logical_index )
shown = not header.isSectionHidden( logical_index )
@@ -738,7 +738,7 @@ def _GenerateCurrentStatus( self ) -> ClientGUIListStatus.ColumnListStatus:
order = self.header().sortIndicatorOrder()
sort_column_type = status.GetColumnTypeFromIndex( sort_column )
- sort_asc = order == QC.Qt.AscendingOrder
+ sort_asc = order == QC.Qt.SortOrder.AscendingOrder
status.SetSort( sort_column_type, sort_asc )
@@ -858,7 +858,7 @@ def AddRowsMenuCallable( self, menu_callable ):
self._rows_menu_callable = menu_callable
- self.setContextMenuPolicy( QC.Qt.CustomContextMenu )
+ self.setContextMenuPolicy( QC.Qt.ContextMenuPolicy.CustomContextMenu )
self.customContextMenuRequested.connect( self.EventShowMenu )
@@ -892,19 +892,19 @@ def keyPressEvent( self, event ):
event_processed = True
- elif key in ( QC.Qt.Key_Enter, QC.Qt.Key_Return ):
+ elif key in ( QC.Qt.Key.Key_Enter, QC.Qt.Key.Key_Return ):
self.ProcessActivateAction()
event_processed = True
- elif key in ( ord( 'A' ), ord( 'a' ) ) and modifier == QC.Qt.ControlModifier:
+ elif key in ( ord( 'A' ), ord( 'a' ) ) and modifier == QC.Qt.KeyboardModifier.ControlModifier:
self.selectAll()
event_processed = True
- elif key in ( ord( 'C' ), ord( 'c' ) ) and modifier == QC.Qt.ControlModifier:
+ elif key in ( ord( 'C' ), ord( 'c' ) ) and modifier == QC.Qt.KeyboardModifier.ControlModifier:
if self._copy_rows_callable is not None:
@@ -1044,7 +1044,7 @@ def minimumSizeHint( self ):
def mouseDoubleClickEvent( self, event: QG.QMouseEvent ):
- if event.button() == QC.Qt.LeftButton:
+ if event.button() == QC.Qt.MouseButton.LeftButton:
index = self.indexAt(event.pos()) # Get the index of the item clicked
@@ -1256,7 +1256,7 @@ def ScrollToData( self, data: object ):
self.scrollTo( model_index, hint = QW.QAbstractItemView.ScrollHint.PositionAtCenter )
- self.setFocus( QC.Qt.OtherFocusReason )
+ self.setFocus( QC.Qt.FocusReason.OtherFocusReason )
@@ -1277,7 +1277,7 @@ def SelectDatas( self, datas: typing.Iterable[ object ], deselect_others = False
model_index = model.GetModelIndexFromData( data )
- selection_model.select( model_index, QC.QItemSelectionModel.Deselect | QC.QItemSelectionModel.Rows )
+ selection_model.select( model_index, QC.QItemSelectionModel.SelectionFlag.Deselect | QC.QItemSelectionModel.SelectionFlag.Rows )
@@ -1287,7 +1287,7 @@ def SelectDatas( self, datas: typing.Iterable[ object ], deselect_others = False
model_index = model.GetModelIndexFromData( data )
- selection_model.select( model_index, QC.QItemSelectionModel.Select | QC.QItemSelectionModel.Rows )
+ selection_model.select( model_index, QC.QItemSelectionModel.SelectionFlag.Select | QC.QItemSelectionModel.SelectionFlag.Rows )
if len( selectee_datas ) > 0:
@@ -1296,7 +1296,7 @@ def SelectDatas( self, datas: typing.Iterable[ object ], deselect_others = False
model_index = model.GetModelIndexFromData( data )
- selection_model.setCurrentIndex( model_index, QC.QItemSelectionModel.Current )
+ selection_model.setCurrentIndex( model_index, QC.QItemSelectionModel.SelectionFlag.Current )
@@ -1354,7 +1354,7 @@ def Sort( self, sort_column_type = None, sort_asc = None ):
# TODO: this may want to be column_list_column_type_logical_position_lookup rather than the status lookup, depending on how we implement column order memory
# or it may simply need to navigate that question carefully if we have multiple lists open with different orders or whatever
column = self._column_list_status.GetColumnIndexFromType( sort_column_type )
- ord = QC.Qt.AscendingOrder if sort_asc else QC.Qt.DescendingOrder
+ ord = QC.Qt.SortOrder.AscendingOrder if sort_asc else QC.Qt.SortOrder.DescendingOrder
# do not call model().sort directly, it does not update the header arrow gubbins
self.sortByColumn( column, ord )
@@ -1666,6 +1666,8 @@ def _ImportFromClipboard( self ):
return
+ # TODO: add a thing here that checks for local paths and eats up PNG or JSON files depending obviously on file content
+
else:
try:
diff --git a/hydrus/client/gui/media/ClientGUIMediaControls.py b/hydrus/client/gui/media/ClientGUIMediaControls.py
index 9c2402b00..6c0de29de 100644
--- a/hydrus/client/gui/media/ClientGUIMediaControls.py
+++ b/hydrus/client/gui/media/ClientGUIMediaControls.py
@@ -94,7 +94,7 @@ def __init__( self, parent, canvas_type, direction = 'down' ):
self._global_mute = AudioMuteButton( self, AUDIO_GLOBAL )
self._global_mute.setToolTip( ClientGUIFunctions.WrapToolTip( 'Global mute/unmute' ) )
- self._global_mute.setFocusPolicy( QC.Qt.NoFocus )
+ self._global_mute.setFocusPolicy( QC.Qt.FocusPolicy.NoFocus )
vbox = QP.VBoxLayout( margin = 0, spacing = 0 )
@@ -144,9 +144,9 @@ def __init__( self, parent, canvas_type, direction = 'down' ):
self._direction = direction
- self.setWindowFlags( QC.Qt.Tool | QC.Qt.FramelessWindowHint )
+ self.setWindowFlags( QC.Qt.WindowType.Tool | QC.Qt.WindowType.FramelessWindowHint )
- self.setAttribute( QC.Qt.WA_ShowWithoutActivating )
+ self.setAttribute( QC.Qt.WidgetAttribute.WA_ShowWithoutActivating )
if self._canvas_type in CC.CANVAS_MEDIA_VIEWER_TYPES:
@@ -187,8 +187,8 @@ def __init__( self, parent, canvas_type, direction = 'down' ):
QP.AddToLayout( vbox, self._specific_mute, CC.FLAGS_CENTER )
- #vbox.setAlignment( self._volume, QC.Qt.AlignHCenter )
- #vbox.setAlignment( self._specific_mute, QC.Qt.AlignHCenter )
+ #vbox.setAlignment( self._volume, QC.Qt.AlignmentFlag.AlignHCenter )
+ #vbox.setAlignment( self._specific_mute, QC.Qt.AlignmentFlag.AlignHCenter )
self.setLayout( vbox )
@@ -282,7 +282,7 @@ def __init__( self, parent, volume_type ):
self._volume_type = volume_type
- self.setOrientation( QC.Qt.Vertical )
+ self.setOrientation( QC.Qt.Orientation.Vertical )
self.setTickInterval( 1 )
self.setTickPosition( QW.QSlider.TickPosition.TicksBothSides )
self.setRange( 0, 100 )
diff --git a/hydrus/client/gui/media/ClientGUIMediaModalActions.py b/hydrus/client/gui/media/ClientGUIMediaModalActions.py
index a7be8551d..73304d152 100644
--- a/hydrus/client/gui/media/ClientGUIMediaModalActions.py
+++ b/hydrus/client/gui/media/ClientGUIMediaModalActions.py
@@ -1130,7 +1130,7 @@ def ShowFileEmbeddedMetadata( win: QW.QWidget, media: ClientMedia.MediaSingleton
if exif_dict is None and file_text is None and len( extra_rows ) == 0:
- ClientGUIDialogsMessage.ShowWarning( win, 'Sorry, could not see any human-readable information in this file! Hydrus should have known this, so if this keeps happening, you may need to schedule a rescan of this info in file maintenance.' )
+ ClientGUIDialogsMessage.ShowWarning( win, 'Sorry, could not see any human-readable information inside this file! Hydrus should have known this, so if this keeps happening, you may need to schedule a rescan of this info in file maintenance.' )
return
diff --git a/hydrus/client/gui/metadata/ClientGUIEditTimestamps.py b/hydrus/client/gui/metadata/ClientGUIEditTimestamps.py
index 95f2cb969..cf11b1d95 100644
--- a/hydrus/client/gui/metadata/ClientGUIEditTimestamps.py
+++ b/hydrus/client/gui/metadata/ClientGUIEditTimestamps.py
@@ -51,7 +51,7 @@ def __init__( self, parent: QW.QWidget, ordered_medias: typing.List[ ClientMedia
self._file_modified_time_warning_st = ClientGUICommon.BetterStaticText( self, label = 'initialising' )
self._file_modified_time_warning_st.setObjectName( 'HydrusWarning' )
- self._file_modified_time_warning_st.setAlignment( QC.Qt.AlignCenter )
+ self._file_modified_time_warning_st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
self._file_modified_time_warning_st.setVisible( False )
domain_box = ClientGUICommon.StaticBox( self, 'web domain times' )
diff --git a/hydrus/client/gui/metadata/ClientGUITime.py b/hydrus/client/gui/metadata/ClientGUITime.py
index 91c7c6cc7..1cb8b0cd9 100644
--- a/hydrus/client/gui/metadata/ClientGUITime.py
+++ b/hydrus/client/gui/metadata/ClientGUITime.py
@@ -215,23 +215,23 @@ def __init__( self, parent, checker_options ):
def _ShowHelp( self ):
- help = 'The intention of this object is to govern how frequently the watcher or subscription checks for new files--and when it should stop completely.'
- help += '\n' * 2
- help += 'PROTIP: Do not change anything here unless you understand what it means!'
- help += '\n' * 2
- help += 'In general, checkers can and should be set up to check faster or slower based on how fast new files are coming in. This is polite to the server you are talking to and saves you CPU and bandwidth. The rate of new files is called the \'file velocity\' and is based on how many files appeared in a certain period before the _most recent check time_.'
- help += '\n' * 2
- help += 'Once the first check is done and an initial file velocity is established, the time to the next check will be based on what you set for the \'intended files per check\'. If the current file velocity is 10 files per 24 hours, and you set the intended files per check to 5 files, the checker will set the next check time to be 12 hours after the previous check time.'
- help += '\n' * 2
- help += 'After a check is completed, the new file velocity and next check time is calculated, so when files are being posted frequently, it will check more often. When things are slow, it will slow down as well. There are also minimum and maximum check periods to smooth out the bumps.'
- help += '\n' * 2
- help += 'But if you would rather just check at a fixed rate, check the checkbox and you will get a simpler \'static checking\' panel.'
- help += '\n' * 2
- help += 'If the \'file velocity\' drops below a certain amount, the checker considers the source of files dead and will stop checking. If it falls into this state but you think there might have since been a rush of new files, hit the watcher or subscription\'s \'check now\' button in an attempt to revive the checker. If there are new files, it will start checking again until they drop off once more.'
- help += '\n' * 2
- help += 'If you are still not comfortable with how this system works, the \'reasonable defaults\' are good fallbacks. Most of the time, setting some reasonable rules and leaving checkers to do their work is the best way to deal with this stuff, rather than obsessing over the exact perfect values you want for each situation.'
-
- ClientGUIDialogsMessage.ShowInformation( self, help )
+ help_text = 'The intention of this object is to govern how frequently the watcher or subscription checks for new files--and when it should stop completely.'
+ help_text += '\n' * 2
+ help_text += 'PROTIP: Do not change anything here unless you understand what it means!'
+ help_text += '\n' * 2
+ help_text += 'In general, checkers can and should be set up to check faster or slower based on how fast new files are coming in. This is polite to the server you are talking to and saves you CPU and bandwidth. The rate of new files is called the \'file velocity\' and is based on how many files appeared in a certain period before the _most recent check time_.'
+ help_text += '\n' * 2
+ help_text += 'Once the first check is done and an initial file velocity is established, the time to the next check will be based on what you set for the \'intended files per check\'. If the current file velocity is 10 files per 24 hours, and you set the intended files per check to 5 files, the checker will set the next check time to be 12 hours after the previous check time.'
+ help_text += '\n' * 2
+ help_text += 'After a check is completed, the new file velocity and next check time is calculated, so when files are being posted frequently, it will check more often. When things are slow, it will slow down as well. There are also minimum and maximum check periods to smooth out the bumps.'
+ help_text += '\n' * 2
+ help_text += 'But if you would rather just check at a fixed rate, check the checkbox and you will get a simpler \'static checking\' panel.'
+ help_text += '\n' * 2
+ help_text += 'If the \'file velocity\' drops below a certain amount, the checker considers the source of files dead and will stop checking. If it falls into this state but you think there might have since been a rush of new files, hit the watcher or subscription\'s \'check now\' button in an attempt to revive the checker. If there are new files, it will start checking again until they drop off once more.'
+ help_text += '\n' * 2
+ help_text += 'If you are still not comfortable with how this system works, the \'reasonable defaults\' are good fallbacks. Most of the time, setting some reasonable rules and leaving checkers to do their work is the best way to deal with this stuff, rather than obsessing over the exact perfect values you want for each situation.'
+
+ ClientGUIDialogsMessage.ShowInformation( self, help_text )
def _UpdateEnabledControls( self ):
@@ -1227,7 +1227,7 @@ def eventFilter( self, watched, event ):
try:
- if event.type() == QC.QEvent.FocusOut:
+ if event.type() == QC.QEvent.Type.FocusOut:
if not ClientGUIFunctions.WidgetOrAnyTLWChildHasFocus( self ):
@@ -1252,8 +1252,6 @@ def eventFilter( self, watched, event ):
def EventChange( self ):
- value = self.GetValue()
-
self._UpdateEnables()
self.timeDeltaChanged.emit()
@@ -1296,7 +1294,7 @@ def GetValue( self ):
return value
- def SetValue( self, value ):
+ def SetValue( self, value: float ):
if self._monthly_allowed:
@@ -1329,35 +1327,35 @@ def SetValue( self, value ):
if self._show_days:
- self._days.setValue( multiplier * ( value // 86400 ) )
+ self._days.setValue( int( multiplier * ( value // 86400 ) ) )
value %= 86400
if self._show_hours:
- self._hours.setValue( multiplier * ( value // 3600 ) )
+ self._hours.setValue( int( multiplier * ( value // 3600 ) ) )
value %= 3600
if self._show_minutes:
- self._minutes.setValue( multiplier * ( value // 60 ) )
+ self._minutes.setValue( int( multiplier * ( value // 60 ) ) )
value %= 60
if self._show_seconds:
- self._seconds.setValue( multiplier * int( value ) )
+ self._seconds.setValue( int( multiplier * int( value ) ) )
value %= 1
if self._show_milliseconds and value > 0:
- self._milliseconds.setValue( multiplier * int( value * 1000 ) )
+ self._milliseconds.setValue( int( multiplier * value * 1000 ) )
@@ -1578,9 +1576,9 @@ def _GetAbsoluteValue( self ):
return HydrusTime.MillisecondiseS( self._absolute_plus_or_minus.GetValue() )
- def _SetAbsoluteValue( self, value ):
+ def _SetAbsoluteValue( self, value_ms ):
- return self._absolute_plus_or_minus.SetValue( HydrusTime.SecondiseMS( value ) )
+ return self._absolute_plus_or_minus.SetValue( value_ms / 1000 )
def _GetSubValue( self ) -> int:
@@ -1588,9 +1586,9 @@ def _GetSubValue( self ) -> int:
return HydrusTime.MillisecondiseS( self._value.GetValue() )
- def _SetSubValue( self, value ):
+ def _SetSubValue( self, value_ms ):
- return self._value.SetValue( HydrusTime.SecondiseMS( value ) )
+ return self._value.SetValue( value_ms / 1000 )
diff --git a/hydrus/client/gui/networking/ClientGUIHydrusNetwork.py b/hydrus/client/gui/networking/ClientGUIHydrusNetwork.py
index da85e28f4..0220d2305 100644
--- a/hydrus/client/gui/networking/ClientGUIHydrusNetwork.py
+++ b/hydrus/client/gui/networking/ClientGUIHydrusNetwork.py
@@ -450,7 +450,7 @@ def __init__( self, parent: QW.QWidget, service_key: bytes, accounts: typing.Lis
item.setText( text )
- item.setData( QC.Qt.UserRole, account )
+ item.setData( QC.Qt.ItemDataRole.UserRole, account )
self._account_list.addItem( item )
@@ -553,7 +553,7 @@ def _AccountClicked( self ):
account_info_components = []
- account_key = item.data( QC.Qt.UserRole )
+ account_key = item.data( QC.Qt.ItemDataRole.UserRole )
my_admin_account = self._service.GetAccount()
@@ -637,7 +637,7 @@ def _RefreshAccounts( self ):
account_identifiers = self._account_identifiers
service = self._service
- pre_refresh_selected_account_keys = { item.data( QC.Qt.UserRole ) for item in self._account_list.selectedItems() }
+ pre_refresh_selected_account_keys = { item.data( QC.Qt.ItemDataRole.UserRole ) for item in self._account_list.selectedItems() }
checked_account_keys = self.GetCheckedAccountKeys()
@@ -727,7 +727,7 @@ def publish_callable( result ):
item = QW.QListWidgetItem()
- item.setFlags( item.flags() | QC.Qt.ItemIsUserCheckable )
+ item.setFlags( item.flags() | QC.Qt.ItemFlag.ItemIsUserCheckable )
account = self._account_keys_to_accounts[ account_key ]
@@ -742,14 +742,14 @@ def publish_callable( result ):
if not self._done_first_fetch or account_key in checked_account_keys:
- item.setCheckState( QC.Qt.Checked )
+ item.setCheckState( QC.Qt.CheckState.Checked )
else:
- item.setCheckState( QC.Qt.Unchecked )
+ item.setCheckState( QC.Qt.CheckState.Unchecked )
- item.setData( QC.Qt.UserRole, account_key )
+ item.setData( QC.Qt.ItemDataRole.UserRole, account_key )
self._account_list.addItem( item )
@@ -812,9 +812,9 @@ def GetCheckedAccountKeys( self ):
item = self._account_list.item( i )
- if item.checkState() == QC.Qt.Checked:
+ if item.checkState() == QC.Qt.CheckState.Checked:
- account_keys.add( item.data( QC.Qt.UserRole ) )
+ account_keys.add( item.data( QC.Qt.ItemDataRole.UserRole ) )
@@ -839,11 +839,11 @@ def UncheckAccountKey( self, account_key: bytes ):
item = self._account_list.item( i )
- checked_account_key = item.data( QC.Qt.UserRole )
+ checked_account_key = item.data( QC.Qt.ItemDataRole.UserRole )
if checked_account_key == account_key:
- item.setCheckState( QC.Qt.Unchecked )
+ item.setCheckState( QC.Qt.CheckState.Unchecked )
return
@@ -856,13 +856,13 @@ def UncheckNullAccount( self ):
item = self._account_list.item( i )
- account_key = item.data( QC.Qt.UserRole )
+ account_key = item.data( QC.Qt.ItemDataRole.UserRole )
account = self._account_keys_to_accounts[ account_key ]
if account.IsNullAccount():
- item.setCheckState( QC.Qt.Unchecked )
+ item.setCheckState( QC.Qt.CheckState.Unchecked )
return
diff --git a/hydrus/client/gui/networking/ClientGUILogin.py b/hydrus/client/gui/networking/ClientGUILogin.py
index ea68e1b26..fc696da37 100644
--- a/hydrus/client/gui/networking/ClientGUILogin.py
+++ b/hydrus/client/gui/networking/ClientGUILogin.py
@@ -304,7 +304,7 @@ def __init__( self, parent, engine, login_scripts, domains_to_login_info ):
warning += 'If a login script does not work for you, or the site you want has a complicated captcha, check out the Hydrus Companion web browser add-on--it can copy login cookies to hydrus! Pixiv now requires this! If you do set up HC for an external login, I recommend you set the respective domain(s) you are logging into to "not active" here (hit "flip active" on them), so hydrus knows it is not supposed to be taking responsibility.'
warning_st = ClientGUICommon.BetterStaticText( self, warning )
- warning_st.setAlignment( QC.Qt.AlignHCenter | QC.Qt.AlignVCenter )
+ warning_st.setAlignment( QC.Qt.AlignmentFlag.AlignHCenter | QC.Qt.AlignmentFlag.AlignVCenter )
warning_st.setWordWrap( True )
warning_st.setObjectName( 'HydrusWarning' )
diff --git a/hydrus/client/gui/networking/ClientGUINetwork.py b/hydrus/client/gui/networking/ClientGUINetwork.py
index 387c72219..1d6549f1f 100644
--- a/hydrus/client/gui/networking/ClientGUINetwork.py
+++ b/hydrus/client/gui/networking/ClientGUINetwork.py
@@ -898,7 +898,7 @@ def __init__( self, parent: QW.QWidget, controller: "CG.ClientController.Control
rules_panel = ClientGUICommon.StaticBox( self, 'rules' )
self._uses_default_rules_st = ClientGUICommon.BetterStaticText( rules_panel )
- self._uses_default_rules_st.setAlignment( QC.Qt.AlignVCenter | QC.Qt.AlignHCenter )
+ self._uses_default_rules_st.setAlignment( QC.Qt.AlignmentFlag.AlignVCenter | QC.Qt.AlignmentFlag.AlignHCenter )
self._rules_rows_panel = QW.QWidget( rules_panel )
diff --git a/hydrus/client/gui/networking/ClientGUINetworkJobControl.py b/hydrus/client/gui/networking/ClientGUINetworkJobControl.py
index 8eb343ee8..f8805182c 100644
--- a/hydrus/client/gui/networking/ClientGUINetworkJobControl.py
+++ b/hydrus/client/gui/networking/ClientGUINetworkJobControl.py
@@ -35,7 +35,7 @@ def __init__( self, parent ):
self._left_text = ClientGUICommon.BetterStaticText( self, ellipsize_end = True )
self._right_text = ClientGUICommon.BetterStaticText( self )
- self._right_text.setAlignment( QC.Qt.AlignRight | QC.Qt.AlignVCenter )
+ self._right_text.setAlignment( QC.Qt.AlignmentFlag.AlignRight | QC.Qt.AlignmentFlag.AlignVCenter )
self._last_right_min_width = 0
diff --git a/hydrus/client/gui/pages/ClientGUIManagementPanels.py b/hydrus/client/gui/pages/ClientGUIManagementPanels.py
index 45fcf7f54..e3e6ae296 100644
--- a/hydrus/client/gui/pages/ClientGUIManagementPanels.py
+++ b/hydrus/client/gui/pages/ClientGUIManagementPanels.py
@@ -277,7 +277,7 @@ def __init__( self, parent, page, controller: "CG.ClientController.Controller",
#self.setFrameStyle( QW.QFrame.Shape.Panel | QW.QFrame.Shadow.Sunken )
#self.setLineWidth( 2 )
#self.setHorizontalScrollBarPolicy( QC.Qt.ScrollBarAlwaysOff )
- self.setVerticalScrollBarPolicy( QC.Qt.ScrollBarAsNeeded )
+ self.setVerticalScrollBarPolicy( QC.Qt.ScrollBarPolicy.ScrollBarAsNeeded )
self._controller = controller
self._management_controller = management_controller
@@ -1879,7 +1879,7 @@ def _SetGUGKeyAndName( self, gug_key_and_name ):
self._query_input.setPlaceholderText( new_initial_search_text )
- self._query_input.setFocus( QC.Qt.OtherFocusReason )
+ self._query_input.setFocus( QC.Qt.FocusReason.OtherFocusReason )
def _SetOptionsToGalleryImports( self ):
@@ -4249,7 +4249,7 @@ def __init__( self, parent, page, controller, management_controller: ClientGUIMa
self._contents_add = ClientGUICommon.BetterCheckBoxList( self._petition_panel )
self._contents_add.itemDoubleClicked.connect( self.ContentsAddDoubleClick )
- self._contents_add.setHorizontalScrollBarPolicy( QC.Qt.ScrollBarAlwaysOff )
+ self._contents_add.setHorizontalScrollBarPolicy( QC.Qt.ScrollBarPolicy.ScrollBarAlwaysOff )
( min_width, min_height ) = ClientGUIFunctions.ConvertTextToPixels( self._contents_add, ( 16, 20 ) )
@@ -4257,7 +4257,7 @@ def __init__( self, parent, page, controller, management_controller: ClientGUIMa
self._contents_delete = ClientGUICommon.BetterCheckBoxList( self._petition_panel )
self._contents_delete.itemDoubleClicked.connect( self.ContentsDeleteDoubleClick )
- self._contents_delete.setHorizontalScrollBarPolicy( QC.Qt.ScrollBarAlwaysOff )
+ self._contents_delete.setHorizontalScrollBarPolicy( QC.Qt.ScrollBarPolicy.ScrollBarAlwaysOff )
( min_width, min_height ) = ClientGUIFunctions.ConvertTextToPixels( self._contents_delete, ( 16, 20 ) )
diff --git a/hydrus/client/gui/pages/ClientGUIMediaResultsPanel.py b/hydrus/client/gui/pages/ClientGUIMediaResultsPanel.py
index c528d5996..84c87418e 100644
--- a/hydrus/client/gui/pages/ClientGUIMediaResultsPanel.py
+++ b/hydrus/client/gui/pages/ClientGUIMediaResultsPanel.py
@@ -377,6 +377,8 @@ def _GetPrettyStatusForStatusBar( self ) -> str:
if len( self._selected_media ) == 1 and len( list(self._selected_media)[0].GetHashes() ) == 1 and CG.client_controller.new_options.GetBoolean( 'show_extended_single_file_info_in_status_bar' ):
+ # TODO: Were I feeling clever, this guy would also emit a tooltip, which we can calculate here no prob
+
singleton_media = ClientMedia.FlattenMedia( self._selected_media )[0]
lines = ClientMediaResultPrettyInfo.GetPrettyMediaResultInfoLines( singleton_media.GetMediaResult(), only_interesting_lines = True )
@@ -387,7 +389,6 @@ def _GetPrettyStatusForStatusBar( self ) -> str:
s += ', '.join( texts )
-
else:
num_inbox = sum( ( media.GetNumInbox() for media in self._selected_media ) )
@@ -866,7 +867,7 @@ def _ManageNotes( self ):
ClientGUIMediaModalActions.EditFileNotes( self, media )
- self.setFocus( QC.Qt.OtherFocusReason )
+ self.setFocus( QC.Qt.FocusReason.OtherFocusReason )
@@ -883,7 +884,7 @@ def _ManageRatings( self ):
dlg.exec()
- self.setFocus( QC.Qt.OtherFocusReason )
+ self.setFocus( QC.Qt.FocusReason.OtherFocusReason )
@@ -908,7 +909,7 @@ def _ManageTags( self ):
dlg.exec()
- self.setFocus( QC.Qt.OtherFocusReason )
+ self.setFocus( QC.Qt.FocusReason.OtherFocusReason )
@@ -922,7 +923,7 @@ def _ManageTimestamps( self ):
ClientGUIMediaModalActions.EditFileTimestamps( self, ordered_selected_flat_media )
- self.setFocus( QC.Qt.OtherFocusReason )
+ self.setFocus( QC.Qt.FocusReason.OtherFocusReason )
@@ -955,7 +956,7 @@ def _ManageURLs( self ):
- self.setFocus( QC.Qt.OtherFocusReason )
+ self.setFocus( QC.Qt.FocusReason.OtherFocusReason )
@@ -1076,7 +1077,7 @@ def _PetitionFiles( self, remote_service_key ):
- self.setFocus( QC.Qt.OtherFocusReason )
+ self.setFocus( QC.Qt.FocusReason.OtherFocusReason )
elif service_type == HC.IPFS:
@@ -2484,7 +2485,7 @@ def SelectByTags( self, page_key, tag_service_key, and_or_or, tags ):
self._Select( ClientMediaFileFilter.FileFilter( ClientMediaFileFilter.FILE_FILTER_TAGS, ( tag_service_key, and_or_or, tags ) ) )
- self.setFocus( QC.Qt.OtherFocusReason )
+ self.setFocus( QC.Qt.FocusReason.OtherFocusReason )
diff --git a/hydrus/client/gui/pages/ClientGUIMediaResultsPanelSortCollect.py b/hydrus/client/gui/pages/ClientGUIMediaResultsPanelSortCollect.py
index 907c385a3..340692f27 100644
--- a/hydrus/client/gui/pages/ClientGUIMediaResultsPanelSortCollect.py
+++ b/hydrus/client/gui/pages/ClientGUIMediaResultsPanelSortCollect.py
@@ -68,13 +68,13 @@ def _HandleItemPressed( self, index ):
item = self.model().itemFromIndex( index )
- if item.checkState() == QC.Qt.Checked:
+ if item.checkState() == QC.Qt.CheckState.Checked:
- item.setCheckState( QC.Qt.Unchecked )
+ item.setCheckState( QC.Qt.CheckState.Unchecked )
else:
- item.setCheckState( QC.Qt.Checked )
+ item.setCheckState( QC.Qt.CheckState.Checked )
self.SetValue( self._cached_text )
@@ -118,7 +118,7 @@ def _ReinitialiseChoices( self ):
item = self.model().item( i, 0 )
t = item.text()
- d = self.itemData( i, QC.Qt.UserRole )
+ d = self.itemData( i, QC.Qt.ItemDataRole.UserRole )
current_text_and_data_tuples.append( ( t, d ) )
@@ -140,7 +140,7 @@ def _ReinitialiseChoices( self ):
item = self.model().item( self.count() - 1, 0 )
- item.setCheckState( QC.Qt.Unchecked )
+ item.setCheckState( QC.Qt.CheckState.Unchecked )
made_changes = True
@@ -157,7 +157,7 @@ def GetCheckedIndices( self ):
item = self.model().item( idx )
- if item.checkState() == QC.Qt.Checked:
+ if item.checkState() == QC.Qt.CheckState.Checked:
indices.append( idx )
@@ -174,7 +174,7 @@ def GetCheckedStrings( self ):
item = self.model().item( idx )
- if item.checkState() == QC.Qt.Checked:
+ if item.checkState() == QC.Qt.CheckState.Checked:
strings.append( item.text() )
@@ -190,7 +190,7 @@ def GetValues( self ):
for index in self.GetCheckedIndices():
- ( collect_type, collect_data ) = self.itemData( index, QC.Qt.UserRole )
+ ( collect_type, collect_data ) = self.itemData( index, QC.Qt.ItemDataRole.UserRole )
if collect_type == 'namespace':
@@ -261,7 +261,7 @@ def SetCollectByValue( self, media_collect ):
for index in range( self.count() ):
- ( collect_type, collect_data ) = self.itemData( index, QC.Qt.UserRole )
+ ( collect_type, collect_data ) = self.itemData( index, QC.Qt.ItemDataRole.UserRole )
p1 = collect_type == 'namespace' and collect_data in media_collect.namespaces
p2 = collect_type == 'rating' and collect_data in media_collect.rating_service_keys
@@ -292,11 +292,11 @@ def SetCheckedIndices( self, indices_to_check ):
if idx in indices_to_check:
- item.setCheckState( QC.Qt.Checked )
+ item.setCheckState( QC.Qt.CheckState.Checked )
else:
- item.setCheckState( QC.Qt.Unchecked )
+ item.setCheckState( QC.Qt.CheckState.Unchecked )
@@ -400,7 +400,7 @@ def eventFilter( self, watched, event ):
if watched == self._collect_comboctrl:
- if event.type() == QC.QEvent.MouseButtonPress and event.button() == QC.Qt.MiddleButton:
+ if event.type() == QC.QEvent.Type.MouseButtonPress and event.button() == QC.Qt.MouseButton.MiddleButton:
self.SetCollect( ClientMedia.MediaCollect( collect_unmatched = self._media_collect.collect_unmatched ) )
diff --git a/hydrus/client/gui/pages/ClientGUIMediaResultsPanelThumbnails.py b/hydrus/client/gui/pages/ClientGUIMediaResultsPanelThumbnails.py
index 0785d99b8..a1214ca7b 100644
--- a/hydrus/client/gui/pages/ClientGUIMediaResultsPanelThumbnails.py
+++ b/hydrus/client/gui/pages/ClientGUIMediaResultsPanelThumbnails.py
@@ -258,7 +258,7 @@ def _DrawCanvasPage( self, page_index, canvas_page ):
painter.setCompositionMode( QG.QPainter.CompositionMode_Source )
- painter.setBackground( QG.QBrush( QC.Qt.transparent ) )
+ painter.setBackground( QG.QBrush( QC.Qt.GlobalColor.transparent ) )
painter.eraseRect( painter.viewport() )
@@ -954,7 +954,7 @@ def MaintainPageCache( self ):
def mouseMoveEvent( self, event ):
- if event.buttons() & QC.Qt.LeftButton:
+ if event.buttons() & QC.Qt.MouseButton.LeftButton:
we_started_dragging_on_this_panel = self._drag_init_coordinates is not None
@@ -986,11 +986,11 @@ def mouseMoveEvent( self, event ):
if len( media ) > 0:
- alt_down = event.modifiers() & QC.Qt.AltModifier
+ alt_down = event.modifiers() & QC.Qt.KeyboardModifier.AltModifier
result = ClientGUIDragDrop.DoFileExportDragDrop( self, self._page_key, media, alt_down )
- if result not in ( QC.Qt.IgnoreAction, ):
+ if result not in ( QC.Qt.DropAction.IgnoreAction, ):
self.focusMediaPaused.emit()
@@ -1010,7 +1010,7 @@ def mouseMoveEvent( self, event ):
def mouseReleaseEvent( self, event ):
- if event.button() != QC.Qt.RightButton:
+ if event.button() != QC.Qt.MouseButton.RightButton:
QW.QScrollArea.mouseReleaseEvent( self, event )
@@ -1970,11 +1970,11 @@ def mousePressEvent( self, event ):
thumb = self._parent._GetThumbnailUnderMouse( event )
- right_on_whitespace = event.button() == QC.Qt.RightButton and thumb is None
+ right_on_whitespace = event.button() == QC.Qt.MouseButton.RightButton and thumb is None
if not right_on_whitespace:
- self._parent._HitMedia( thumb, event.modifiers() & QC.Qt.ControlModifier, event.modifiers() & QC.Qt.ShiftModifier )
+ self._parent._HitMedia( thumb, event.modifiers() & QC.Qt.KeyboardModifier.ControlModifier, event.modifiers() & QC.Qt.KeyboardModifier.ShiftModifier )
# this specifically does not scroll to media, as for clicking (esp. double-clicking attempts), the scroll can be jarring
@@ -2359,7 +2359,7 @@ def GetQtImage( self, media: ClientMedia.Media, media_panel: ClientGUIMediaResul
bd_colour = media_panel.GetColour( border_colour_type )
painter.setBrush( QG.QBrush( bd_colour ) )
- painter.setPen( QG.QPen( QC.Qt.NoPen ) )
+ painter.setPen( QG.QPen( QC.Qt.PenStyle.NoPen ) )
rectangles = []
diff --git a/hydrus/client/gui/pages/ClientGUINewPageChooser.py b/hydrus/client/gui/pages/ClientGUINewPageChooser.py
index 026972424..ecb0311a5 100644
--- a/hydrus/client/gui/pages/ClientGUINewPageChooser.py
+++ b/hydrus/client/gui/pages/ClientGUINewPageChooser.py
@@ -64,15 +64,15 @@ def __init__( self, parent, controller: "CG.ClientController.Controller" ):
self._button_3.setObjectName('3')
# this ensures these buttons won't get focus and receive key events, letting dialog handle arrow/number shortcuts
- self._button_7.setFocusPolicy( QC.Qt.NoFocus )
- self._button_8.setFocusPolicy( QC.Qt.NoFocus )
- self._button_9.setFocusPolicy( QC.Qt.NoFocus )
- self._button_4.setFocusPolicy( QC.Qt.NoFocus )
- self._button_5.setFocusPolicy( QC.Qt.NoFocus )
- self._button_6.setFocusPolicy( QC.Qt.NoFocus )
- self._button_1.setFocusPolicy( QC.Qt.NoFocus )
- self._button_2.setFocusPolicy( QC.Qt.NoFocus )
- self._button_3.setFocusPolicy( QC.Qt.NoFocus )
+ self._button_7.setFocusPolicy( QC.Qt.FocusPolicy.NoFocus )
+ self._button_8.setFocusPolicy( QC.Qt.FocusPolicy.NoFocus )
+ self._button_9.setFocusPolicy( QC.Qt.FocusPolicy.NoFocus )
+ self._button_4.setFocusPolicy( QC.Qt.FocusPolicy.NoFocus )
+ self._button_5.setFocusPolicy( QC.Qt.FocusPolicy.NoFocus )
+ self._button_6.setFocusPolicy( QC.Qt.FocusPolicy.NoFocus )
+ self._button_1.setFocusPolicy( QC.Qt.FocusPolicy.NoFocus )
+ self._button_2.setFocusPolicy( QC.Qt.FocusPolicy.NoFocus )
+ self._button_3.setFocusPolicy( QC.Qt.FocusPolicy.NoFocus )
gridbox = QP.GridLayout( cols = 3 )
@@ -326,7 +326,7 @@ def _InitButtons( self, menu_keyword ):
def event( self, event ):
- if event.type() == QC.QEvent.WindowDeactivate and not self._action_picked:
+ if event.type() == QC.QEvent.Type.WindowDeactivate and not self._action_picked:
self.done( QW.QDialog.DialogCode.Rejected )
@@ -344,20 +344,20 @@ def keyPressEvent( self, event ):
( modifier, key ) = ClientGUIShortcuts.ConvertKeyEventToSimpleTuple( event )
- if key == QC.Qt.Key_Up: button_id = 8
- elif key == QC.Qt.Key_Left: button_id = 4
- elif key == QC.Qt.Key_Right: button_id = 6
- elif key == QC.Qt.Key_Down: button_id = 2
- elif key == QC.Qt.Key_1 and modifier == QC.Qt.KeypadModifier: button_id = 1
- elif key == QC.Qt.Key_2 and modifier == QC.Qt.KeypadModifier: button_id = 2
- elif key == QC.Qt.Key_3 and modifier == QC.Qt.KeypadModifier: button_id = 3
- elif key == QC.Qt.Key_4 and modifier == QC.Qt.KeypadModifier: button_id = 4
- elif key == QC.Qt.Key_5 and modifier == QC.Qt.KeypadModifier: button_id = 5
- elif key == QC.Qt.Key_6 and modifier == QC.Qt.KeypadModifier: button_id = 6
- elif key == QC.Qt.Key_7 and modifier == QC.Qt.KeypadModifier: button_id = 7
- elif key == QC.Qt.Key_8 and modifier == QC.Qt.KeypadModifier: button_id = 8
- elif key == QC.Qt.Key_9 and modifier == QC.Qt.KeypadModifier: button_id = 9
- elif key in ( QC.Qt.Key_Enter, QC.Qt.Key_Return ):
+ if key == QC.Qt.Key.Key_Up: button_id = 8
+ elif key == QC.Qt.Key.Key_Left: button_id = 4
+ elif key == QC.Qt.Key.Key_Right: button_id = 6
+ elif key == QC.Qt.Key.Key_Down: button_id = 2
+ elif key == QC.Qt.Key.Key_1 and modifier == QC.Qt.KeyboardModifier.KeypadModifier: button_id = 1
+ elif key == QC.Qt.Key.Key_2 and modifier == QC.Qt.KeyboardModifier.KeypadModifier: button_id = 2
+ elif key == QC.Qt.Key.Key_3 and modifier == QC.Qt.KeyboardModifier.KeypadModifier: button_id = 3
+ elif key == QC.Qt.Key.Key_4 and modifier == QC.Qt.KeyboardModifier.KeypadModifier: button_id = 4
+ elif key == QC.Qt.Key.Key_5 and modifier == QC.Qt.KeyboardModifier.KeypadModifier: button_id = 5
+ elif key == QC.Qt.Key.Key_6 and modifier == QC.Qt.KeyboardModifier.KeypadModifier: button_id = 6
+ elif key == QC.Qt.Key.Key_7 and modifier == QC.Qt.KeyboardModifier.KeypadModifier: button_id = 7
+ elif key == QC.Qt.Key.Key_8 and modifier == QC.Qt.KeyboardModifier.KeypadModifier: button_id = 8
+ elif key == QC.Qt.Key.Key_9 and modifier == QC.Qt.KeyboardModifier.KeypadModifier: button_id = 9
+ elif key in ( QC.Qt.Key.Key_Enter, QC.Qt.Key.Key_Return ):
# get the 'first', scanning from top-left
@@ -371,7 +371,7 @@ def keyPressEvent( self, event ):
- elif key == QC.Qt.Key_Escape:
+ elif key == QC.Qt.Key.Key_Escape:
self.done( QW.QDialog.DialogCode.Rejected )
diff --git a/hydrus/client/gui/pages/ClientGUIPages.py b/hydrus/client/gui/pages/ClientGUIPages.py
index 349ea86e9..fb0891c5e 100644
--- a/hydrus/client/gui/pages/ClientGUIPages.py
+++ b/hydrus/client/gui/pages/ClientGUIPages.py
@@ -84,10 +84,10 @@ def __init__( self, parent: QW.QWidget, controller: "CG.ClientController.Control
self._pretty_status = ''
self._management_media_split = QW.QSplitter( self )
- self._management_media_split.setOrientation( QC.Qt.Horizontal )
+ self._management_media_split.setOrientation( QC.Qt.Orientation.Horizontal )
self._search_preview_split = QW.QSplitter( self._management_media_split )
- self._search_preview_split.setOrientation( QC.Qt.Vertical )
+ self._search_preview_split.setOrientation( QC.Qt.Orientation.Vertical )
self._done_split_setups = False
@@ -661,7 +661,7 @@ def RefreshQuery( self ):
def SetMediaFocus( self ):
- self._media_panel.setFocus( QC.Qt.OtherFocusReason )
+ self._media_panel.setFocus( QC.Qt.FocusReason.OtherFocusReason )
def SetName( self, name ):
@@ -1043,11 +1043,11 @@ def _UpdateOptions( self ):
if CG.client_controller.new_options.GetBoolean( 'elide_page_tab_names' ):
- self.tabBar().setElideMode( QC.Qt.ElideMiddle )
+ self.tabBar().setElideMode( QC.Qt.TextElideMode.ElideMiddle )
else:
- self.tabBar().setElideMode( QC.Qt.ElideNone )
+ self.tabBar().setElideMode( QC.Qt.TextElideMode.ElideNone )
direction = CG.client_controller.new_options.GetInteger( 'notebook_tab_alignment' )
@@ -2077,7 +2077,7 @@ def eventFilter( self, watched, event ):
try:
- if event.type() in ( QC.QEvent.MouseButtonDblClick, QC.QEvent.MouseButtonRelease ):
+ if event.type() in ( QC.QEvent.Type.MouseButtonDblClick, QC.QEvent.Type.MouseButtonRelease ):
screen_position = QG.QCursor.pos()
@@ -2111,24 +2111,24 @@ def eventFilter( self, watched, event ):
- if event.type() == QC.QEvent.MouseButtonDblClick:
+ if event.type() == QC.QEvent.Type.MouseButtonDblClick:
- if event.button() == QC.Qt.LeftButton and over_tab_greyspace and not over_a_tab:
+ if event.button() == QC.Qt.MouseButton.LeftButton and over_tab_greyspace and not over_a_tab:
self.EventNewPageFromScreenPosition( screen_position )
return True
- elif event.type() == QC.QEvent.MouseButtonRelease:
+ elif event.type() == QC.QEvent.Type.MouseButtonRelease:
- if event.button() == QC.Qt.RightButton and ( over_a_tab or over_tab_greyspace ):
+ if event.button() == QC.Qt.MouseButton.RightButton and ( over_a_tab or over_tab_greyspace ):
self.ShowMenuFromScreenPosition( screen_position )
return True
- elif event.button() == QC.Qt.MiddleButton and over_tab_greyspace and not over_a_tab:
+ elif event.button() == QC.Qt.MouseButton.MiddleButton and over_tab_greyspace and not over_a_tab:
self.EventNewPageFromScreenPosition( screen_position )
@@ -2893,7 +2893,7 @@ def MediaDragAndDropDropped( self, source_page_key, hashes ):
# queryKBM here for instant check, not waiting for event processing to catch up u wot mate
- ctrl_down = QW.QApplication.queryKeyboardModifiers() & QC.Qt.ControlModifier
+ ctrl_down = QW.QApplication.queryKeyboardModifiers() & QC.Qt.KeyboardModifier.ControlModifier
if not ctrl_down:
diff --git a/hydrus/client/gui/panels/ClientGUIManageOptionsPanel.py b/hydrus/client/gui/panels/ClientGUIManageOptionsPanel.py
index a42912d61..f6f62800d 100644
--- a/hydrus/client/gui/panels/ClientGUIManageOptionsPanel.py
+++ b/hydrus/client/gui/panels/ClientGUIManageOptionsPanel.py
@@ -365,8 +365,6 @@ def __init__( self, parent ):
general = ClientGUICommon.StaticBox( self, 'general' )
- self._verify_regular_https = QW.QCheckBox( general )
-
if self._new_options.GetBoolean( 'advanced_mode' ):
network_timeout_min = 1
@@ -412,6 +410,11 @@ def __init__( self, parent ):
self._max_network_jobs = ClientGUICommon.BetterSpinBox( general, min = 1, max = max_network_jobs_max )
self._max_network_jobs_per_domain = ClientGUICommon.BetterSpinBox( general, min = 1, max = max_network_jobs_per_domain_max )
+ self._set_requests_ca_bundle_env = QW.QCheckBox( general )
+ self._set_requests_ca_bundle_env.setToolTip( ClientGUIFunctions.WrapToolTip( 'Just testing something here; ignore unless hydev asks you to use it please. Requires restart.' ) )
+
+ self._verify_regular_https = QW.QCheckBox( general )
+
#
proxy_panel = ClientGUICommon.StaticBox( self, 'proxy settings' )
@@ -431,6 +434,7 @@ def __init__( self, parent ):
#
+ self._set_requests_ca_bundle_env.setChecked( self._new_options.GetBoolean( 'set_requests_ca_bundle_env' ) )
self._verify_regular_https.setChecked( self._new_options.GetBoolean( 'verify_regular_https' ) )
self._http_proxy.SetValue( self._new_options.GetNoneableString( 'http_proxy' ) )
@@ -475,6 +479,7 @@ def __init__( self, parent ):
rows.append( ( 'Halt new jobs as long as this many network infrastructure errors on their domain (0 for never wait): ', self._domain_network_infrastructure_error_velocity ) )
rows.append( ( 'max number of simultaneous active network jobs: ', self._max_network_jobs ) )
rows.append( ( 'max number of simultaneous active network jobs per domain: ', self._max_network_jobs_per_domain ) )
+ rows.append( ( 'DEBUG: set the REQUESTS_CA_BUNDLE env to certifi cacert.pem on program start:', self._set_requests_ca_bundle_env ) )
rows.append( ( 'BUGFIX: verify regular https traffic:', self._verify_regular_https ) )
gridbox = ClientGUICommon.WrapInGrid( general, rows )
@@ -528,6 +533,7 @@ def __init__( self, parent ):
def UpdateOptions( self ):
+ self._new_options.SetBoolean( 'set_requests_ca_bundle_env', self._set_requests_ca_bundle_env.isChecked() )
self._new_options.SetBoolean( 'verify_regular_https', self._verify_regular_https.isChecked() )
self._new_options.SetNoneableString( 'http_proxy', self._http_proxy.GetValue() )
@@ -969,6 +975,10 @@ def __init__( self, parent ):
self._secret_discord_dnd_fix = QW.QCheckBox( self._dnd_panel )
self._secret_discord_dnd_fix.setToolTip( ClientGUIFunctions.WrapToolTip( 'THIS SOMETIMES FIXES DnD FOR WEIRD PROGRAMS, BUT IT ALSO OFTEN BREAKS IT FOR OTHERS.\n\nBecause of weird security/permission issues, a program will sometimes not accept a drag and drop file export from hydrus unless the DnD is set to "move" rather than "copy" (discord has done this for some people). Since we do not want to let you accidentally move your files out of your primary file store, this is only enabled if you are copying the files in question to your temp folder first!' ) )
+ self._export_folder_panel = ClientGUICommon.StaticBox( self, 'export folder' )
+
+ self._export_location = QP.DirPickerCtrl( self._export_folder_panel )
+
#
self._new_options = CG.client_controller.new_options
@@ -979,6 +989,16 @@ def __init__( self, parent ):
self._secret_discord_dnd_fix.setChecked( self._new_options.GetBoolean( 'secret_discord_dnd_fix' ) )
+ if HC.options[ 'export_path' ] is not None:
+
+ abs_path = HydrusPaths.ConvertPortablePathToAbsPath( HC.options[ 'export_path' ] )
+
+ if abs_path is not None:
+
+ self._export_location.SetPath( abs_path )
+
+
+
#
rows = []
@@ -988,7 +1008,7 @@ def __init__( self, parent ):
rows.append( ( 'Drag-and-drop export filename pattern: ', self._discord_dnd_filename_pattern ) )
rows.append( ( '', self._export_pattern_button ) )
- gridbox = ClientGUICommon.WrapInGrid( self, rows )
+ gridbox = ClientGUICommon.WrapInGrid( self._dnd_panel, rows )
label = 'You can drag-and-drop a selection of files out of the client to quickly copy-export them to a folder or an external program (include web browser upload boxes).'
@@ -1005,9 +1025,22 @@ def __init__( self, parent ):
self._dnd_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
+ #
+
+ rows = []
+
+ rows.append( ( 'Default export directory: ', self._export_location ) )
+
+ gridbox = ClientGUICommon.WrapInGrid( self._export_folder_panel, rows )
+
+ self._export_folder_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
+
+ #
+
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, self._dnd_panel, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
+ QP.AddToLayout( vbox, self._export_folder_panel, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.addStretch( 1 )
@@ -1033,6 +1066,8 @@ def UpdateOptions( self ):
self._new_options.SetString( 'discord_dnd_filename_pattern', self._discord_dnd_filename_pattern.text() )
self._new_options.SetBoolean( 'secret_discord_dnd_fix', self._secret_discord_dnd_fix.isChecked() )
+ HC.options[ 'export_path' ] = HydrusPaths.ConvertAbsPathToPortablePath( self._export_location.GetPath() )
+
class _ExternalProgramsPanel( OptionsPagePanel ):
@@ -1235,8 +1270,6 @@ def __init__( self, parent ):
self._new_options = CG.client_controller.new_options
- self._export_location = QP.DirPickerCtrl( self )
-
self._prefix_hash_when_copying = QW.QCheckBox( self )
self._prefix_hash_when_copying.setToolTip( ClientGUIFunctions.WrapToolTip( 'If you often paste hashes into boorus, check this to automatically prefix with the type, like "md5:2496dabcbd69e3c56a5d8caabb7acde5".' ) )
@@ -1286,16 +1319,6 @@ def __init__( self, parent ):
#
- if HC.options[ 'export_path' ] is not None:
-
- abs_path = HydrusPaths.ConvertPortablePathToAbsPath( HC.options[ 'export_path' ] )
-
- if abs_path is not None:
-
- self._export_location.SetPath( abs_path )
-
-
-
self._prefix_hash_when_copying.setChecked( self._new_options.GetBoolean( 'prefix_hash_when_copying' ) )
self._delete_to_recycle_bin.setChecked( HC.options[ 'delete_to_recycle_bin' ] )
@@ -1357,7 +1380,6 @@ def __init__( self, parent ):
rows.append( ( 'Remove files from view when they are moved to another local file domain: ', self._remove_local_domain_moved_files ) )
rows.append( ( 'Number of hours a file can be in the trash before being deleted: ', self._trash_max_age ) )
rows.append( ( 'Maximum size of trash (MB): ', self._trash_max_size ) )
- rows.append( ( 'Default export directory: ', self._export_location ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
@@ -1439,8 +1461,6 @@ def _UpdateRemoveFiltered( self ):
def UpdateOptions( self ):
- HC.options[ 'export_path' ] = HydrusPaths.ConvertAbsPathToPortablePath( self._export_location.GetPath() )
-
self._new_options.SetBoolean( 'prefix_hash_when_copying', self._prefix_hash_when_copying.isChecked() )
HC.options[ 'delete_to_recycle_bin' ] = self._delete_to_recycle_bin.isChecked()
@@ -2149,8 +2169,6 @@ def __init__( self, parent, new_options ):
default_fios = ClientGUICommon.StaticBox( self, 'default file import options' )
- show_downloader_options = True
-
quiet_file_import_options = self._new_options.GetDefaultFileImportOptions( FileImportOptions.IMPORT_TYPE_QUIET )
show_downloader_options = True
@@ -2168,10 +2186,16 @@ def __init__( self, parent, new_options ):
#
+ st = ClientGUICommon.BetterStaticText( default_fios, label = 'You might like to set different "presentation options" for importers that work in the background vs those that work in a page in front of you.\n\nNOTE: I am likely to break "File Import Options" into smaller pieces in an upcoming update, and this options page will change too.' )
+
+ st.setWordWrap( True )
+
+ default_fios.Add( st, CC.FLAGS_EXPAND_PERPENDICULAR )
+
rows = []
- rows.append( ( 'For \'quiet\' import contexts: import folders, subscriptions, Client API:', self._quiet_fios ) )
- rows.append( ( 'For \'loud\' import contexts: downloader pages:', self._loud_fios ) )
+ rows.append( ( 'For import contexts that happen in a popup window or with no UI at all:\n(import folders, subscriptions, Client API)', self._quiet_fios ) )
+ rows.append( ( 'For import contexts that happen on a page in the main gui window:\n(gallery or url import pages, watchers, local file import pages, any files/urls you drag and drop on the client)', self._loud_fios ) )
gridbox = ClientGUICommon.WrapInGrid( default_fios, rows )
@@ -2803,8 +2827,20 @@ def __init__( self, parent ):
self._use_nice_resolution_strings = QW.QCheckBox( media_canvas_panel )
self._use_nice_resolution_strings.setToolTip( ClientGUIFunctions.WrapToolTip( 'Use "1080p" instead of "1920x1080" for common resolutions.' ) )
+ self._file_info_line_consider_archived_interesting = QW.QCheckBox( media_canvas_panel )
+ self._file_info_line_consider_archived_interesting.setToolTip( ClientGUIFunctions.WrapToolTip( 'Should we show the fact a file is archived in the top hover file info summary?' ) )
+
+ self._file_info_line_consider_archived_time_interesting = QW.QCheckBox( media_canvas_panel )
+ self._file_info_line_consider_archived_time_interesting.setToolTip( ClientGUIFunctions.WrapToolTip( 'If we show the archived status, should we show when it happened?' ) )
+
+ self._file_info_line_consider_file_services_interesting = QW.QCheckBox( media_canvas_panel )
+ self._file_info_line_consider_file_services_interesting.setToolTip( ClientGUIFunctions.WrapToolTip( 'Should we show all the file services a file is in in the top hover file info summary?' ) )
+
+ self._file_info_line_consider_file_services_import_times_interesting = QW.QCheckBox( media_canvas_panel )
+ self._file_info_line_consider_file_services_import_times_interesting.setToolTip( ClientGUIFunctions.WrapToolTip( 'If we show the file services, should we show when they were added?' ) )
+
self._hide_uninteresting_modified_time = QW.QCheckBox( media_canvas_panel )
- self._hide_uninteresting_modified_time.setToolTip( ClientGUIFunctions.WrapToolTip( 'If the file has a modified time similar to its import time (i.e. the number of seconds since both events differs by less than 10%), hide the modified time in the top of the media viewer.' ) )
+ self._hide_uninteresting_modified_time.setToolTip( ClientGUIFunctions.WrapToolTip( 'If the file has a modified time similar to its import time (specifically, the number of seconds since both events differs by less than 10%), hide the modified time in the top hover file info summary.' ) )
#
@@ -2848,6 +2884,11 @@ def __init__( self, parent ):
self._draw_notes_hover_in_media_viewer_background.setChecked( self._new_options.GetBoolean( 'draw_notes_hover_in_media_viewer_background' ) )
self._draw_bottom_right_index_in_media_viewer_background.setChecked( self._new_options.GetBoolean( 'draw_bottom_right_index_in_media_viewer_background' ) )
self._use_nice_resolution_strings.setChecked( self._new_options.GetBoolean( 'use_nice_resolution_strings' ) )
+
+ self._file_info_line_consider_archived_interesting.setChecked( self._new_options.GetBoolean( 'file_info_line_consider_archived_interesting' ) )
+ self._file_info_line_consider_archived_time_interesting.setChecked( self._new_options.GetBoolean( 'file_info_line_consider_archived_time_interesting' ) )
+ self._file_info_line_consider_file_services_interesting.setChecked( self._new_options.GetBoolean( 'file_info_line_consider_file_services_interesting' ) )
+ self._file_info_line_consider_file_services_import_times_interesting.setChecked( self._new_options.GetBoolean( 'file_info_line_consider_file_services_import_times_interesting' ) )
self._hide_uninteresting_modified_time.setChecked( self._new_options.GetBoolean( 'hide_uninteresting_modified_time' ) )
self._media_viewer_cursor_autohide_time_ms.SetValue( self._new_options.GetNoneableInteger( 'media_viewer_cursor_autohide_time_ms' ) )
@@ -2889,7 +2930,11 @@ def __init__( self, parent ):
rows.append( ( 'Duplicate notes hover-window information in the background of the viewer:', self._draw_notes_hover_in_media_viewer_background ) )
rows.append( ( 'Draw bottom-right index text in the background of the viewer:', self._draw_bottom_right_index_in_media_viewer_background ) )
rows.append( ( 'Swap in common resolution labels:', self._use_nice_resolution_strings ) )
- rows.append( ( 'Hide uninteresting modified times:', self._hide_uninteresting_modified_time ) )
+ rows.append( ( 'Show archived status in top hover summary', self._file_info_line_consider_archived_interesting ) )
+ rows.append( ( 'Show archived time in top hover summary', self._file_info_line_consider_archived_time_interesting ) )
+ rows.append( ( 'Show file services in top hover summary', self._file_info_line_consider_file_services_interesting ) )
+ rows.append( ( 'Show file service add times in top hover summary', self._file_info_line_consider_file_services_import_times_interesting ) )
+ rows.append( ( 'Hide uninteresting modified times in top hover summary:', self._hide_uninteresting_modified_time ) )
media_canvas_gridbox = ClientGUICommon.WrapInGrid( media_canvas_panel, rows )
@@ -2920,6 +2965,17 @@ def __init__( self, parent ):
self.setLayout( vbox )
+ self._file_info_line_consider_archived_interesting.clicked.connect( self._UpdateFileInfoLineWidgets )
+ self._file_info_line_consider_file_services_interesting.clicked.connect( self._UpdateFileInfoLineWidgets )
+
+ self._UpdateFileInfoLineWidgets()
+
+
+ def _UpdateFileInfoLineWidgets( self ):
+
+ self._file_info_line_consider_archived_time_interesting.setEnabled( self._file_info_line_consider_archived_interesting.isChecked() )
+ self._file_info_line_consider_file_services_import_times_interesting.setEnabled( self._file_info_line_consider_file_services_interesting.isChecked() )
+
def EventSlideshowChanged( self, text ):
@@ -2951,6 +3007,11 @@ def UpdateOptions( self ):
self._new_options.SetBoolean( 'draw_notes_hover_in_media_viewer_background', self._draw_notes_hover_in_media_viewer_background.isChecked() )
self._new_options.SetBoolean( 'draw_bottom_right_index_in_media_viewer_background', self._draw_bottom_right_index_in_media_viewer_background.isChecked() )
self._new_options.SetBoolean( 'use_nice_resolution_strings', self._use_nice_resolution_strings.isChecked() )
+
+ self._new_options.SetBoolean( 'file_info_line_consider_archived_interesting', self._file_info_line_consider_archived_interesting.isChecked() )
+ self._new_options.SetBoolean( 'file_info_line_consider_archived_time_interesting', self._file_info_line_consider_archived_time_interesting.isChecked() )
+ self._new_options.SetBoolean( 'file_info_line_consider_file_services_interesting', self._file_info_line_consider_file_services_interesting.isChecked() )
+ self._new_options.SetBoolean( 'file_info_line_consider_file_services_import_times_interesting', self._file_info_line_consider_file_services_import_times_interesting.isChecked() )
self._new_options.SetBoolean( 'hide_uninteresting_modified_time', self._hide_uninteresting_modified_time.isChecked() )
self._new_options.SetBoolean( 'disallow_media_drags_on_duration_media', self._disallow_media_drags_on_duration_media.isChecked() )
@@ -5555,7 +5616,7 @@ def __init__( self, parent, new_options ):
self._media_background_bmp_path = QP.FilePickerCtrl( self )
self._show_extended_single_file_info_in_status_bar = QW.QCheckBox( self )
- tt = 'This will show the info texts you see in the top hover window of the media viewer in the main gui status bar any time a single thumbnail is selected.'
+ tt = 'This will show, any time you have a single thumbnail selected, the file info summary you see in the top hover window of the media viewer in the main gui status bar. Check the "media viewer" options panel to edit this summary more.'
self._show_extended_single_file_info_in_status_bar.setToolTip( ClientGUIFunctions.WrapToolTip( tt ) )
#
diff --git a/hydrus/client/gui/panels/ClientGUIScrolledPanels.py b/hydrus/client/gui/panels/ClientGUIScrolledPanels.py
index ab8b6cb30..f36acb439 100644
--- a/hydrus/client/gui/panels/ClientGUIScrolledPanels.py
+++ b/hydrus/client/gui/panels/ClientGUIScrolledPanels.py
@@ -17,7 +17,7 @@ def eventFilter( self, watched, event ):
try:
- if event.type() == QC.QEvent.Resize:
+ if event.type() == QC.QEvent.Type.Resize:
parent = self.parent()
diff --git a/hydrus/client/gui/panels/ClientGUIScrolledPanelsCommitFiltering.py b/hydrus/client/gui/panels/ClientGUIScrolledPanelsCommitFiltering.py
index 15843a783..35442b4c1 100644
--- a/hydrus/client/gui/panels/ClientGUIScrolledPanelsCommitFiltering.py
+++ b/hydrus/client/gui/panels/ClientGUIScrolledPanelsCommitFiltering.py
@@ -76,14 +76,14 @@ def __init__( self, parent, label ):
st = ClientGUICommon.BetterStaticText( self, label )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._commit, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
st = ClientGUICommon.BetterStaticText( self, '-or-' )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._back, CC.FLAGS_EXPAND_PERPENDICULAR )
@@ -117,7 +117,7 @@ def __init__( self, parent, kept_label: typing.Optional[ str ], deletion_options
st = ClientGUICommon.BetterStaticText( self, label )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
@@ -141,7 +141,7 @@ def __init__( self, parent, kept_label: typing.Optional[ str ], deletion_options
st = ClientGUICommon.BetterStaticText( self, label )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
@@ -158,7 +158,7 @@ def __init__( self, parent, kept_label: typing.Optional[ str ], deletion_options
st = ClientGUICommon.BetterStaticText( self, label )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
@@ -172,7 +172,7 @@ def __init__( self, parent, kept_label: typing.Optional[ str ], deletion_options
st = ClientGUICommon.BetterStaticText( self, label )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
@@ -216,7 +216,7 @@ def do_it():
st = ClientGUICommon.BetterStaticText( self, '-or-' )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._back, CC.FLAGS_EXPAND_PERPENDICULAR )
@@ -295,14 +295,14 @@ def cancel_callback( parent ):
st = ClientGUICommon.BetterStaticText( self, label )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
st = ClientGUICommon.BetterStaticText( self, '-or-' )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._back, CC.FLAGS_EXPAND_PERPENDICULAR )
diff --git a/hydrus/client/gui/panels/ClientGUIScrolledPanelsEdit.py b/hydrus/client/gui/panels/ClientGUIScrolledPanelsEdit.py
index 8effefb32..05995c19f 100644
--- a/hydrus/client/gui/panels/ClientGUIScrolledPanelsEdit.py
+++ b/hydrus/client/gui/panels/ClientGUIScrolledPanelsEdit.py
@@ -1009,11 +1009,11 @@ def _SetFocus( self ):
if self._action_radio.isEnabled():
- self._action_radio.setFocus( QC.Qt.OtherFocusReason )
+ self._action_radio.setFocus( QC.Qt.FocusReason.OtherFocusReason )
elif self._reason_panel.isEnabled():
- self._reason_radio.setFocus( QC.Qt.OtherFocusReason )
+ self._reason_radio.setFocus( QC.Qt.FocusReason.OtherFocusReason )
@@ -2984,7 +2984,7 @@ def _RemoveURL( self, url ):
def _SetSearchFocus( self ):
- self._url_input.setFocus( QC.Qt.OtherFocusReason )
+ self._url_input.setFocus( QC.Qt.FocusReason.OtherFocusReason )
def _UpdateList( self ):
@@ -3016,7 +3016,7 @@ def _UpdateList( self ):
item = QW.QListWidgetItem()
item.setText( label )
- item.setData( QC.Qt.UserRole, url )
+ item.setData( QC.Qt.ItemDataRole.UserRole, url )
self._urls_listbox.addItem( item )
diff --git a/hydrus/client/gui/panels/ClientGUIScrolledPanelsReview.py b/hydrus/client/gui/panels/ClientGUIScrolledPanelsReview.py
index 23646062d..e6d0e49bb 100644
--- a/hydrus/client/gui/panels/ClientGUIScrolledPanelsReview.py
+++ b/hydrus/client/gui/panels/ClientGUIScrolledPanelsReview.py
@@ -46,6 +46,7 @@
from hydrus.client.gui.importing import ClientGUIImportOptions
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
from hydrus.client.gui.lists import ClientGUIListCtrl
+from hydrus.client.gui.metadata import ClientGUITime
from hydrus.client.gui.panels import ClientGUIScrolledPanels
from hydrus.client.gui.search import ClientGUIACDropdown
from hydrus.client.gui.widgets import ClientGUICommon
@@ -83,19 +84,19 @@ def __init__( self, parent, name, version, description_versions, description_ava
#
desc_label = ClientGUICommon.BetterStaticText( self, description_versions )
- desc_label.setAlignment( QC.Qt.AlignHCenter | QC.Qt.AlignVCenter )
+ desc_label.setAlignment( QC.Qt.AlignmentFlag.AlignHCenter | QC.Qt.AlignmentFlag.AlignVCenter )
#
availability_label = ClientGUICommon.BetterStaticText( self, description_availability )
- availability_label.setAlignment( QC.Qt.AlignHCenter | QC.Qt.AlignVCenter )
+ availability_label.setAlignment( QC.Qt.AlignmentFlag.AlignHCenter | QC.Qt.AlignmentFlag.AlignVCenter )
#
credits = QW.QTextEdit( self )
credits.setPlainText( 'Created by ' + ', '.join( developers ) )
credits.setReadOnly( True )
- credits.setAlignment( QC.Qt.AlignHCenter )
+ credits.setAlignment( QC.Qt.AlignmentFlag.AlignHCenter )
license_textedit = QW.QTextEdit( self )
license_textedit.setPlainText( license_text )
@@ -187,7 +188,7 @@ def __init__( self, parent: QW.QWidget, controller: "CG.ClientController.Control
self._thumbnails_location_clear = ClientGUICommon.BetterButton( file_locations_panel, 'clear', self._ClearThumbnailLocation )
self._rebalance_status_st = ClientGUICommon.BetterStaticText( file_locations_panel )
- self._rebalance_status_st.setAlignment( QC.Qt.AlignRight | QC.Qt.AlignVCenter )
+ self._rebalance_status_st.setAlignment( QC.Qt.AlignmentFlag.AlignRight | QC.Qt.AlignmentFlag.AlignVCenter )
self._rebalance_button = ClientGUICommon.BetterButton( file_locations_panel, 'move files now', self._Rebalance )
@@ -689,6 +690,7 @@ def _Rebalance( self ):
yes_tuples.append( ( 'run for 10 minutes', 600 ) )
yes_tuples.append( ( 'run for 30 minutes', 1800 ) )
yes_tuples.append( ( 'run for 1 hour', 3600 ) )
+ yes_tuples.append( ( 'run for custom time', -1 ) )
yes_tuples.append( ( 'run indefinitely', None ) )
try:
@@ -706,6 +708,31 @@ def _Rebalance( self ):
else:
+ if result == -1:
+
+ with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'set time to run' ) as dlg:
+
+ panel = ClientGUIScrolledPanels.EditSingleCtrlPanel( dlg )
+
+ control = ClientGUITime.TimeDeltaCtrl( self, min = 60, days = False, hours = True, minutes = True )
+
+ control.SetValue( 7200 )
+
+ panel.SetControl( control, perpendicular = True )
+
+ dlg.SetPanel( panel )
+
+ if dlg.exec() == QW.QDialog.DialogCode.Accepted:
+
+ result = int( control.GetValue() )
+
+ else:
+
+ return
+
+
+
+
stop_time = HydrusTime.GetNow() + result
@@ -971,7 +998,7 @@ def __init__( self, parent, network_engine ):
st = ClientGUICommon.BetterStaticText( self, label = 'To import, drag-and-drop hydrus\'s special downloader-encoded pngs onto Lain. Or click her to open a file selection dialog, or copy the png bitmap, file path, or raw downloader JSON to your clipboard and hit the paste button.' )
st.setWordWrap( True )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
lain_path = os.path.join( HC.STATIC_DIR, 'lain.jpg' )
@@ -979,7 +1006,7 @@ def __init__( self, parent, network_engine ):
win = ClientGUICommon.BufferedWindowIcon( self, lain_qt_pixmap )
- win.setCursor( QG.QCursor( QC.Qt.PointingHandCursor ) )
+ win.setCursor( QG.QCursor( QC.Qt.CursorShape.PointingHandCursor ) )
self._select_from_list = QW.QCheckBox( self )
self._select_from_list.setToolTip( ClientGUIFunctions.WrapToolTip( 'If the payload includes multiple objects (most do), select what you want to import.' ) )
@@ -1518,7 +1545,7 @@ def __init__( self, parent, mime: int, exif_dict: typing.Optional[ dict ], file_
st = ClientGUICommon.BetterStaticText( exif_panel, label = label )
st.setWordWrap( True )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
exif_panel.Add( st, CC.FLAGS_EXPAND_PERPENDICULAR )
exif_panel.Add( self._exif_listctrl, CC.FLAGS_EXPAND_BOTH_WAYS )
@@ -1692,7 +1719,7 @@ def __init__( self, parent ):
)
self._loading_text = ClientGUICommon.BetterStaticText( self._search_panel )
- self._loading_text.setAlignment( QC.Qt.AlignVCenter | QC.Qt.AlignRight )
+ self._loading_text.setAlignment( QC.Qt.AlignmentFlag.AlignVCenter | QC.Qt.AlignmentFlag.AlignRight )
self._cancel_button = ClientGUICommon.BetterBitmapButton( self._search_panel, CC.global_pixmaps().stop, self._CancelCurrentSearch )
self._refresh_button = ClientGUICommon.BetterBitmapButton( self._search_panel, CC.global_pixmaps().refresh, self._RefreshSearch )
@@ -2372,7 +2399,7 @@ def __init__( self, parent ):
)
self._loading_text = ClientGUICommon.BetterStaticText( self._search_panel )
- self._loading_text.setAlignment( QC.Qt.AlignVCenter | QC.Qt.AlignRight )
+ self._loading_text.setAlignment( QC.Qt.AlignmentFlag.AlignVCenter | QC.Qt.AlignmentFlag.AlignRight )
self._cancel_button = ClientGUICommon.BetterBitmapButton( self._search_panel, CC.global_pixmaps().stop, self._CancelCurrentSearch )
self._refresh_button = ClientGUICommon.BetterBitmapButton( self._search_panel, CC.global_pixmaps().refresh, self._RefreshSearch )
@@ -2864,7 +2891,7 @@ def __init__( self, parent, paths = None ):
self._cog_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().cog, menu_items )
self._delete_after_success_st = ClientGUICommon.BetterStaticText( self )
- self._delete_after_success_st.setAlignment( QC.Qt.AlignRight | QC.Qt.AlignVCenter )
+ self._delete_after_success_st.setAlignment( QC.Qt.AlignmentFlag.AlignRight | QC.Qt.AlignmentFlag.AlignVCenter )
self._delete_after_success_st.setObjectName( 'HydrusWarning' )
self._delete_after_success = QW.QCheckBox( 'delete original files after successful import', self )
diff --git a/hydrus/client/gui/panels/ClientGUIScrolledPanelsSelectFromList.py b/hydrus/client/gui/panels/ClientGUIScrolledPanelsSelectFromList.py
index ed2efcc4a..2f9c0a922 100644
--- a/hydrus/client/gui/panels/ClientGUIScrolledPanelsSelectFromList.py
+++ b/hydrus/client/gui/panels/ClientGUIScrolledPanelsSelectFromList.py
@@ -43,7 +43,7 @@ def __init__( self, parent: QW.QWidget, choice_tuples: list, value_to_select = N
item = QW.QListWidgetItem()
item.setText( label )
- item.setData( QC.Qt.UserRole, value )
+ item.setData( QC.Qt.ItemDataRole.UserRole, value )
self._list.addItem( item )
diff --git a/hydrus/client/gui/parsing/ClientGUIParsingFormulae.py b/hydrus/client/gui/parsing/ClientGUIParsingFormulae.py
index a31244731..6697c476f 100644
--- a/hydrus/client/gui/parsing/ClientGUIParsingFormulae.py
+++ b/hydrus/client/gui/parsing/ClientGUIParsingFormulae.py
@@ -723,7 +723,7 @@ def __init__( self, parent: QW.QWidget, collapse_newlines: bool, formula: Client
item = QW.QListWidgetItem()
item.setText( pretty_rule )
- item.setData( QC.Qt.UserRole, rule )
+ item.setData( QC.Qt.ItemDataRole.UserRole, rule )
self._tag_rules.addItem( item )
@@ -836,7 +836,7 @@ def Add( self ):
item = QW.QListWidgetItem()
item.setText( pretty_rule )
- item.setData( QC.Qt.UserRole, rule )
+ item.setData( QC.Qt.ItemDataRole.UserRole, rule )
self._tag_rules.addItem( item )
@@ -875,7 +875,7 @@ def Edit( self ):
pretty_rule = rule.ToString()
self._tag_rules.item( selection ).setText( pretty_rule )
- self._tag_rules.item( selection ).setData( QC.Qt.UserRole, rule )
+ self._tag_rules.item( selection ).setData( QC.Qt.ItemDataRole.UserRole, rule )
@@ -922,7 +922,7 @@ def MoveDown( self ):
item = QW.QListWidgetItem()
item.setText( pretty_rule )
- item.setData( QC.Qt.UserRole, rule )
+ item.setData( QC.Qt.ItemDataRole.UserRole, rule )
self._tag_rules.insertItem( selection + 1, item )
@@ -940,7 +940,7 @@ def MoveUp( self ):
item = QW.QListWidgetItem()
item.setText( pretty_rule )
- item.setData( QC.Qt.UserRole, rule )
+ item.setData( QC.Qt.ItemDataRole.UserRole, rule )
self._tag_rules.insertItem( selection - 1, item )
@@ -1121,7 +1121,7 @@ def __init__( self, parent: QW.QWidget, collapse_newlines: bool, formula: Client
item = QW.QListWidgetItem()
item.setText( pretty_rule )
- item.setData( QC.Qt.UserRole, rule )
+ item.setData( QC.Qt.ItemDataRole.UserRole, rule )
self._parse_rules.addItem( item )
@@ -1217,7 +1217,7 @@ def Add( self ):
item = QW.QListWidgetItem()
item.setText( pretty_rule )
- item.setData( QC.Qt.UserRole, rule )
+ item.setData( QC.Qt.ItemDataRole.UserRole, rule )
self._parse_rules.addItem( item )
@@ -1256,7 +1256,7 @@ def Edit( self ):
pretty_rule = ClientParsing.RenderJSONParseRule( rule )
self._parse_rules.item( selection ).setText( pretty_rule )
- self._parse_rules.item( selection ).setData( QC.Qt.UserRole, rule )
+ self._parse_rules.item( selection ).setData( QC.Qt.ItemDataRole.UserRole, rule )
@@ -1290,7 +1290,7 @@ def MoveDown( self ):
item = QW.QListWidgetItem()
item.setText( pretty_rule )
- item.setData( QC.Qt.UserRole, rule )
+ item.setData( QC.Qt.ItemDataRole.UserRole, rule )
self._parse_rules.insertItem( selection + 1, item )
@@ -1308,7 +1308,7 @@ def MoveUp( self ):
item = QW.QListWidgetItem()
item.setText( pretty_rule )
- item.setData( QC.Qt.UserRole, rule )
+ item.setData( QC.Qt.ItemDataRole.UserRole, rule )
self._parse_rules.insertItem( selection - 1, item )
diff --git a/hydrus/client/gui/search/ClientGUIACDropdown.py b/hydrus/client/gui/search/ClientGUIACDropdown.py
index 9d49c8ef9..5e07c33ee 100644
--- a/hydrus/client/gui/search/ClientGUIACDropdown.py
+++ b/hydrus/client/gui/search/ClientGUIACDropdown.py
@@ -1061,7 +1061,7 @@ def _HandleEscape( self ):
elif self._float_mode:
- self.parentWidget().setFocus( QC.Qt.OtherFocusReason )
+ self.parentWidget().setFocus( QC.Qt.FocusReason.OtherFocusReason )
return True
@@ -1242,7 +1242,7 @@ def eventFilter( self, watched, event ):
if watched == self._text_ctrl:
- if event.type() == QC.QEvent.KeyPress and self._can_intercept_unusual_key_events:
+ if event.type() == QC.QEvent.Type.KeyPress and self._can_intercept_unusual_key_events:
# ok for a while this thing was a mis-mash of logical tests and basically sending anything not explicitly caught to the list
# this resulted in annoying miss-cases where ctrl+c et al were being passed to the list and so you couldn't copy text from the text input
@@ -1254,24 +1254,24 @@ def eventFilter( self, watched, event ):
send_input_to_current_list = False
- ctrl = event.modifiers() & QC.Qt.ControlModifier
+ ctrl = event.modifiers() & QC.Qt.KeyboardModifier.ControlModifier
# previous/next hardcoded shortcuts, should obviously be migrated to a user-customised shortcut set in future!
crazy_n_p_hardcodes = ctrl and key in ( ord( 'P' ), ord( 'p' ), ord( 'N' ), ord( 'n' ) )
- we_copying = ctrl and key in( ord( 'C' ), ord( 'c' ), QC.Qt.Key_Insert )
+ we_copying = ctrl and key in( ord( 'C' ), ord( 'c' ), QC.Qt.Key.Key_Insert )
we_copying_the_list = we_copying and self._text_ctrl.selectedText() == ''
- if key in ( QC.Qt.Key_Up, QC.Qt.Key_Down, QC.Qt.Key_PageDown, QC.Qt.Key_PageUp, QC.Qt.Key_Home, QC.Qt.Key_End ) or crazy_n_p_hardcodes or we_copying_the_list:
+ if key in ( QC.Qt.Key.Key_Up, QC.Qt.Key.Key_Down, QC.Qt.Key.Key_PageDown, QC.Qt.Key.Key_PageUp, QC.Qt.Key.Key_Home, QC.Qt.Key.Key_End ) or crazy_n_p_hardcodes or we_copying_the_list:
send_input_to_current_list = True
- elif key in ( QC.Qt.Key_Return, QC.Qt.Key_Enter ):
+ elif key in ( QC.Qt.Key.Key_Return, QC.Qt.Key.Key_Enter ):
if self._ShouldBroadcastCurrentInputOnEnterKey():
- shift_down = modifier == QC.Qt.ShiftModifier
+ shift_down = modifier == QC.Qt.KeyboardModifier.ShiftModifier
self._BroadcastCurrentInputFromEnterKey( shift_down )
@@ -1284,7 +1284,7 @@ def eventFilter( self, watched, event ):
send_input_to_current_list = True
- elif key == QC.Qt.Key_Escape:
+ elif key == QC.Qt.Key.Key_Escape:
escape_caught = self._HandleEscape()
@@ -1309,7 +1309,7 @@ def eventFilter( self, watched, event ):
return event.isAccepted()
- elif event.type() == QC.QEvent.Wheel:
+ elif event.type() == QC.QEvent.Type.Wheel:
current_results_list = typing.cast( ClientGUIListBoxes.ListBoxTags, self._dropdown_notebook.currentWidget() )
@@ -1328,7 +1328,7 @@ def eventFilter( self, watched, event ):
return True
- elif event.modifiers() & QC.Qt.ControlModifier:
+ elif event.modifiers() & QC.Qt.KeyboardModifier.ControlModifier:
if event.angleDelta().y() > 0:
@@ -1356,13 +1356,13 @@ def eventFilter( self, watched, event ):
# I could probably wangle this garbagewith setFocusProxy on all the children of the dropdown, assuming that wouldn't break anything, but this seems to work ok nonetheless
- if event.type() == QC.QEvent.FocusIn:
+ if event.type() == QC.QEvent.Type.FocusIn:
self._DropdownHideShow()
return False
- elif event.type() == QC.QEvent.FocusOut:
+ elif event.type() == QC.QEvent.Type.FocusOut:
current_focus_widget = QW.QApplication.focusWidget()
@@ -1383,7 +1383,7 @@ def eventFilter( self, watched, event ):
elif self._temporary_focus_widget is not None and watched == self._temporary_focus_widget:
- if self._float_mode and event.type() == QC.QEvent.FocusOut:
+ if self._float_mode and event.type() == QC.QEvent.Type.FocusOut:
self._temporary_focus_widget.removeEventFilter( self )
@@ -1490,7 +1490,7 @@ def MoveNotebookPageFocus( self, index = None, direction = None ):
self._dropdown_notebook.setCurrentIndex( new_index )
- self.setFocus( QC.Qt.OtherFocusReason )
+ self.setFocus( QC.Qt.FocusReason.OtherFocusReason )
diff --git a/hydrus/client/gui/search/ClientGUIPredicatesMultiple.py b/hydrus/client/gui/search/ClientGUIPredicatesMultiple.py
deleted file mode 100644
index 26ea42c03..000000000
--- a/hydrus/client/gui/search/ClientGUIPredicatesMultiple.py
+++ /dev/null
@@ -1,596 +0,0 @@
-import typing
-
-from qtpy import QtCore as QC
-from qtpy import QtWidgets as QW
-
-from hydrus.core import HydrusConstants as HC
-
-from hydrus.client import ClientConstants as CC
-from hydrus.client import ClientGlobals as CG
-from hydrus.client.gui import ClientGUIFunctions
-from hydrus.client.gui import ClientGUIRatings
-from hydrus.client.gui import QtPorting as QP
-from hydrus.client.gui.search import ClientGUIPredicatesSingle
-from hydrus.client.gui.widgets import ClientGUICommon
-from hydrus.client.metadata import ClientRatings
-from hydrus.client.search import ClientSearchPredicate
-
-class PredicateSystemRatingIncDecControl( QW.QWidget ):
-
- def __init__( self, parent: QW.QWidget, service_key: bytes, predicate: typing.Optional[ ClientSearchPredicate.Predicate ] ):
-
- super().__init__( parent )
-
- self._service_key = service_key
-
- service = CG.client_controller.services_manager.GetService( self._service_key )
-
- name = service.GetName()
-
- name_st = ClientGUICommon.BetterStaticText( self, name )
-
- name_st.setAlignment( QC.Qt.AlignLeft | QC.Qt.AlignVCenter )
-
- choice_tuples = [
- ( 'more than', '>' ),
- ( 'less than', '<' ),
- ( 'is', '=' ),
- ( 'is about', HC.UNICODE_APPROX_EQUAL ),
- ( 'do not search', '' )
- ]
-
- self._choice = ClientGUICommon.BetterRadioBox( self, choice_tuples, vertical = True )
-
- self._rating_value = ClientGUICommon.BetterSpinBox( self, initial = 0, min = 0, max = 1000000 )
-
- self._choice.SetValue( '' )
-
- #
-
- if predicate is not None:
-
- value = predicate.GetValue()
-
- if value is not None:
-
- ( operator, rating, service_key ) = value
-
- self._choice.SetValue( operator )
-
- self._rating_value.setValue( rating )
-
-
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, name_st, CC.FLAGS_EXPAND_BOTH_WAYS )
- QP.AddToLayout( hbox, self._choice, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._rating_value, CC.FLAGS_CENTER_PERPENDICULAR )
-
- self.setLayout( hbox )
-
- self._choice.radioBoxChanged.connect( self._UpdateControls )
-
- self._UpdateControls()
-
-
- def _UpdateControls( self ):
-
- choice = self._choice.GetValue()
-
- spinctrl_matters = choice != ''
-
- self._rating_value.setEnabled( spinctrl_matters )
-
-
- def GetPredicates( self ):
-
- choice = self._choice.GetValue()
-
- if choice == '':
-
- return []
-
-
- operator = choice
-
- rating = self._rating_value.value()
-
- predicate = ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING, ( operator, rating, self._service_key ) )
-
- return [ predicate ]
-
-
-class PredicateSystemRatingLikeControl( QW.QWidget ):
-
- def __init__( self, parent: QW.QWidget, service_key: bytes, predicate: typing.Optional[ ClientSearchPredicate.Predicate ] ):
-
- super().__init__( parent )
-
- self.setToolTip( ClientGUIFunctions.WrapToolTip( 'Set "is" and leave rating null to search for "unrated".' ) )
-
- self._service_key = service_key
-
- service = CG.client_controller.services_manager.GetService( self._service_key )
-
- name = service.GetName()
-
- name_st = ClientGUICommon.BetterStaticText( self, name )
-
- name_st.setAlignment( QC.Qt.AlignLeft | QC.Qt.AlignVCenter )
-
- choice_tuples = [
- ( 'has rating', 'rated' ),
- ( 'is', '=' ),
- ( 'do not search', '' )
- ]
-
- self._choice = ClientGUICommon.BetterRadioBox( self, choice_tuples, vertical = True )
-
- self._rating_control = ClientGUIRatings.RatingLikeDialog( self, service_key )
-
- #
-
- self._choice.SetValue( '' )
-
- if predicate is not None:
-
- value = predicate.GetValue()
-
- if value is not None:
-
- ( operator, rating, service_key ) = value
-
- if rating == 'rated':
-
- self._choice.SetValue( 'rated' )
-
- else:
-
- self._choice.SetValue( '=' )
-
- if rating == 'not rated':
-
- self._rating_control.SetRatingState( ClientRatings.NULL )
-
- elif rating == 0:
-
- self._rating_control.SetRatingState( ClientRatings.DISLIKE )
-
- else:
-
- self._rating_control.SetRatingState( ClientRatings.LIKE )
-
-
-
-
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, name_st, CC.FLAGS_EXPAND_BOTH_WAYS )
- QP.AddToLayout( hbox, self._choice, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._rating_control, CC.FLAGS_CENTER_PERPENDICULAR )
-
- self.setLayout( hbox )
-
- self._choice.radioBoxChanged.connect( self._UpdateControls )
- self._rating_control.valueChanged.connect( self._RatingChanged )
-
- self._UpdateControls()
-
-
- def _RatingChanged( self ):
-
- if self._choice.GetValue() in ( 'rated', '' ):
-
- self._choice.SetValue( '=' )
-
-
-
- def _UpdateControls( self ):
-
- choice = self._choice.GetValue()
-
- if choice in ( 'rated', '' ):
-
- self._rating_control.blockSignals( True )
- self._rating_control.SetRatingState( ClientRatings.NULL )
- self._rating_control.blockSignals( False )
-
-
-
- def GetPredicates( self ):
-
- choice = self._choice.GetValue()
-
- if choice == '':
-
- return []
-
-
- if choice == 'rated':
-
- rating = 'rated'
-
- else:
-
- rating_state = self._rating_control.GetRatingState()
-
- if rating_state == ClientRatings.LIKE:
-
- rating = 1
-
- elif rating_state == ClientRatings.DISLIKE:
-
- rating = 0
-
- else:
-
- rating = 'not rated'
-
-
-
- predicate = ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING, ( '=', rating, self._service_key ) )
-
- return [ predicate ]
-
-
-class PredicateSystemRatingNumericalControl( QW.QWidget ):
-
- def __init__( self, parent: QW.QWidget, service_key: bytes, predicate: typing.Optional[ ClientSearchPredicate.Predicate ] ):
-
- super().__init__( parent )
-
- self.setToolTip( ClientGUIFunctions.WrapToolTip( 'Set "is" and leave rating null to search for "unrated".' ) )
-
- self._service_key = service_key
-
- service = CG.client_controller.services_manager.GetService( self._service_key )
-
- name = service.GetName()
-
- name_st = ClientGUICommon.BetterStaticText( self, name )
-
- name_st.setAlignment( QC.Qt.AlignLeft | QC.Qt.AlignVCenter )
-
- choice_tuples = [
- ( 'has rating', 'rated' ),
- ( 'more than', '>' ),
- ( 'less than', '<' ),
- ( 'is', '=' ),
- ( 'is about', HC.UNICODE_APPROX_EQUAL ),
- ( 'do not search', '' )
- ]
-
- self._choice = ClientGUICommon.BetterRadioBox( self, choice_tuples, vertical = True )
-
- self._rating_control = ClientGUIRatings.RatingNumericalDialog( self, service_key )
-
- self._choice.SetValue( '' )
-
- #
-
- if predicate is not None:
-
- value = predicate.GetValue()
-
- if value is not None:
-
- ( operator, rating, service_key ) = value
-
- if rating == 'rated':
-
- self._choice.SetValue( 'rated' )
-
- elif rating == 'not rated':
-
- self._choice.SetValue( '=' )
-
- self._rating_control.SetRating( None )
-
- else:
-
- self._choice.SetValue( operator )
-
- self._rating_control.SetRating( rating )
-
-
-
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, name_st, CC.FLAGS_EXPAND_BOTH_WAYS )
- QP.AddToLayout( hbox, self._choice, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._rating_control, CC.FLAGS_CENTER_PERPENDICULAR )
-
- self.setLayout( hbox )
-
- self._choice.radioBoxChanged.connect( self._UpdateControls )
- self._rating_control.valueChanged.connect( self._RatingChanged )
-
- self._UpdateControls()
-
-
- def _RatingChanged( self ):
-
- if self._choice.GetValue() in ( 'rated', '' ):
-
- self._choice.SetValue( '=' )
-
-
-
- def _UpdateControls( self ):
-
- choice = self._choice.GetValue()
-
- if choice in ( 'rated', '' ):
-
- self._rating_control.blockSignals( True )
- self._rating_control.SetRating( None )
- self._rating_control.blockSignals( False )
-
-
-
- def GetPredicates( self ):
-
- choice = self._choice.GetValue()
-
- if choice == '':
-
- return []
-
-
- operator = '='
- rating = None
-
- if choice == 'rated':
-
- operator = '='
- rating = 'rated'
-
- else:
-
- operator = choice
-
- if self._rating_control.GetRatingState() == ClientRatings.NULL:
-
- if operator != '=':
-
- return []
-
-
- rating = 'not rated'
-
- else:
-
- rating = self._rating_control.GetRating()
-
-
-
- predicate = ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING, ( operator, rating, self._service_key ) )
-
- return [ predicate ]
-
-
-class PanelPredicateSystemMultiple( ClientGUIPredicatesSingle.PanelPredicateSystem ):
-
- def _FilterWhatICanEdit( self, predicates: typing.Collection[ ClientSearchPredicate.Predicate ] ) -> typing.Collection[ ClientSearchPredicate.Predicate ]:
-
- raise NotImplementedError()
-
-
- def _GetPredicatesToInitialisePanelWith( self, predicates: typing.Collection[ ClientSearchPredicate.Predicate ] ) -> typing.Collection[ ClientSearchPredicate.Predicate ]:
-
- raise NotImplementedError()
-
-
- def ClearCustomDefault( self ):
-
- raise NotImplementedError()
-
-
- def GetDefaultPredicates( self ) -> typing.Collection[ ClientSearchPredicate.Predicate ]:
-
- raise NotImplementedError()
-
-
- def GetPredicates( self ):
-
- raise NotImplementedError()
-
-
- def SaveCustomDefault( self ):
-
- raise NotImplementedError()
-
-
- def UsesCustomDefault( self ) -> bool:
-
- raise NotImplementedError()
-
-
-class PanelPredicateSystemRating( PanelPredicateSystemMultiple ):
-
- def __init__( self, parent, predicates ):
-
- super().__init__( parent )
-
- #
-
- local_like_services = CG.client_controller.services_manager.GetServices( ( HC.LOCAL_RATING_LIKE, ) )
-
- gridbox = QP.GridLayout( cols = 5 )
-
- gridbox.setColumnStretch( 0, 1 )
-
- predicates = self._GetPredicatesToInitialisePanelWith( predicates )
-
- service_keys_to_predicates = { predicate.GetValue()[2] : predicate for predicate in predicates }
-
- self._rating_panels = []
-
- for service in local_like_services:
-
- service_key = service.GetServiceKey()
-
- if service_key in service_keys_to_predicates:
-
- predicate = service_keys_to_predicates[ service_key ]
-
- else:
-
- predicate = None
-
-
- panel = PredicateSystemRatingLikeControl( self, service_key, predicate )
-
- self._rating_panels.append( panel )
-
-
- #
-
- local_numerical_services = CG.client_controller.services_manager.GetServices( ( HC.LOCAL_RATING_NUMERICAL, ) )
-
- for service in local_numerical_services:
-
- service_key = service.GetServiceKey()
-
- if service_key in service_keys_to_predicates:
-
- predicate = service_keys_to_predicates[ service_key ]
-
- else:
-
- predicate = None
-
-
- panel = PredicateSystemRatingNumericalControl( self, service_key, predicate )
-
- self._rating_panels.append( panel )
-
-
- #
-
- local_incdec_services = CG.client_controller.services_manager.GetServices( ( HC.LOCAL_RATING_INCDEC, ) )
-
- for service in local_incdec_services:
-
- service_key = service.GetServiceKey()
-
- if service_key in service_keys_to_predicates:
-
- predicate = service_keys_to_predicates[ service_key ]
-
- else:
-
- predicate = None
-
-
- panel = PredicateSystemRatingIncDecControl( self, service_key, predicate )
-
- self._rating_panels.append( panel )
-
-
- #
-
- vbox = QP.VBoxLayout()
-
- for panel in self._rating_panels:
-
- QP.AddToLayout( vbox, panel, CC.FLAGS_EXPAND_PERPENDICULAR )
-
-
- self.setLayout( vbox )
-
-
- def _FilterWhatICanEdit( self, predicates: typing.Collection[ ClientSearchPredicate.Predicate ] ) -> typing.Collection[ ClientSearchPredicate.Predicate ]:
-
- local_rating_service_keys = CG.client_controller.services_manager.GetServiceKeys( HC.RATINGS_SERVICES )
-
- good_predicates = []
-
- for predicate in predicates:
-
- value = predicate.GetValue()
-
- if value is not None:
-
- ( operator, rating, service_key ) = value
-
- if service_key in local_rating_service_keys:
-
- good_predicates.append( predicate )
-
-
-
-
- return good_predicates
-
-
- def _GetPredicatesToInitialisePanelWith( self, predicates: typing.Collection[ ClientSearchPredicate.Predicate ] ) -> typing.Collection[ ClientSearchPredicate.Predicate ]:
-
- predicates = self._FilterWhatICanEdit( predicates )
-
- if len( predicates ) > 0:
-
- return predicates
-
-
- custom_default_predicates = CG.client_controller.new_options.GetCustomDefaultSystemPredicates( predicate_type = ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING )
-
- custom_default_predicates = self._FilterWhatICanEdit( custom_default_predicates )
-
- if len( custom_default_predicates ) > 0:
-
- return custom_default_predicates
-
-
- default_predicates = self.GetDefaultPredicates()
-
- return default_predicates
-
-
- def ClearCustomDefault( self ):
-
- CG.client_controller.new_options.ClearCustomDefaultSystemPredicates( predicate_type = ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING )
-
-
- def GetDefaultPredicates( self ):
-
- return []
-
-
- def GetPredicates( self ):
-
- predicates = []
-
- for panel in self._rating_panels:
-
- predicates.extend( panel.GetPredicates() )
-
-
- return predicates
-
-
- def SaveCustomDefault( self ):
-
- predicates = self.GetPredicates()
-
- CG.client_controller.new_options.SetCustomDefaultSystemPredicates( predicate_type = ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING, predicates = predicates )
-
-
- def UsesCustomDefault( self ) -> bool:
-
- custom_default_predicates = CG.client_controller.new_options.GetCustomDefaultSystemPredicates( predicate_type = ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING )
-
- custom_default_predicates = self._FilterWhatICanEdit( custom_default_predicates )
-
- return len( custom_default_predicates ) > 0
-
-
diff --git a/hydrus/client/gui/search/ClientGUIPredicatesSingle.py b/hydrus/client/gui/search/ClientGUIPredicatesSingle.py
index 7198590a0..786624319 100644
--- a/hydrus/client/gui/search/ClientGUIPredicatesSingle.py
+++ b/hydrus/client/gui/search/ClientGUIPredicatesSingle.py
@@ -18,12 +18,14 @@
from hydrus.client.gui import ClientGUIDialogsMessage
from hydrus.client.gui import ClientGUIFunctions
from hydrus.client.gui import ClientGUIOptionsPanels
+from hydrus.client.gui import ClientGUIRatings
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.metadata import ClientGUITime
from hydrus.client.gui.widgets import ClientGUIBytes
from hydrus.client.gui.widgets import ClientGUICommon
from hydrus.client.gui.widgets import ClientGUINumberTest
from hydrus.client.gui.widgets import ClientGUIRegex
+from hydrus.client.metadata import ClientRatings
from hydrus.client.search import ClientNumberTest
from hydrus.client.search import ClientSearchPredicate
@@ -2099,6 +2101,434 @@ def GetPredicates( self ):
+class PredicateSystemRatingIncDec( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent: QW.QWidget, service_key: bytes, predicate: typing.Optional[ ClientSearchPredicate.Predicate ] ):
+
+ super().__init__( parent )
+
+ self._service_key = service_key
+
+ try:
+
+ service = CG.client_controller.services_manager.GetService( self._service_key )
+
+ name = service.GetName()
+
+ except:
+
+ name = 'unknown service'
+
+
+ name_st = ClientGUICommon.BetterStaticText( self, name )
+
+ name_st.setAlignment( QC.Qt.AlignmentFlag.AlignLeft | QC.Qt.AlignmentFlag.AlignVCenter )
+
+ choice_tuples = [
+ ( 'has count', 'has count' ),
+ ( 'no count', 'no count' ),
+ ( 'more than', '>' ),
+ ( 'less than', '<' ),
+ ( 'is', '=' ),
+ ( 'is about', HC.UNICODE_APPROX_EQUAL )
+ ]
+
+ self._choice = ClientGUICommon.BetterRadioBox( self, choice_tuples, vertical = True )
+
+ self._rating_value = ClientGUICommon.BetterSpinBox( self, initial = 0, min = 0, max = 1000000 )
+
+ self._choice.SetValue( '' )
+
+ #
+
+ if predicate is not None:
+
+ value = predicate.GetValue()
+
+ if value is not None:
+
+ ( operator, rating, service_key ) = value
+
+ self._rating_value.setValue( 0 )
+
+ if operator == '>' and rating == 0:
+
+ self._choice.SetValue( 'has count' )
+
+ elif ( operator == '<' and rating == 1 ) or ( operator == '=' and rating == '0' ):
+
+ self._choice.SetValue( 'no count' )
+
+ else:
+
+ self._choice.SetValue( operator )
+
+ self._rating_value.setValue( rating )
+
+
+
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, name_st, CC.FLAGS_EXPAND_BOTH_WAYS )
+ QP.AddToLayout( hbox, self._choice, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._rating_value, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ self.setLayout( hbox )
+
+ self._choice.radioBoxChanged.connect( self._UpdateControls )
+
+ self._UpdateControls()
+
+
+ def _UpdateControls( self ):
+
+ choice = self._choice.GetValue()
+
+ spinctrl_matters = choice not in ( 'has count', 'no count' )
+
+ self._rating_value.setEnabled( spinctrl_matters )
+
+
+ def GetDefaultPredicate( self ):
+
+ return ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING, ( '>', 0, self._service_key ) )
+
+
+ def GetPredicates( self ):
+
+ choice = self._choice.GetValue()
+
+ if choice == 'has count':
+
+ operator = '>'
+ rating = 0
+
+ elif choice == 'no count':
+
+ operator = '='
+ rating = 0
+
+ else:
+
+ operator = choice
+
+ rating = self._rating_value.value()
+
+
+ predicate = ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING, ( operator, rating, self._service_key ) )
+
+ return [ predicate ]
+
+
+
+class PredicateSystemRatingLike( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent: QW.QWidget, service_key: bytes, predicate: typing.Optional[ ClientSearchPredicate.Predicate ] ):
+
+ super().__init__( parent )
+
+ self._service_key = service_key
+
+ try:
+
+ service = CG.client_controller.services_manager.GetService( self._service_key )
+
+ name = service.GetName()
+
+ except HydrusExceptions.DataMissing:
+
+ name = 'unknown service'
+
+
+ name_st = ClientGUICommon.BetterStaticText( self, name )
+
+ name_st.setAlignment( QC.Qt.AlignmentFlag.AlignLeft | QC.Qt.AlignmentFlag.AlignVCenter )
+
+ choice_tuples = [
+ ( 'has rating', 'rated' ),
+ ( 'no rating', 'not rated' ),
+ ( 'is', '=' )
+ ]
+
+ self._choice = ClientGUICommon.BetterRadioBox( self, choice_tuples, vertical = True )
+
+ self._rating_control = ClientGUIRatings.RatingLikeDialog( self, service_key )
+
+ #
+
+ self._choice.SetValue( '' )
+
+ if predicate is not None:
+
+ value = predicate.GetValue()
+
+ if value is not None:
+
+ ( operator, rating, service_key ) = value
+
+ self._rating_control.SetRatingState( ClientRatings.NULL )
+
+ if rating == 'rated':
+
+ self._choice.SetValue( 'rated' )
+
+ elif rating == 'not rated':
+
+ self._choice.SetValue( 'not rated' )
+
+ else:
+
+ self._choice.SetValue( '=' )
+
+ if rating == 0:
+
+ self._rating_control.SetRatingState( ClientRatings.DISLIKE )
+
+ elif rating == 1:
+
+ self._rating_control.SetRatingState( ClientRatings.LIKE )
+
+
+
+
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, name_st, CC.FLAGS_EXPAND_BOTH_WAYS )
+ QP.AddToLayout( hbox, self._choice, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._rating_control, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ self.setLayout( hbox )
+
+ self._choice.radioBoxChanged.connect( self._UpdateControls )
+ self._rating_control.valueChanged.connect( self._RatingChanged )
+
+ self._UpdateControls()
+
+
+ def _RatingChanged( self ):
+
+ if self._choice.GetValue() in ( 'rated', 'not rated' ):
+
+ self._choice.SetValue( '=' )
+
+
+
+ def _UpdateControls( self ):
+
+ choice = self._choice.GetValue()
+
+ if choice in ( 'rated', 'not rated' ):
+
+ self._rating_control.blockSignals( True )
+ self._rating_control.SetRatingState( ClientRatings.NULL )
+ self._rating_control.blockSignals( False )
+
+
+
+ def GetDefaultPredicate( self ):
+
+ return ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING, ( '=', 'rated', self._service_key ) )
+
+
+ def GetPredicates( self ):
+
+ choice = self._choice.GetValue()
+
+ if choice == '':
+
+ return []
+
+
+ operator = '='
+
+ if choice == 'rated':
+
+ rating = 'rated'
+
+ elif choice == 'not rated':
+
+ rating = 'not rated'
+
+ else:
+
+ rating_state = self._rating_control.GetRatingState()
+
+ if rating_state == ClientRatings.LIKE:
+
+ rating = 1
+
+ elif rating_state == ClientRatings.DISLIKE:
+
+ rating = 0
+
+ else:
+
+ rating = 'not rated'
+
+
+
+ predicate = ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING, ( operator, rating, self._service_key ) )
+
+ return [ predicate ]
+
+
+
+class PredicateSystemRatingNumerical( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent: QW.QWidget, service_key: bytes, predicate: typing.Optional[ ClientSearchPredicate.Predicate ] ):
+
+ super().__init__( parent )
+
+ self._service_key = service_key
+
+ try:
+
+ service = CG.client_controller.services_manager.GetService( self._service_key )
+
+ name = service.GetName()
+
+ except HydrusExceptions.DataMissing:
+
+ name = 'unknown service'
+
+
+ name_st = ClientGUICommon.BetterStaticText( self, name )
+
+ name_st.setAlignment( QC.Qt.AlignmentFlag.AlignLeft | QC.Qt.AlignmentFlag.AlignVCenter )
+
+ choice_tuples = [
+ ( 'has rating', 'rated' ),
+ ( 'no rating', 'not rated' ),
+ ( 'more than', '>' ),
+ ( 'less than', '<' ),
+ ( 'is', '=' ),
+ ( 'is about', HC.UNICODE_APPROX_EQUAL )
+ ]
+
+ self._choice = ClientGUICommon.BetterRadioBox( self, choice_tuples, vertical = True )
+
+ self._rating_control = ClientGUIRatings.RatingNumericalDialog( self, service_key )
+
+ self._choice.SetValue( '' )
+
+ #
+
+ if predicate is not None:
+
+ value = predicate.GetValue()
+
+ if value is not None:
+
+ ( operator, rating, service_key ) = value
+
+ self._rating_control.SetRating( None )
+
+ if rating == 'rated':
+
+ self._choice.SetValue( 'rated' )
+
+ elif rating == 'not rated':
+
+ self._choice.SetValue( 'not rated' )
+
+ else:
+
+ self._choice.SetValue( operator )
+
+ self._rating_control.SetRating( rating )
+
+
+
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, name_st, CC.FLAGS_EXPAND_BOTH_WAYS )
+ QP.AddToLayout( hbox, self._choice, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._rating_control, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ self.setLayout( hbox )
+
+ self._choice.radioBoxChanged.connect( self._UpdateControls )
+ self._rating_control.valueChanged.connect( self._RatingChanged )
+
+ self._UpdateControls()
+
+
+ def _RatingChanged( self ):
+
+ if self._choice.GetValue() in ( 'rated', 'not rated' ):
+
+ self._choice.SetValue( '=' )
+
+
+
+ def _UpdateControls( self ):
+
+ choice = self._choice.GetValue()
+
+ if choice in ( 'rated', 'not rated' ):
+
+ self._rating_control.blockSignals( True )
+ self._rating_control.SetRating( None )
+ self._rating_control.blockSignals( False )
+
+
+
+ def GetDefaultPredicate( self ):
+
+ return ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING, ( '=', 'rated', self._service_key ) )
+
+
+ def GetPredicates( self ):
+
+ choice = self._choice.GetValue()
+
+ operator = '='
+ rating = None
+
+ if choice == 'rated':
+
+ operator = '='
+ rating = 'rated'
+
+ elif choice == 'not rated':
+
+ operator = '='
+ rating = 'not rated'
+
+ else:
+
+ operator = choice
+
+ if self._rating_control.GetRatingState() == ClientRatings.NULL:
+
+ if operator != '=':
+
+ return []
+
+
+ rating = 'not rated'
+
+ else:
+
+ rating = self._rating_control.GetRating()
+
+
+
+ predicate = ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING, ( operator, rating, self._service_key ) )
+
+ return [ predicate ]
+
+
+
class PanelPredicateSystemRatio( PanelPredicateSystemSingle ):
def __init__( self, parent, predicate ):
@@ -2188,8 +2618,8 @@ def __init__( self, parent, predicate ):
predicate = self._GetPredicateToInitialisePanelWith( predicate )
- self._pixel_hashes.setPlaceholderText( 'enter pixel hash (64 chars each, paste newline-separated for multiple)' )
- self._perceptual_hashes.setPlaceholderText( 'enter perceptual hash (16 chars each, paste newline-separated for multiple)' )
+ self._pixel_hashes.setPlaceholderText( 'pixel hash (64 chars each, paste newline-separated for multiple)' )
+ self._perceptual_hashes.setPlaceholderText( 'perceptual hash (16 chars each, paste newline-separated for multiple)' )
( pixel_hashes, perceptual_hashes, hamming_distance ) = predicate.GetValue()
@@ -2221,10 +2651,10 @@ def __init__( self, parent, predicate ):
big_vbox = QP.VBoxLayout()
- st = ClientGUICommon.BetterStaticText( self, label = 'Use this if you want to look up a file without importing it. Just copy its file path or image data to your clipboard and paste.' )
+ st = ClientGUICommon.BetterStaticText( self, label = 'Use this if you want to look up a file without needing to import it. Just copy its file path or image data to your clipboard and paste, and hydrus will figure out the hash data.' )
st.setWordWrap( True )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
QP.AddToLayout( big_vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
@@ -2274,21 +2704,37 @@ def _Paste( self ):
else:
- try:
+ if CG.client_controller.ClipboardHasLocalPaths():
+
+ paths = CG.client_controller.GetClipboardLocalPaths()
- raw_text = CG.client_controller.GetClipboardText()
+ if len( paths ) == 0:
+
+ ClientGUIDialogsMessage.ShowWarning( self, 'The clipboard seemed to have local paths on it, but when I asked for them I got nothing!' )
+
+ return
+
- except HydrusExceptions.DataMissing as e:
+ path = list( paths )[0]
- ClientGUIDialogsMessage.ShowWarning( self, 'Did not see an image bitmap or a file path in the clipboard!' )
+ else:
- return
+ try:
+
+ raw_text = CG.client_controller.GetClipboardText()
+
+ except HydrusExceptions.DataMissing as e:
+
+ ClientGUIDialogsMessage.ShowWarning( self, 'Did not see an image bitmap or a file path in the clipboard!' )
+
+ return
+
+
+ path = raw_text
try:
- path = raw_text
-
if os.path.exists( path ) and os.path.isfile( path ):
mime = HydrusFileHandling.GetMime( path )
@@ -2384,7 +2830,7 @@ def __init__( self, parent, predicate ):
predicate = self._GetPredicateToInitialisePanelWith( predicate )
- self._hashes.setPlaceholderText( 'enter file hash (64 chars each, paste newline-separated for multiple)' )
+ self._hashes.setPlaceholderText( 'file sha-256 hashes (64 chars each, paste newline-separated for multiple)' )
( hashes, hamming_distance ) = predicate.GetValue()
@@ -2405,10 +2851,10 @@ def __init__( self, parent, predicate ):
hbox.addStretch( 1 )
- st = ClientGUICommon.BetterStaticText( self, label = 'This searches for files that look like each other, just like in the duplicates system. Paste the files\' hash(es) here, and the results will look like any of them.' )
+ st = ClientGUICommon.BetterStaticText( self, label = 'This searches for files that look like each other within your database, just like in the duplicates system. It uses the SHA256 hash. Paste the files\' hash(es) here, and the results will look like any of them.' )
st.setWordWrap( True )
- st.setAlignment( QC.Qt.AlignCenter )
+ st.setAlignment( QC.Qt.AlignmentFlag.AlignCenter )
vbox = QP.VBoxLayout()
@@ -2497,6 +2943,7 @@ def GetPredicates( self ):
return predicates
+
class PanelPredicateSystemTagAsNumber( PanelPredicateSystemSingle ):
def __init__( self, parent, predicate ):
@@ -2554,6 +3001,7 @@ def GetPredicates( self ):
return predicates
+
class PanelPredicateSystemWidth( PanelPredicateSystemSingle ):
def __init__( self, parent, predicate ):
diff --git a/hydrus/client/gui/search/ClientGUISearch.py b/hydrus/client/gui/search/ClientGUISearch.py
index b1d55c4d5..a77f501ea 100644
--- a/hydrus/client/gui/search/ClientGUISearch.py
+++ b/hydrus/client/gui/search/ClientGUISearch.py
@@ -4,6 +4,7 @@
from qtpy import QtWidgets as QW
from hydrus.core import HydrusConstants as HC
+from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.client import ClientConstants as CC
@@ -15,7 +16,6 @@
from hydrus.client.gui import ClientGUIShortcuts
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.panels import ClientGUIScrolledPanels
-from hydrus.client.gui.search import ClientGUIPredicatesMultiple
from hydrus.client.gui.search import ClientGUIPredicatesSingle
from hydrus.client.gui.search import ClientGUIPredicatesOR
from hydrus.client.gui.widgets import ClientGUICommon
@@ -197,7 +197,7 @@ def __init__( self, parent, predicates: typing.Collection[ ClientSearchPredicate
predicates = list( predicates )
- predicates.sort( key = lambda p: ( p.GetMagicSortValue(), p.ToString( with_count = False ) ) )
+ predicates.sort( key = lambda p: p.GetMagicSortValue() )
self._uneditable_predicates = []
@@ -405,7 +405,56 @@ def __init__( self, parent, predicates: typing.Collection[ ClientSearchPredicate
if len( rating_preds ) > 0:
- self._editable_pred_panels.append( ClientGUIPredicatesMultiple.PanelPredicateSystemRating( self, rating_preds ) )
+ order_of_panels = [ HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL, HC.LOCAL_RATING_INCDEC ]
+
+ def key( p: ClientSearchPredicate.Predicate ):
+
+ s_k = p.GetValue()[2]
+
+ try:
+
+ service = CG.client_controller.services_manager.GetService( s_k )
+
+ return ( order_of_panels.index( service.GetServiceType() ), service.GetName() )
+
+ except HydrusExceptions.DataMissing:
+
+ return ( 3, 'zzz' )
+
+
+
+ rating_preds = sorted( rating_preds, key = key )
+
+ for pred in rating_preds:
+
+ service_key = pred.GetValue()[2]
+
+ try:
+
+ service = CG.client_controller.services_manager.GetService( service_key )
+
+ except HydrusExceptions.DataMissing:
+
+ HydrusData.ShowText( 'Hey, I was asked to edit a rating predicate that seems to refer to a service that no longer exists. I cannot do that, so it was ignored!' )
+
+ continue
+
+
+ type = service.GetServiceType()
+
+ if type == HC.LOCAL_RATING_LIKE:
+
+ self._editable_pred_panels.append( ClientGUIPredicatesSingle.PredicateSystemRatingLike( self, service_key, pred ) )
+
+ elif type == HC.LOCAL_RATING_NUMERICAL:
+
+ self._editable_pred_panels.append( ClientGUIPredicatesSingle.PredicateSystemRatingNumerical( self, service_key, pred ) )
+
+ elif type == HC.LOCAL_RATING_INCDEC:
+
+ self._editable_pred_panels.append( ClientGUIPredicatesSingle.PredicateSystemRatingIncDec( self, service_key, pred ) )
+
+
vbox = QP.VBoxLayout()
@@ -485,6 +534,8 @@ def __init__( self, parent, predicate: ClientSearchPredicate.Predicate ):
static_pred_buttons = []
editable_pred_panels = []
+ do_static_preds_in_two_column_table = False
+
if predicate_type == ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_AGE:
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_AGE, ( '<', 'delta', ( 0, 0, 1, 0 ) ) ), ), show_remove_button = False ) )
@@ -619,18 +670,22 @@ def __init__( self, parent, predicate: ClientSearchPredicate.Predicate ):
recent_predicate_types = []
+ do_static_preds_in_two_column_table = True
+
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_AUDIO, True ), ), show_remove_button = False ) )
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_AUDIO, False ), ), show_remove_button = False ) )
- static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_TRANSPARENCY, True ), ), show_remove_button = False ) )
- static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_TRANSPARENCY, False ), ), show_remove_button = False ) )
- static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_EXIF, True ), ), show_remove_button = False ) )
- static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_EXIF, False ), ), show_remove_button = False ) )
+ static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_DURATION, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '>', 0 ) ), ), show_remove_button = False ) )
+ static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_DURATION, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '=', 0 ) ), ), show_remove_button = False ) )
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_HUMAN_READABLE_EMBEDDED_METADATA, True ), ), show_remove_button = False ) )
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_HUMAN_READABLE_EMBEDDED_METADATA, False ), ), show_remove_button = False ) )
- static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_ICC_PROFILE, True ), ), show_remove_button = False ) )
- static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_ICC_PROFILE, False ), ), show_remove_button = False ) )
+ static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_EXIF, True ), ), show_remove_button = False ) )
+ static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_EXIF, False ), ), show_remove_button = False ) )
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_FORCED_FILETYPE, True ), ), show_remove_button = False ) )
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_FORCED_FILETYPE, False ), ), show_remove_button = False ) )
+ static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_ICC_PROFILE, True ), ), show_remove_button = False ) )
+ static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_ICC_PROFILE, False ), ), show_remove_button = False ) )
+ static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_TRANSPARENCY, True ), ), show_remove_button = False ) )
+ static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, ( ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_TRANSPARENCY, False ), ), show_remove_button = False ) )
elif predicate_type == ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_TRANSPARENCY:
@@ -712,11 +767,31 @@ def __init__( self, parent, predicate: ClientSearchPredicate.Predicate ):
services_manager = CG.client_controller.services_manager
- ratings_services = services_manager.GetServices( HC.RATINGS_SERVICES )
+ rating_services = services_manager.GetServices( HC.RATINGS_SERVICES )
- if len( ratings_services ) > 0:
+ if len( rating_services ) > 0:
- editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesMultiple.PanelPredicateSystemRating, ( predicate, ) ) )
+ for service_type_in_order in [ HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL, HC.LOCAL_RATING_INCDEC ]:
+
+ for rating_service in rating_services:
+
+ if rating_service.GetServiceType() == service_type_in_order:
+
+ if service_type_in_order == HC.LOCAL_RATING_LIKE:
+
+ editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PredicateSystemRatingLike, rating_service.GetServiceKey(), predicate ) )
+
+ elif service_type_in_order == HC.LOCAL_RATING_NUMERICAL:
+
+ editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PredicateSystemRatingNumerical, rating_service.GetServiceKey(), predicate ) )
+
+ elif service_type_in_order == HC.LOCAL_RATING_INCDEC:
+
+ editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PredicateSystemRatingIncDec, rating_service.GetServiceKey(), predicate ) )
+
+
+
+
elif predicate_type == ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_SIMILAR_TO:
@@ -819,9 +894,28 @@ def __init__( self, parent, predicate: ClientSearchPredicate.Predicate ):
- for button in static_pred_buttons:
+ if do_static_preds_in_two_column_table:
+
+ gridbox = QP.GridLayout( cols = 2 )
+
+ gridbox.setColumnStretch( 1, 1 )
+
+ for button in static_pred_buttons:
+
+ QP.AddToLayout( gridbox, button, CC.FLAGS_EXPAND_BOTH_WAYS )
+
+
+ QP.AddToLayout( page_vbox, gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
- QP.AddToLayout( page_vbox, button, CC.FLAGS_EXPAND_PERPENDICULAR )
+ else:
+
+ for button in static_pred_buttons:
+
+ QP.AddToLayout( page_vbox, button, CC.FLAGS_EXPAND_PERPENDICULAR )
+
+
+
+ for button in static_pred_buttons:
button.predicatesChosen.connect( self.StaticButtonClicked )
button.predicatesRemoved.connect( self.StaticRemoveButtonClicked )
@@ -921,14 +1015,14 @@ def SubPanelOK( self, predicates ):
class _PredOKPanel( QW.QWidget ):
- def __init__( self, parent: "FleshOutPredicatePanel", predicate_panel_class, predicate ):
+ def __init__( self, parent: "FleshOutPredicatePanel", predicate_panel_class, *args ):
super().__init__( parent )
self._defaults_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().star, self._DefaultsMenu )
self._defaults_button.setToolTip( ClientGUIFunctions.WrapToolTip( 'Set a new default.' ) )
- self._predicate_panel = predicate_panel_class( self, predicate )
+ self._predicate_panel = predicate_panel_class( self, *args )
self._parent = parent
self._ok = QW.QPushButton( 'ok', self )
@@ -989,7 +1083,7 @@ def keyPressEvent( self, event ):
( modifier, key ) = ClientGUIShortcuts.ConvertKeyEventToSimpleTuple( event )
- if key in ( QC.Qt.Key_Enter, QC.Qt.Key_Return ):
+ if key in ( QC.Qt.Key.Key_Enter, QC.Qt.Key.Key_Return ):
self._DoOK()
diff --git a/hydrus/client/gui/widgets/ClientGUIColourPicker.py b/hydrus/client/gui/widgets/ClientGUIColourPicker.py
index 5b2e67fb5..a9bbf19f6 100644
--- a/hydrus/client/gui/widgets/ClientGUIColourPicker.py
+++ b/hydrus/client/gui/widgets/ClientGUIColourPicker.py
@@ -241,7 +241,7 @@ def contextMenuEvent( self, event ):
def mouseReleaseEvent( self, event ):
- if event.button() != QC.Qt.RightButton:
+ if event.button() != QC.Qt.MouseButton.RightButton:
return QW.QPushButton.mouseReleaseEvent( self, event )
diff --git a/hydrus/client/gui/widgets/ClientGUICommon.py b/hydrus/client/gui/widgets/ClientGUICommon.py
index 7bb0beb16..c1f352541 100644
--- a/hydrus/client/gui/widgets/ClientGUICommon.py
+++ b/hydrus/client/gui/widgets/ClientGUICommon.py
@@ -353,15 +353,15 @@ def Append( self, text, data, starts_checked = False ):
item = QW.QListWidgetItem()
- item.setFlags( item.flags() | QC.Qt.ItemIsUserCheckable )
+ item.setFlags( item.flags() | QC.Qt.ItemFlag.ItemIsUserCheckable )
- qt_state = QC.Qt.Checked if starts_checked else QC.Qt.Unchecked
+ qt_state = QC.Qt.CheckState.Checked if starts_checked else QC.Qt.CheckState.Unchecked
item.setCheckState( qt_state )
item.setText( text )
- item.setData( QC.Qt.UserRole, data )
+ item.setData( QC.Qt.ItemDataRole.UserRole, data )
self.addItem( item )
@@ -370,7 +370,7 @@ def Append( self, text, data, starts_checked = False ):
def Check( self, index: int, value: bool = True ):
- qt_state = QC.Qt.Checked if value else QC.Qt.Unchecked
+ qt_state = QC.Qt.CheckState.Checked if value else QC.Qt.CheckState.Unchecked
item = self.item( index )
@@ -386,7 +386,7 @@ def Flip( self, index: int ):
def GetData( self, index: int ):
- return self.item( index ).data( QC.Qt.UserRole )
+ return self.item( index ).data( QC.Qt.ItemDataRole.UserRole )
def GetCheckedIndices( self ) -> typing.List[ int ]:
@@ -412,7 +412,7 @@ def GetValue( self ):
def IsChecked( self, index: int ) -> bool:
- return self.item( index ).checkState() == QC.Qt.Checked
+ return self.item( index ).checkState() == QC.Qt.CheckState.Checked
def IsSelected( self, index: int ) -> bool:
@@ -434,7 +434,7 @@ def SetValue( self, datas: typing.Collection ):
def mousePressEvent( self, event ):
- if event.button() == QC.Qt.RightButton:
+ if event.button() == QC.Qt.MouseButton.RightButton:
self.rightClicked.emit()
@@ -470,11 +470,11 @@ def GetValue( self ):
if selection != -1:
- return self.itemData( selection, QC.Qt.UserRole )
+ return self.itemData( selection, QC.Qt.ItemDataRole.UserRole )
elif self.count() > 0:
- return self.itemData( 0, QC.Qt.UserRole )
+ return self.itemData( 0, QC.Qt.ItemDataRole.UserRole )
else:
@@ -486,7 +486,7 @@ def SetValue( self, data ):
for i in range( self.count() ):
- if data == self.itemData( i, QC.Qt.UserRole ):
+ if data == self.itemData( i, QC.Qt.ItemDataRole.UserRole ):
self.setCurrentIndex( i )
@@ -594,7 +594,7 @@ def __init__( self, parent: QW.QWidget, action: QW.QAction ):
self.setPopupMode( QW.QToolButton.ToolButtonPopupMode.MenuButtonPopup )
- self.setToolButtonStyle( QC.Qt.ToolButtonTextOnly )
+ self.setToolButtonStyle( QC.Qt.ToolButtonStyle.ToolButtonTextOnly )
self.setDefaultAction( action )
@@ -623,7 +623,7 @@ def eventFilter( self, watched, event ):
try:
- if event.type() == QC.QEvent.Show and watched == self._menu:
+ if event.type() == QC.QEvent.Type.Show and watched == self._menu:
pos = QG.QCursor.pos()
@@ -762,7 +762,7 @@ def __init__( self, parent, label = None, tooltip_label = False, **kwargs ):
super().__init__( parent, ellipsize_end = ellipsize_end )
# otherwise by default html in 'this is a
parsing step' stuff renders fully lmaoooo
- self.setTextFormat( QC.Qt.PlainText )
+ self.setTextFormat( QC.Qt.TextFormat.PlainText )
self._tooltip_label = tooltip_label
@@ -816,8 +816,8 @@ def __init__( self, parent, label, url ):
self.setToolTip( ClientNetworkingFunctions.ConvertURLToHumanString( self._url ) )
- self.setTextFormat( QC.Qt.RichText )
- self.setTextInteractionFlags( QC.Qt.LinksAccessibleByMouse | QC.Qt.LinksAccessibleByKeyboard )
+ self.setTextFormat( QC.Qt.TextFormat.RichText )
+ self.setTextInteractionFlags( QC.Qt.TextInteractionFlag.LinksAccessibleByMouse | QC.Qt.TextInteractionFlag.LinksAccessibleByKeyboard )
self._colours = {
'link_color' : QG.QColor( 0, 0, 255 )
@@ -932,7 +932,7 @@ class BusyCursor( object ):
def __enter__( self ):
- QW.QApplication.setOverrideCursor( QC.Qt.WaitCursor )
+ QW.QApplication.setOverrideCursor( QC.Qt.CursorShape.WaitCursor )
def __exit__( self, exc_type, exc_val, exc_tb ):
@@ -1696,7 +1696,7 @@ def eventFilter( self, watched, event ):
try:
- if event.type() == QC.QEvent.KeyPress and event.key() in ( QC.Qt.Key_Enter, QC.Qt.Key_Return ):
+ if event.type() == QC.QEvent.Type.KeyPress and event.key() in ( QC.Qt.Key.Key_Enter, QC.Qt.Key.Key_Return ):
self._callable()
diff --git a/hydrus/client/media/ClientMediaResultPrettyInfo.py b/hydrus/client/media/ClientMediaResultPrettyInfo.py
index 3d5e7392f..d25204eb1 100644
--- a/hydrus/client/media/ClientMediaResultPrettyInfo.py
+++ b/hydrus/client/media/ClientMediaResultPrettyInfo.py
@@ -125,31 +125,13 @@ def timestamp_ms_is_interesting( timestamp_ms_1, timestamp_ms_2 ):
pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( f'filetype was originally: {HC.mime_string_lookup[ file_info_manager.original_mime ]}', False ) )
+ #
+
current_service_keys = locations_manager.GetCurrent()
deleted_service_keys = locations_manager.GetDeleted()
- local_file_services = CG.client_controller.services_manager.GetLocalMediaFileServices()
-
seen_local_file_service_timestamps_ms = set()
- current_local_file_services = [ service for service in local_file_services if service.GetServiceKey() in current_service_keys ]
-
- if len( current_local_file_services ) > 0:
-
- for local_file_service in current_local_file_services:
-
- timestamp_ms = times_manager.GetImportedTimestampMS( local_file_service.GetServiceKey() )
-
- line = f'added to {local_file_service.GetName()}: {HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( timestamp_ms ) )}'
-
- tooltip = f'added to {local_file_service.GetName()}: {HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( timestamp_ms ), reverse_iso_delta_setting = True )}'
-
- pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( line, False, tooltip = tooltip ) )
-
- seen_local_file_service_timestamps_ms.add( timestamp_ms )
-
-
-
if CC.COMBINED_LOCAL_FILE_SERVICE_KEY in current_service_keys:
timestamp_ms = times_manager.GetImportedTimestampMS( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
@@ -162,12 +144,42 @@ def timestamp_ms_is_interesting( timestamp_ms_1, timestamp_ms_2 ):
pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( line, line_is_interesting, tooltip = tooltip ) )
- if line_is_interesting:
+ seen_local_file_service_timestamps_ms.add( timestamp_ms )
+
+
+ local_file_services = CG.client_controller.services_manager.GetLocalMediaFileServices()
+
+ current_local_file_services = [ service for service in local_file_services if service.GetServiceKey() in current_service_keys ]
+
+ if len( current_local_file_services ) > 0:
+
+ state_local_service_timestamp = not only_interesting_lines or CG.client_controller.new_options.GetBoolean( 'file_info_line_consider_file_services_import_times_interesting' )
+
+ line_is_interesting = CG.client_controller.new_options.GetBoolean( 'file_info_line_consider_file_services_interesting' )
+
+ for local_file_service in current_local_file_services:
+
+ timestamp_ms = times_manager.GetImportedTimestampMS( local_file_service.GetServiceKey() )
+
+ if state_local_service_timestamp:
+
+ line = f'added to {local_file_service.GetName()}: {HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( timestamp_ms ) )}'
+
+ else:
+
+ line = local_file_service.GetName()
+
+
+ tooltip = f'added to {local_file_service.GetName()}: {HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( timestamp_ms ), reverse_iso_delta_setting = True )}'
+
+ pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( line, line_is_interesting, tooltip = tooltip ) )
seen_local_file_service_timestamps_ms.add( timestamp_ms )
+ #
+
deleted_local_file_services = [ service for service in local_file_services if service.GetServiceKey() in deleted_service_keys ]
local_file_deletion_reason = locations_manager.GetLocalFileDeletionReason()
@@ -178,9 +190,11 @@ def timestamp_ms_is_interesting( timestamp_ms_1, timestamp_ms_2 ):
line = f'deleted from this client {HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( timestamp_ms ) )} ({local_file_deletion_reason})'
+ line_is_interesting = True
+
tooltip = f'deleted from this client {HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( timestamp_ms ), reverse_iso_delta_setting = True )} ({local_file_deletion_reason})'
- pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( line, True, tooltip = tooltip ) )
+ pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( line, line_is_interesting, tooltip = tooltip ) )
elif CC.TRASH_SERVICE_KEY in current_service_keys:
@@ -200,7 +214,9 @@ def timestamp_ms_is_interesting( timestamp_ms_1, timestamp_ms_2 ):
tooltip = f'{tooltip} ({local_file_deletion_reason})'
- pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( line, True, tooltip = tooltip ) )
+ line_is_interesting = False
+
+ pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( line, line_is_interesting, tooltip = tooltip ) )
if len( deleted_local_file_services ) > 1:
@@ -214,6 +230,12 @@ def timestamp_ms_is_interesting( timestamp_ms_1, timestamp_ms_2 ):
pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( 'in the trash', True ) )
+ #
+
+ state_remote_service_timestamp = not only_interesting_lines or CG.client_controller.new_options.GetBoolean( 'file_info_line_consider_file_services_import_times_interesting' )
+
+ line_is_interesting = CG.client_controller.new_options.GetBoolean( 'file_info_line_consider_file_services_interesting' )
+
for service_key in current_service_keys.intersection( CG.client_controller.services_manager.GetServiceKeys( HC.REMOTE_FILE_SERVICES ) ):
timestamp_ms = times_manager.GetImportedTimestampMS( service_key )
@@ -238,12 +260,22 @@ def timestamp_ms_is_interesting( timestamp_ms_1, timestamp_ms_2 ):
status_label = 'uploaded'
- line = f'{status_label} to {service.GetName()} {HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( timestamp_ms ) )}'
+ if state_remote_service_timestamp:
+
+ line = f'{status_label} to {service.GetName()} {HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( timestamp_ms ) )}'
+
+ else:
+
+ line = f'{status_label} to {service.GetName()}'
+
+
tooltip = f'{status_label} to {service.GetName()} {HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( timestamp_ms ), reverse_iso_delta_setting = True )}'
- pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( line, True, tooltip = tooltip ) )
+ pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( line, line_is_interesting, tooltip = tooltip ) )
+ #
+
times_manager = locations_manager.GetTimesManager()
file_modified_timestamp_ms = times_manager.GetAggregateModifiedTimestampMS()
@@ -266,6 +298,8 @@ def timestamp_ms_is_interesting( timestamp_ms_1, timestamp_ms_2 ):
pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( line, line_is_interesting, tooltip = tooltip ) )
+ #
+
modified_timestamp_lines = []
timestamp_ms = times_manager.GetFileModifiedTimestampMS()
@@ -294,18 +328,46 @@ def timestamp_ms_is_interesting( timestamp_ms_1, timestamp_ms_2 ):
+ #
+
if not locations_manager.inbox:
+ state_archived_timestamp = not only_interesting_lines or CG.client_controller.new_options.GetBoolean( 'file_info_line_consider_archived_time_interesting' )
+
archived_timestamp_ms = times_manager.GetArchivedTimestampMS()
- if archived_timestamp_ms is not None:
+ if archived_timestamp_ms is None:
- line = f'archived: {HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( archived_timestamp_ms ) )}'
- tooltip = f'archived: {HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( archived_timestamp_ms ), reverse_iso_delta_setting = True )}'
+ if state_archived_timestamp:
+
+ line = f'archived: unknown time'
+
+ else:
+
+ line = 'archived'
+
+
+ tooltip = f'archived: unknown time'
+
+ else:
+
+ if state_archived_timestamp:
+
+ line = f'archived: {HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( archived_timestamp_ms ) )}'
+
+ else:
+
+ line = 'archived'
+
- pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( line, True, tooltip = tooltip ) )
+ tooltip = f'archived: {HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( archived_timestamp_ms ), reverse_iso_delta_setting = True )}'
+ line_is_interesting = CG.client_controller.new_options.GetBoolean( 'file_info_line_consider_archived_interesting' )
+
+ pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( line, line_is_interesting, tooltip = tooltip ) )
+
+
if file_info_manager.has_audio:
@@ -324,7 +386,7 @@ def timestamp_ms_is_interesting( timestamp_ms_1, timestamp_ms_2 ):
if file_info_manager.has_human_readable_embedded_metadata:
- pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( 'has human-readable embedded metadata', False ) )
+ pretty_info_lines.append( ClientMediaResultPrettyInfoObjects.PrettyMediaResultInfoLine( 'has embedded metadata', False ) )
if file_info_manager.has_icc_profile:
diff --git a/hydrus/client/metadata/ClientRatings.py b/hydrus/client/metadata/ClientRatings.py
index ccd34a9fe..2e3b10e64 100644
--- a/hydrus/client/metadata/ClientRatings.py
+++ b/hydrus/client/metadata/ClientRatings.py
@@ -20,6 +20,49 @@
PENTAGRAM_STAR : 'pentagram star'
}
+def ConvertRatingToStars( num_stars: int, allow_zero: bool, rating: float ) -> int:
+
+ if allow_zero:
+
+ stars = int( round( rating * num_stars ) )
+
+ else:
+
+ stars = int( round( rating * ( num_stars - 1 ) ) ) + 1
+
+
+ return stars
+
+
+def ConvertStarsToRating( num_stars: int, allow_zero: bool, stars: int ) -> float:
+
+ if stars > num_stars:
+
+ stars = num_stars
+
+
+ if allow_zero:
+
+ if stars < 0:
+
+ stars = 0
+
+
+ rating = stars / num_stars
+
+ else:
+
+ if stars < 1:
+
+ stars = 1
+
+
+ rating = ( stars - 1 ) / ( num_stars - 1 )
+
+
+ return rating
+
+
def GetIncDecStateFromMedia( media, service_key ):
values_seen = { m.GetRatingsManager().GetRating( service_key ) for m in media }
diff --git a/hydrus/client/networking/ClientNetworkingJobs.py b/hydrus/client/networking/ClientNetworkingJobs.py
index 987e609ea..1f885a2d0 100644
--- a/hydrus/client/networking/ClientNetworkingJobs.py
+++ b/hydrus/client/networking/ClientNetworkingJobs.py
@@ -1786,8 +1786,16 @@ def Start( self ):
if isinstance( e, requests.exceptions.SSLError ):
- fail_text = 'Problem with SSL: {}'.format( repr( e ) )
- delay_text = 'SSL connection failed'
+ if 'SSLCertVerificationError' in str( e ):
+
+ fail_text = f'Problem with SSL Verification. (This may be due to a bad certificate on the site or hydrus\'s "requests" library not having up to date root certs or SSL, but ISP level content blockers can also cause it.): {e}\n\n'
+ delay_text = 'SSL Cert Verification failed'
+
+ else:
+
+ fail_text = 'Problem with SSL: {}'.format( repr( e ) )
+ delay_text = 'SSL connection failed'
+
else:
diff --git a/hydrus/client/search/ClientNumberTest.py b/hydrus/client/search/ClientNumberTest.py
index c1e727579..2c5bb77bb 100644
--- a/hydrus/client/search/ClientNumberTest.py
+++ b/hydrus/client/search/ClientNumberTest.py
@@ -20,6 +20,17 @@
NUMBER_TEST_OPERATOR_APPROXIMATE_ABSOLUTE : HC.UNICODE_APPROX_EQUAL
}
+legacy_str_operator_to_number_test_operator_lookup = { s : o for ( o, s ) in number_test_operator_to_str_lookup.items() }
+
+number_test_operator_to_pretty_str_lookup = {
+ NUMBER_TEST_OPERATOR_LESS_THAN : 'less than',
+ NUMBER_TEST_OPERATOR_GREATER_THAN : 'more than',
+ NUMBER_TEST_OPERATOR_EQUAL : 'is',
+ NUMBER_TEST_OPERATOR_APPROXIMATE_PERCENT : 'is about',
+ NUMBER_TEST_OPERATOR_NOT_EQUAL : 'is not',
+ NUMBER_TEST_OPERATOR_APPROXIMATE_ABSOLUTE : 'is about'
+}
+
number_test_str_to_operator_lookup = { value : key for ( key, value ) in number_test_operator_to_str_lookup.items() if key != NUMBER_TEST_OPERATOR_APPROXIMATE_ABSOLUTE }
class NumberTest( HydrusSerialisable.SerialisableBase ):
@@ -149,11 +160,11 @@ def GetLambda( self ):
if lower <= 0:
- return lambda x: x is None or x < upper
+ return lambda x: x is None or x <= upper
else:
- return lambda x: x is not None and lower < x < upper
+ return lambda x: x is not None and lower <= x <= upper
elif self.operator == NUMBER_TEST_OPERATOR_APPROXIMATE_ABSOLUTE:
@@ -163,11 +174,11 @@ def GetLambda( self ):
if lower <= 0:
- return lambda x: x is None or x < upper
+ return lambda x: x is None or x <= upper
else:
- return lambda x: x is not None and lower < x < upper
+ return lambda x: x is not None and lower <= x <= upper
elif self.operator == NUMBER_TEST_OPERATOR_NOT_EQUAL:
@@ -225,11 +236,11 @@ def GetSQLitePredicates( self, variable_name ):
if lower <= 0:
- return [ f'( {variable_name} is NULL OR {variable_name} < {upper} )' ]
+ return [ f'( {variable_name} is NULL OR {variable_name} <= {upper} )' ]
else:
- return [ f'{variable_name} > {lower}', f'{variable_name} < {upper}' ]
+ return [ f'{variable_name} >= {lower}', f'{variable_name} <= {upper}' ]
elif self.operator == NUMBER_TEST_OPERATOR_APPROXIMATE_ABSOLUTE:
@@ -239,11 +250,11 @@ def GetSQLitePredicates( self, variable_name ):
if lower <= 0:
- return [ f'( {variable_name} IS NULL OR {variable_name} < {upper} )' ]
+ return [ f'( {variable_name} IS NULL OR {variable_name} <= {upper} )' ]
else:
- return [ f'{variable_name} > {lower}', f'{variable_name} < {upper}' ]
+ return [ f'{variable_name} >= {lower}', f'{variable_name} <= {upper}' ]
elif self.operator == NUMBER_TEST_OPERATOR_NOT_EQUAL:
diff --git a/hydrus/client/search/ClientSearchParseSystemPredicates.py b/hydrus/client/search/ClientSearchParseSystemPredicates.py
index 98600f58f..6606f791f 100644
--- a/hydrus/client/search/ClientSearchParseSystemPredicates.py
+++ b/hydrus/client/search/ClientSearchParseSystemPredicates.py
@@ -161,6 +161,19 @@ def rating_service_pred_generator( operator, value_and_service_name ):
value = service.ConvertStarsToRating( value )
+ elif service.GetServiceType() == HC.LOCAL_RATING_INCDEC:
+
+ if value == 'rated':
+
+ operator = '>'
+ value = 0
+
+ elif value == 'not rated':
+
+ operator = '='
+ value = 0
+
+
predicate_value = ( operator, value, service_key )
@@ -225,6 +238,20 @@ def url_class_pred_generator( include, url_class_name ):
SystemPredicateParser.Predicate.FILETYPE : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_MIME, tuple( v ) ),
SystemPredicateParser.Predicate.HAS_DURATION : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_DURATION, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '>', 0 ) ),
SystemPredicateParser.Predicate.NO_DURATION : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_DURATION, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '=', 0 ) ),
+ SystemPredicateParser.Predicate.HAS_FRAMERATE : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_FRAMERATE, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '>', 0 ) ),
+ SystemPredicateParser.Predicate.NO_FRAMERATE : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_FRAMERATE, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '=', 0 ) ),
+ SystemPredicateParser.Predicate.HAS_FRAMES : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_FRAMES, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '>', 0 ) ),
+ SystemPredicateParser.Predicate.NO_FRAMES : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_FRAMES, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '=', 0 ) ),
+ SystemPredicateParser.Predicate.HAS_WIDTH : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_WIDTH, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '>', 0 ) ),
+ SystemPredicateParser.Predicate.NO_WIDTH : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_WIDTH, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '=', 0 ) ),
+ SystemPredicateParser.Predicate.HAS_HEIGHT : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HEIGHT, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '>', 0 ) ),
+ SystemPredicateParser.Predicate.NO_HEIGHT : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HEIGHT, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '=', 0 ) ),
+ SystemPredicateParser.Predicate.HAS_NOTES : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '>', 0 ) ),
+ SystemPredicateParser.Predicate.NO_NOTES : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '=', 0 ) ),
+ SystemPredicateParser.Predicate.HAS_URLS : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_URLS, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '>', 0 ) ),
+ SystemPredicateParser.Predicate.NO_URLS : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_URLS, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '=', 0 ) ),
+ SystemPredicateParser.Predicate.HAS_WORDS : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_WORDS, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '>', 0 ) ),
+ SystemPredicateParser.Predicate.NO_WORDS : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_WORDS, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '=', 0 ) ),
SystemPredicateParser.Predicate.HAS_TAGS : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( '*', '>', 0 ) ),
SystemPredicateParser.Predicate.UNTAGGED : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( '*', '=', 0 ) ),
SystemPredicateParser.Predicate.NUM_OF_TAGS : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( '*', o, v ) ),
@@ -264,8 +291,6 @@ def url_class_pred_generator( include, url_class_name ):
SystemPredicateParser.Predicate.TIME_IMPORTED : lambda o, v, u: date_pred_generator( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_AGE, o, v ),
SystemPredicateParser.Predicate.FILE_SERVICE : file_service_pred_generator,
SystemPredicateParser.Predicate.NUM_FILE_RELS : num_file_relationships_pred_generator,
- SystemPredicateParser.Predicate.HAS_NOTES : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '>', 0 ) ),
- SystemPredicateParser.Predicate.NO_NOTES : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ClientNumberTest.NumberTest.STATICCreateFromCharacters( '=', 0 ) ),
SystemPredicateParser.Predicate.NUM_NOTES : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ClientNumberTest.NumberTest.STATICCreateFromCharacters( o, v ) ),
SystemPredicateParser.Predicate.HAS_NOTE_NAME : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_NOTE_NAME, ( True, strip_quotes( v ) ) ),
SystemPredicateParser.Predicate.NO_NOTE_NAME : lambda o, v, u: ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_NOTE_NAME, ( False, strip_quotes( v ) ) ),
diff --git a/hydrus/client/search/ClientSearchPredicate.py b/hydrus/client/search/ClientSearchPredicate.py
index 6ce68c130..9f0c9e53d 100644
--- a/hydrus/client/search/ClientSearchPredicate.py
+++ b/hydrus/client/search/ClientSearchPredicate.py
@@ -939,15 +939,15 @@ def GetMagicSortValue( self ):
if self._predicate_type == PREDICATE_TYPE_SYSTEM_WIDTH:
- return -2
+ return 'system:dimensions:0'
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_HEIGHT:
- return -1
+ return 'system:dimensions:1'
else:
- return 0
+ return self.ToString( with_count = False )
@@ -1240,54 +1240,54 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
if self._predicate_type == PREDICATE_TYPE_SYSTEM_WIDTH:
base = 'width'
- has_phrase = ': has width'
- not_has_phrase = ': no width'
+ has_phrase = 'has width'
+ not_has_phrase = 'no width'
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_HEIGHT:
base = 'height'
- has_phrase = ': has height'
- not_has_phrase = ': no height'
+ has_phrase = 'has height'
+ not_has_phrase = 'no height'
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_FRAMERATE:
absolute_number_renderer = lambda s: f'{HydrusNumbers.ToHumanInt(s)}fps'
base = 'framerate'
- has_phrase = ': has framerate'
- not_has_phrase = ': no framerate'
+ has_phrase = 'has framerate'
+ not_has_phrase = 'no framerate'
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_NUM_NOTES:
base = 'number of notes'
- has_phrase = ': has notes'
- not_has_phrase = ': no notes'
+ has_phrase = 'has notes'
+ not_has_phrase = 'no notes'
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_NUM_WORDS:
base = 'number of words'
- has_phrase = ': has words'
- not_has_phrase = ': no words'
+ has_phrase = 'has words'
+ not_has_phrase = 'no words'
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_NUM_URLS:
base = 'number of urls'
- has_phrase = ': has urls'
- not_has_phrase = ': no urls'
+ has_phrase = 'has urls'
+ not_has_phrase = 'no urls'
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_NUM_FRAMES:
base = 'number of frames'
- has_phrase = ': has frames'
- not_has_phrase = ': no frames'
+ has_phrase = 'has frames'
+ not_has_phrase = 'no frames'
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_DURATION:
- absolute_number_renderer = HydrusTime.MillisecondsDurationToPrettyTime
+ absolute_number_renderer = lambda n: HydrusTime.MillisecondsDurationToPrettyTime( n, force_numbers = True )
base = 'duration'
- has_phrase = ': has duration'
- not_has_phrase = ': no duration'
+ has_phrase = 'has duration'
+ not_has_phrase = 'no duration'
if self._value is not None:
@@ -1296,11 +1296,11 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
if number_test.IsZero() and not_has_phrase is not None:
- base += not_has_phrase
+ base = not_has_phrase
elif number_test.IsAnythingButZero() and has_phrase is not None:
- base += has_phrase
+ base = has_phrase
else:
@@ -1592,7 +1592,7 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_HAS_HUMAN_READABLE_EMBEDDED_METADATA:
- base = 'has human-readable embedded metadata'
+ base = 'has embedded metadata'
if self._value is not None:
@@ -1600,7 +1600,7 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
if not has_human_readable_embedded_metadata:
- base = 'no human-readable embedded metadata'
+ base = 'no embedded metadata'
@@ -1694,23 +1694,45 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
try:
+ pretty_operator = ClientNumberTest.number_test_operator_to_pretty_str_lookup.get( ClientNumberTest.legacy_str_operator_to_number_test_operator_lookup.get( operator, 'unknown' ), 'unknown' )
+
service = CG.client_controller.services_manager.GetService( service_key )
name = service.GetName()
- if value == 'rated':
-
- base = 'has a rating for {}'.format( name )
-
- elif value == 'not rated':
+ if service.GetServiceType() == HC.LOCAL_RATING_INCDEC:
- base = 'does not have a rating for {}'.format( name )
+ if operator == '>' and value == 0:
+
+ base = f'has count for {name}'
+
+ elif ( operator == '<' and value == 1 ) or ( operator == '=' and value == 0 ):
+
+ base = f'no count for {name}'
+
+ else:
+
+ pretty_value = service.ConvertNoneableRatingToString( value )
+
+ base = f'count for {name} {pretty_operator} {pretty_value}'
+
else:
- pretty_value = service.ConvertNoneableRatingToString( value )
-
- base += ' for {} {} {}'.format( service.GetName(), operator, pretty_value )
+ if value == 'rated':
+
+ base = f'has rating for {name}'
+
+ elif value == 'not rated':
+
+ base = f'no rating for {name}'
+
+ else:
+
+ pretty_value = service.ConvertNoneableRatingToString( value )
+
+ base = f'rating for {name} {pretty_operator} {pretty_value}'
+
except HydrusExceptions.DataMissing:
diff --git a/hydrus/core/HydrusConstants.py b/hydrus/core/HydrusConstants.py
index 890ae653d..62650eb6f 100644
--- a/hydrus/core/HydrusConstants.py
+++ b/hydrus/core/HydrusConstants.py
@@ -105,7 +105,7 @@
# Misc
NETWORK_VERSION = 20
-SOFTWARE_VERSION = 601
+SOFTWARE_VERSION = 602
CLIENT_API_VERSION = 76
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )
diff --git a/hydrus/core/HydrusDB.py b/hydrus/core/HydrusDB.py
index 310223688..769c98227 100644
--- a/hydrus/core/HydrusDB.py
+++ b/hydrus/core/HydrusDB.py
@@ -862,13 +862,13 @@ def _ProcessJob( self, job: HydrusDBBase.JobDatabase ):
if job_type in ( 'read_write', 'write' ):
- self._current_status = 'db write locked'
+ self._current_status = 'db writing'
self._cursor_transaction_wrapper.NotifyWriteOccuring()
else:
- self._current_status = 'db read locked'
+ self._current_status = 'db reading'
self.publish_status_update()
@@ -1203,6 +1203,10 @@ def MainLoop( self ):
self._CloseDBConnection()
+ self._current_status = 'db locked'
+
+ self.publish_status_update()
+
while self._pause_and_disconnect:
if self._local_shutdown or HG.model_shutdown:
@@ -1215,6 +1219,8 @@ def MainLoop( self ):
self._InitDBConnection()
+ self._current_status = ''
+
self._CloseDBConnection()
diff --git a/hydrus/core/HydrusEnvironment.py b/hydrus/core/HydrusEnvironment.py
new file mode 100644
index 000000000..f7f9be31b
--- /dev/null
+++ b/hydrus/core/HydrusEnvironment.py
@@ -0,0 +1,34 @@
+import os
+
+from hydrus.core import HydrusData
+
+def DumpEnv( env = None ):
+
+ if env is None:
+
+ env = os.environ.copy()
+
+
+ rows = []
+
+ for ( key, value ) in sorted( env.items() ):
+
+ if ( 'PATH' in key or 'DIRS' in key ) and os.pathsep in value:
+
+ rows.append( f'{key}:' )
+
+ sub_values = value.split( os.pathsep )
+
+ for sub_value in sub_values:
+
+ rows.append( f' {sub_value}' )
+
+
+ else:
+
+ rows.append( f'{key}: {value}' )
+
+
+
+ HydrusData.ShowText( 'Full environment:\n' + '\n'.join( rows ) )
+
diff --git a/hydrus/core/HydrusProcess.py b/hydrus/core/HydrusProcess.py
index 2a7b36fac..b9c47d44f 100644
--- a/hydrus/core/HydrusProcess.py
+++ b/hydrus/core/HydrusProcess.py
@@ -6,6 +6,7 @@
from hydrus.core import HydrusBoot
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
+from hydrus.core import HydrusEnvironment
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusPSUtil
@@ -70,9 +71,7 @@ def GetSubprocessEnv():
if HG.subprocess_report_mode:
- env = os.environ.copy()
-
- HydrusData.ShowText( 'Your unmodified env is: {}'.format( env ) )
+ HydrusEnvironment.DumpEnv()
env = os.environ.copy()
diff --git a/hydrus/core/HydrusTime.py b/hydrus/core/HydrusTime.py
index 42179ce29..1b14dc5b0 100644
--- a/hydrus/core/HydrusTime.py
+++ b/hydrus/core/HydrusTime.py
@@ -449,11 +449,11 @@ def TimestampToPrettyExpires( timestamp ):
-def MillisecondsDurationToPrettyTime( duration_ms: typing.Optional[ int ] ) -> str:
+def MillisecondsDurationToPrettyTime( duration_ms: typing.Optional[ int ], force_numbers = False ) -> str:
# should this function just be merged into timedeltatoprettytimedelta or something?
- if duration_ms is None or duration_ms == 0:
+ if ( duration_ms is None or duration_ms == 0 ) and not force_numbers:
return 'no duration'
diff --git a/hydrus/core/files/HydrusVideoHandling.py b/hydrus/core/files/HydrusVideoHandling.py
index 9f6f9c046..aac8f8ce5 100644
--- a/hydrus/core/files/HydrusVideoHandling.py
+++ b/hydrus/core/files/HydrusVideoHandling.py
@@ -7,6 +7,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
+from hydrus.core import HydrusEnvironment
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusProcess
from hydrus.core import HydrusText
@@ -100,14 +101,14 @@ def GetFFMPEGVersion():
message += '\n' * 2
message += str( sbp_kwargs )
message += '\n' * 2
- message += str( os.environ )
- message += '\n' * 2
message += 'STDOUT Response: {}'.format( stdout )
message += '\n' * 2
message += 'STDERR Response: {}'.format( stderr )
HydrusData.Print( message )
+ HydrusEnvironment.DumpEnv()
+
global FFMPEG_NO_CONTENT_ERROR_PUBBED
FFMPEG_NO_CONTENT_ERROR_PUBBED = True
@@ -197,14 +198,14 @@ def GetFFMPEGInfoLines( path, count_frames_manually = False, only_first_second =
message += '\n' * 2
message += str( sbp_kwargs )
message += '\n' * 2
- message += str( os.environ )
- message += '\n' * 2
message += 'STDOUT Response: {}'.format( stdout )
message += '\n' * 2
message += 'STDERR Response: {}'.format( stderr )
HydrusData.DebugPrint( message )
+ HydrusEnvironment.DumpEnv()
+
FFMPEG_NO_CONTENT_ERROR_PUBBED = True
diff --git a/hydrus/external/SystemPredicateParser.py b/hydrus/external/SystemPredicateParser.py
index 6b073e70a..e6ef6bb36 100644
--- a/hydrus/external/SystemPredicateParser.py
+++ b/hydrus/external/SystemPredicateParser.py
@@ -90,6 +90,18 @@ class Predicate( Enum ):
ARCHIVE = auto()
HAS_DURATION = auto()
NO_DURATION = auto()
+ HAS_FRAMERATE = auto()
+ NO_FRAMERATE = auto()
+ HAS_FRAMES = auto()
+ NO_FRAMES = auto()
+ HAS_WIDTH = auto()
+ NO_WIDTH = auto()
+ HAS_HEIGHT = auto()
+ NO_HEIGHT = auto()
+ HAS_URLS = auto()
+ NO_URLS = auto()
+ HAS_WORDS = auto()
+ NO_WORDS = auto()
BEST_QUALITY_OF_GROUP = auto()
NOT_BEST_QUALITY_OF_GROUP = auto()
HAS_AUDIO = auto()
@@ -217,6 +229,20 @@ class Units( Enum ):
'archived?$': (Predicate.ARCHIVE, None, None, None), # $ so as not to clash with system:archive(d) date
'has duration': (Predicate.HAS_DURATION, None, None, None),
'no duration': (Predicate.NO_DURATION, None, None, None),
+ 'has framerate': (Predicate.HAS_FRAMERATE, None, None, None),
+ 'no framerate': (Predicate.NO_FRAMERATE, None, None, None),
+ 'has frames': (Predicate.HAS_FRAMES, None, None, None),
+ 'no frames': (Predicate.NO_FRAMES, None, None, None),
+ 'has width': (Predicate.HAS_WIDTH, None, None, None),
+ 'no width': (Predicate.NO_WIDTH, None, None, None),
+ 'has height': (Predicate.HAS_HEIGHT, None, None, None),
+ 'no height': (Predicate.NO_HEIGHT, None, None, None),
+ 'has notes': (Predicate.HAS_NOTES, None, None, None),
+ 'no notes': (Predicate.NO_NOTES, None, None, None),
+ 'has urls': (Predicate.HAS_URLS, None, None, None),
+ 'no urls': (Predicate.NO_URLS, None, None, None),
+ 'has words': (Predicate.HAS_WORDS, None, None, None),
+ 'no words': (Predicate.NO_WORDS, None, None, None),
'(is the )?best quality( file)? of( its)?( duplicate)? group': (Predicate.BEST_QUALITY_OF_GROUP, None, None, None),
'(((is )?not)|(isn\'t))( the)? best quality( file)? of( its)?( duplicate)? group': (Predicate.NOT_BEST_QUALITY_OF_GROUP, None, None, None),
'has audio': (Predicate.HAS_AUDIO, None, None, None),
@@ -250,7 +276,7 @@ class Units( Enum ):
'last view(ed)? (date|time)|(date|time) last viewed|last viewed': (Predicate.LAST_VIEWED_TIME, Operators.RELATIONAL_TIME, Value.DATE_OR_TIME_INTERVAL, None),
'import(ed)? (date|time)|(date|time) imported|imported': (Predicate.TIME_IMPORTED, Operators.RELATIONAL_TIME, Value.DATE_OR_TIME_INTERVAL, None),
'duration': (Predicate.DURATION, Operators.RELATIONAL, Value.TIME_SEC_MSEC, None),
- 'framerate': (Predicate.FRAMERATE, Operators.RELATIONAL_EXACT, Value.NATURAL, Units.FPS_OR_NONE),
+ 'framerate': (Predicate.FRAMERATE, Operators.RELATIONAL, Value.NATURAL, Units.FPS_OR_NONE),
'num(ber)?( of)? frames': (Predicate.NUM_OF_FRAMES, Operators.RELATIONAL, Value.NATURAL, None),
'file service': (Predicate.FILE_SERVICE, Operators.FILESERVICE_STATUS, Value.ANY_STRING, None),
'num(ber)?( of)? file relationships': (Predicate.NUM_FILE_RELS, Operators.RELATIONAL, Value.NATURAL, Units.FILE_RELATIONSHIP_TYPE),
@@ -277,11 +303,11 @@ class Units( Enum ):
'num(ber)?( of)? notes?': (Predicate.NUM_NOTES, Operators.RELATIONAL_EXACT, Value.NATURAL, None),
'(has (a )?)?note (with name|named)': (Predicate.HAS_NOTE_NAME, None, Value.ANY_STRING, None),
'((has )?no|does not have( a)?|doesn\'t have( a)?) note (with name|named)': (Predicate.NO_NOTE_NAME, None, Value.ANY_STRING, None),
- 'has( a)? rating( for)?': (Predicate.HAS_RATING, None, Value.ANY_STRING, None ),
- '((has )?no|does not have( a)?|doesn\'t have( a)?) rating( for)?': (Predicate.NO_RATING, None, Value.ANY_STRING, None ),
- r'rating( for)?(?=.+?\d+/\d+$)': (Predicate.RATING_SPECIFIC_NUMERICAL, Operators.RELATIONAL_FOR_RATING_SERVICE, Value.RATING_SERVICE_NAME_AND_NUMERICAL_VALUE, None ),
- 'rating( for)?(?=.+?(like|dislike)$)': (Predicate.RATING_SPECIFIC_LIKE_DISLIKE, None, Value.RATING_SERVICE_NAME_AND_LIKE_DISLIKE, None ),
- r'rating( for)?(?=.+?[^/]\d+$)': (Predicate.RATING_SPECIFIC_INCDEC, Operators.RELATIONAL_FOR_RATING_SERVICE, Value.RATING_SERVICE_NAME_AND_INCDEC, None ),
+ 'has( a)? (rating|count)( for)?': (Predicate.HAS_RATING, None, Value.ANY_STRING, None ),
+ '((has )?no|does not have( a)?|doesn\'t have( a)?) (rating|count)( for)?': (Predicate.NO_RATING, None, Value.ANY_STRING, None ),
+ r'(rating|count)( for)?(?=.+?\d+/\d+$)': (Predicate.RATING_SPECIFIC_NUMERICAL, Operators.RELATIONAL_FOR_RATING_SERVICE, Value.RATING_SERVICE_NAME_AND_NUMERICAL_VALUE, None ),
+ '(rating|count)( for)?(?=.+?(like|dislike)$)': (Predicate.RATING_SPECIFIC_LIKE_DISLIKE, None, Value.RATING_SERVICE_NAME_AND_LIKE_DISLIKE, None ),
+ r'(rating|count)( for)?(?=.+?[^/]\d+$)': (Predicate.RATING_SPECIFIC_INCDEC, Operators.RELATIONAL_FOR_RATING_SERVICE, Value.RATING_SERVICE_NAME_AND_INCDEC, None ),
}
def string_looks_like_date( string ):
@@ -372,6 +398,7 @@ def parse_unit( string: str, spec ):
if match: return string[ len( match[ 0 ] ): ], 'megapixels'
raise ValueError( "Invalid unit, expected pixels" )
elif spec == Units.FPS_OR_NONE:
+
if not string:
return string, None
else:
diff --git a/hydrus/test/TestClientListBoxes.py b/hydrus/test/TestClientListBoxes.py
index 758527b14..c8722cc7e 100644
--- a/hydrus/test/TestClientListBoxes.py
+++ b/hydrus/test/TestClientListBoxes.py
@@ -17,7 +17,7 @@ def DoClick( click, panel, do_delayed_ok_afterwards = False ):
if do_delayed_ok_afterwards:
- TG.test_controller.CallLaterQtSafe( panel, 1, 'test click', PressKeyOnFocusedWindow, QC.Qt.Key_Return )
+ TG.test_controller.CallLaterQtSafe( panel, 1, 'test click', PressKeyOnFocusedWindow, QC.Qt.Key.Key_Return )
QW.QApplication.processEvents()
@@ -34,7 +34,7 @@ def GetAllClickableIndices( panel ):
current_y = 5
- click = GenerateClick( panel, QC.QPointF( 10, current_y ), QC.QEvent.MouseButtonPress, QC.Qt.LeftButton, QC.Qt.NoModifier )
+ click = GenerateClick( panel, QC.QPointF( 10, current_y ), QC.QEvent.Type.MouseButtonPress, QC.Qt.MouseButton.LeftButton, QC.Qt.KeyboardModifier.NoModifier )
all_clickable_indices = {}
@@ -49,14 +49,14 @@ def GetAllClickableIndices( panel ):
current_y += 5
- click = GenerateClick( panel, QC.QPointF( 10, current_y ), QC.QEvent.MouseButtonPress, QC.Qt.LeftButton, QC.Qt.NoModifier )
+ click = GenerateClick( panel, QC.QPointF( 10, current_y ), QC.QEvent.Type.MouseButtonPress, QC.Qt.MouseButton.LeftButton, QC.Qt.KeyboardModifier.NoModifier )
return all_clickable_indices
def PressKey( window, key ):
- window.setFocus( QC.Qt.OtherFocusReason )
+ window.setFocus( QC.Qt.FocusReason.OtherFocusReason )
uias = QP.UIActionSimulator()
@@ -117,7 +117,7 @@ def qt_code():
for ( index, y ) in list( all_clickable_indices.items() ):
- click = GenerateClick( panel, QC.QPointF( 10, y ), QC.QEvent.MouseButtonPress, QC.Qt.LeftButton, QC.Qt.NoModifier )
+ click = GenerateClick( panel, QC.QPointF( 10, y ), QC.QEvent.Type.MouseButtonPress, QC.Qt.MouseButton.LeftButton, QC.Qt.KeyboardModifier.NoModifier )
DoClick( click, panel )
@@ -128,13 +128,13 @@ def qt_code():
current_y = 5
- click = QG.QMouseEvent( QC.QEvent.MouseButtonPress, QC.QPointF( 10, current_y ), QC.Qt.LeftButton, QC.Qt.LeftButton, QC.Qt.NoModifier )
+ click = QG.QMouseEvent( QC.QEvent.Type.MouseButtonPress, QC.QPointF( 10, current_y ), QC.Qt.MouseButton.LeftButton, QC.Qt.MouseButton.LeftButton, QC.Qt.KeyboardModifier.NoModifier )
while panel._GetLogicalIndexUnderMouse( click ) is not None:
current_y += 5
- click = GenerateClick( panel, QC.QPointF( 10, current_y ), QC.QEvent.MouseButtonPress, QC.Qt.LeftButton, QC.Qt.NoModifier )
+ click = GenerateClick( panel, QC.QPointF( 10, current_y ), QC.QEvent.Type.MouseButtonPress, QC.Qt.MouseButton.LeftButton, QC.Qt.KeyboardModifier.NoModifier )
DoClick( click, panel )
@@ -151,7 +151,7 @@ def qt_code():
for index in indices:
- click = GenerateClick( panel, QC.QPointF( 10, all_clickable_indices[ index ] ), QC.QEvent.MouseButtonPress, QC.Qt.LeftButton, QC.Qt.ControlModifier )
+ click = GenerateClick( panel, QC.QPointF( 10, all_clickable_indices[ index ] ), QC.QEvent.Type.MouseButtonPress, QC.Qt.MouseButton.LeftButton, QC.Qt.KeyboardModifier.ControlModifier )
DoClick( click, panel )
@@ -176,20 +176,20 @@ def qt_code():
current_y = 5
- click = GenerateClick( panel, QC.QPointF( 10, current_y ), QC.QEvent.MouseButtonPress, QC.Qt.LeftButton, QC.Qt.NoModifier )
+ click = GenerateClick( panel, QC.QPointF( 10, current_y ), QC.QEvent.Type.MouseButtonPress, QC.Qt.MouseButton.LeftButton, QC.Qt.KeyboardModifier.NoModifier )
while panel._GetLogicalIndexUnderMouse( click ) is not None:
current_y += 5
- click = GenerateClick( panel, QC.QPointF( 10, current_y ), QC.QEvent.MouseButtonPress, QC.Qt.LeftButton, QC.Qt.NoModifier )
+ click = GenerateClick( panel, QC.QPointF( 10, current_y ), QC.QEvent.Type.MouseButtonPress, QC.Qt.MouseButton.LeftButton, QC.Qt.KeyboardModifier.NoModifier )
DoClick( click, panel )
# select the random index
- click = GenerateClick( panel, QC.QPointF( 10, all_clickable_indices[ random_index ] ), QC.QEvent.MouseButtonPress, QC.Qt.LeftButton, QC.Qt.NoModifier )
+ click = GenerateClick( panel, QC.QPointF( 10, all_clickable_indices[ random_index ] ), QC.QEvent.Type.MouseButtonPress, QC.Qt.MouseButton.LeftButton, QC.Qt.KeyboardModifier.NoModifier )
DoClick( click, panel )
diff --git a/hydrus/test/TestClientSearch.py b/hydrus/test/TestClientSearch.py
index 02df095d8..0d2576a94 100644
--- a/hydrus/test/TestClientSearch.py
+++ b/hydrus/test/TestClientSearch.py
@@ -1429,13 +1429,13 @@ def test_predicate_strings_and_namespaces( self ):
p = ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_HUMAN_READABLE_EMBEDDED_METADATA, True )
- self.assertEqual( p.ToString(), 'system:has human-readable embedded metadata' )
+ self.assertEqual( p.ToString(), 'system:has embedded metadata' )
self.assertEqual( p.GetNamespace(), 'system' )
self.assertEqual( p.GetTextsAndNamespaces( render_for_user ), [ ( p.ToString(), 'namespace', p.GetNamespace() ) ] )
p = ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_HAS_HUMAN_READABLE_EMBEDDED_METADATA, False )
- self.assertEqual( p.ToString(), 'system:no human-readable embedded metadata' )
+ self.assertEqual( p.ToString(), 'system:no embedded metadata' )
self.assertEqual( p.GetNamespace(), 'system' )
self.assertEqual( p.GetTextsAndNamespaces( render_for_user ), [ ( p.ToString(), 'namespace', p.GetNamespace() ) ] )
@@ -1551,13 +1551,13 @@ def test_predicate_strings_and_namespaces( self ):
p = ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING, ( '>', 0.2, TestController.LOCAL_RATING_NUMERICAL_SERVICE_KEY ) )
- self.assertEqual( p.ToString(), 'system:rating for example local rating numerical service > 1/5' )
+ self.assertEqual( p.ToString(), 'system:rating for example local rating numerical service more than 1/5' )
self.assertEqual( p.GetNamespace(), 'system' )
self.assertEqual( p.GetTextsAndNamespaces( render_for_user ), [ ( p.ToString(), 'namespace', p.GetNamespace() ) ] )
p = ClientSearchPredicate.Predicate( ClientSearchPredicate.PREDICATE_TYPE_SYSTEM_RATING, ( '>', 3, TestController.LOCAL_RATING_INCDEC_SERVICE_KEY ) )
- self.assertEqual( p.ToString(), 'system:rating for example local rating inc/dec service > 3' )
+ self.assertEqual( p.ToString(), 'system:count for example local rating inc/dec service more than 3' )
self.assertEqual( p.GetNamespace(), 'system' )
self.assertEqual( p.GetTextsAndNamespaces( render_for_user ), [ ( p.ToString(), 'namespace', p.GetNamespace() ) ] )
@@ -1658,12 +1658,54 @@ def test_system_predicate_parsing( self ):
( 'system:archive', "system:archive " ),
( 'system:archive', "system:archived " ),
( 'system:archive', "system:archived" ),
- ( 'system:duration: has duration', "system:has duration" ),
- ( 'system:duration: has duration', "system:has_duration" ),
- ( 'system:duration: no duration', " system:no_duration" ),
- ( 'system:duration: no duration', "system:no duration" ),
- ( 'system:duration: has duration', "system:duration: has duration" ),
- ( 'system:duration: no duration', "system:duration: no duration" ),
+ ( 'system:has duration', "system:has duration" ),
+ ( 'system:has duration', "system:has_duration" ),
+ ( 'system:no duration', " system:no_duration" ),
+ ( 'system:no duration', "system:no duration" ),
+ ( 'system:has duration', "system:duration: has duration" ),
+ ( 'system:no duration', "system:duration: no duration" ),
+ ( 'system:has framerate', "system:has framerate" ),
+ ( 'system:has framerate', "system:has_framerate" ),
+ ( 'system:no framerate', " system:no_framerate" ),
+ ( 'system:no framerate', "system:no framerate" ),
+ ( 'system:has framerate', "system:framerate: has framerate" ),
+ ( 'system:no framerate', "system:framerate: no framerate" ),
+ ( 'system:has frames', "system:has frames" ),
+ ( 'system:has frames', "system:has_frames" ),
+ ( 'system:no frames', " system:no_frames" ),
+ ( 'system:no frames', "system:no frames" ),
+ ( 'system:has frames', "system:number of frames: has frames" ),
+ ( 'system:no frames', "system:number of frames: no frames" ),
+ ( 'system:has width', "system:has width" ),
+ ( 'system:has width', "system:has_width" ),
+ ( 'system:no width', " system:no_width" ),
+ ( 'system:no width', "system:no width" ),
+ ( 'system:has width', "system:width: has width" ),
+ ( 'system:no width', "system:width: no width" ),
+ ( 'system:has height', "system:has height" ),
+ ( 'system:has height', "system:has_height" ),
+ ( 'system:no height', " system:no_height" ),
+ ( 'system:no height', "system:no height" ),
+ ( 'system:has height', "system:height: has height" ),
+ ( 'system:no height', "system:height: no height" ),
+ ( 'system:has notes', "system:has notes" ),
+ ( 'system:has notes', "system:has_notes" ),
+ ( 'system:no notes', " system:no_notes" ),
+ ( 'system:no notes', "system:no notes" ),
+ ( 'system:has notes', "system:number of notes: has notes" ),
+ ( 'system:no notes', "system:number of notes: no notes" ),
+ ( 'system:has urls', "system:has urls" ),
+ ( 'system:has urls', "system:has_urls" ),
+ ( 'system:no urls', " system:no_urls" ),
+ ( 'system:no urls', "system:no urls" ),
+ ( 'system:has urls', "system:number of urls: has urls" ),
+ ( 'system:no urls', "system:number of urls: no urls" ),
+ ( 'system:has words', "system:has words" ),
+ ( 'system:has words', "system:has_words" ),
+ ( 'system:no words', " system:no_words" ),
+ ( 'system:no words', "system:no words" ),
+ ( 'system:has words', "system:number of words: has words" ),
+ ( 'system:no words', "system:number of words: no words" ),
( 'system:is the best quality file of its duplicate group', "system:is the best quality file of its group" ),
( 'system:is not the best quality file of its duplicate group', "system:isn't the best quality file of its duplicate group" ),
( 'system:is not the best quality file of its duplicate group', 'system:is not the best quality file of its duplicate group' ),
@@ -1672,10 +1714,12 @@ def test_system_predicate_parsing( self ):
( 'system:has tags', "system:has tags" ),
( 'system:untagged', "system:no tags" ),
( 'system:untagged', "system:untagged" ),
- ( 'system:has human-readable embedded metadata', "system:has human readable embedded metadata" ),
- ( 'system:no human-readable embedded metadata', "system:no human readable embedded metadata" ),
- ( 'system:has human-readable embedded metadata', "system:has embedded metadata" ),
- ( 'system:no human-readable embedded metadata', "system:no embedded metadata" ),
+ ( 'system:has embedded metadata', "system:has human readable embedded metadata" ),
+ ( 'system:no embedded metadata', "system:no human readable embedded metadata" ),
+ ( 'system:has embedded metadata', "system:has human-readable embedded metadata" ),
+ ( 'system:no embedded metadata', "system:no human-readable embedded metadata" ),
+ ( 'system:has embedded metadata', "system:has embedded metadata" ),
+ ( 'system:no embedded metadata', "system:no embedded metadata" ),
( 'system:has icc profile', "system:has icc profile" ),
( 'system:no icc profile', "system:no icc profile" ),
( 'system:has forced filetype', "system:has forced filetype" ),
@@ -1686,16 +1730,12 @@ def test_system_predicate_parsing( self ):
( 'system:has tags', "system:number of tags > 0 " ),
( 'system:number of urls < 2', 'system:number of urls < 2' ),
( 'system:number of urls < 2', 'system:num urls < 2' ),
- ( 'system:number of urls: has urls', 'system:num urls > 0' ),
- ( 'system:number of urls: has urls', 'system:num urls: has urls' ),
- ( 'system:number of urls: no urls', 'system:num urls = 0' ),
- ( 'system:number of urls: no urls', 'system:number of urls: no urls' ),
+ ( 'system:has urls', 'system:num urls > 0' ),
+ ( 'system:no urls', 'system:num urls = 0' ),
( 'system:number of urls < 2', 'system:number of urls < 2' ),
( 'system:number of urls < 2', 'system:num urls < 2' ),
- ( 'system:number of words: has words', 'system:num words > 0' ),
- ( 'system:number of words: has words', 'system:num words: has words' ),
- ( 'system:number of words: no words', 'system:num words = 0' ),
- ( 'system:number of words: no words', 'system:number of words: no words' ),
+ ( 'system:has words', 'system:num words > 0' ),
+ ( 'system:no words', 'system:num words = 0' ),
( 'system:height = 600', "system:height = 600px" ),
( 'system:height = 800', "system:height is 800" ),
( 'system:height > 900', "system:height > 900" ),
@@ -1763,6 +1803,10 @@ def test_system_predicate_parsing( self ):
( 'system:duration < 5 seconds', "system:duration < 5 seconds" ),
( f'system:duration {HC.UNICODE_APPROX_EQUAL} 11 seconds {HC.UNICODE_PLUS_OR_MINUS}15%', "system:duration ~= 5 sec 6000 msecs" ),
( 'system:duration > 3 milliseconds', "system:duration > 3 milliseconds" ),
+ ( 'system:framerate < 60fps', "system:framerate < 60fps" ),
+ ( f'system:framerate {HC.UNICODE_APPROX_EQUAL} 12fps {HC.UNICODE_PLUS_OR_MINUS}15%', "system:framerate ~= 12fps" ),
+ ( 'system:number of frames < 600', "system:number of frames < 600" ),
+ ( f'system:number of frames {HC.UNICODE_APPROX_EQUAL} 120 {HC.UNICODE_PLUS_OR_MINUS}15%', "system:number of frames ~= 120" ),
( 'system:is pending to my files', "system:file service is pending to my files" ),
( 'system:is pending to my files', "system:file service is pending to MY FILES" ),
( 'system:is currently in my files', " system:file service currently in my files" ),
@@ -1796,17 +1840,10 @@ def test_system_predicate_parsing( self ):
( 'system:tag as number: page less than 5', "system:tag as number page < 5" ),
( 'system:tag as number: page less than 5', "system:tag as number: page less than 5" ),
( 'system:tag as number: page_underscore less than 5', "system:tag as number page_underscore < 5" ),
- ( 'system:number of notes: has notes', 'system:has note' ),
- ( 'system:number of notes: has notes', 'system:has notes' ),
- ( 'system:number of notes: no notes', 'system:no note' ),
- ( 'system:number of notes: no notes', 'system:has no note' ),
- ( 'system:number of notes: no notes', 'system:no notes' ),
- ( 'system:number of notes: no notes', 'system:does not have notes' ),
- ( 'system:number of notes: no notes', 'system:does not have a note' ),
( 'system:number of notes = 5', 'system:num notes = 5' ),
( 'system:number of notes > 1', 'system:number of notes > 1' ),
- ( 'system:number of notes: has notes', 'system:number of notes > 0' ),
- ( 'system:number of notes: no notes', 'system:number of notes = 0' ),
+ ( 'system:has notes', 'system:number of notes > 0' ),
+ ( 'system:no notes', 'system:number of notes = 0' ),
( 'system:has note with name "test"', 'system:has note with name test' ),
( 'system:has note with name "test"', 'system:has a note with name test' ),
( 'system:has note with name "test"', 'system:note with name test' ),
@@ -1816,27 +1853,31 @@ def test_system_predicate_parsing( self ):
( 'system:does not have note with name "test"', 'system:doesn\'t have note with name test' ),
( 'system:does not have note with name "test"', 'system:does not have a note with name test' ),
( 'system:does not have note with name "test"', 'system:does not have a note with name "test"' ),
- ( 'system:has a rating for example local rating numerical service', 'system:has rating example local rating numerical service' ),
- ( 'system:has a rating for example local rating numerical service', 'system:has a rating for example local rating numerical service' ),
- ( 'system:does not have a rating for example local rating numerical service', 'system:no rating example local rating numerical service' ),
- ( 'system:does not have a rating for example local rating numerical service', 'system:does not have a rating for example local rating numerical service' ),
- ( 'system:rating for example local rating numerical service = 3/5', 'system:rating for example local rating numerical service = 3/5' ),
- ( 'system:rating for example local rating numerical service = 3/5', 'system:rating for example local rating numerical service is 3/5' ),
- ( f'system:rating for example local rating numerical service {HC.UNICODE_APPROX_EQUAL} 3/5', f'system:rating for example local rating numerical service {HC.UNICODE_APPROX_EQUAL} 3/5' ),
- ( f'system:rating for example local rating numerical service {HC.UNICODE_APPROX_EQUAL} 3/5', 'system:rating for example local rating numerical service about 3/5' ),
- ( 'system:rating for example local rating numerical service < 3/5', 'system:rating for example local rating numerical service < 3/5' ),
- ( 'system:rating for example local rating numerical service < 3/5', 'system:rating for example local rating numerical service less than 3/5' ),
- ( 'system:rating for example local rating numerical service > 3/5', 'system:rating for example local rating numerical service > 3/5' ),
- ( 'system:rating for example local rating numerical service > 3/5', 'system:rating for example local rating numerical service more than 3/5' ),
- ( 'system:rating for example local rating like service = like', 'system:rating for example local rating like service = like' ),
- ( 'system:rating for example local rating like service = dislike', 'system:rating for example local rating like service = dislike' ),
- ( 'system:rating for example local rating inc/dec service = 123', 'system:rating for example local rating inc/dec service = 123' ),
- ( 'system:rating for example local rating inc/dec service > 123', 'system:rating for example local rating inc/dec service > 123' ),
- ( 'system:rating for example local rating inc/dec service > 123', 'system:rating example local rating inc/dec service more than 123' ),
- ( 'system:rating for example local rating inc/dec service < 123', 'system:rating for example local rating inc/dec service < 123' ),
- ( 'system:rating for example local rating inc/dec service < 123', 'system:rating for example local rating inc/dec service less than 123' ),
- ( f'system:rating for example local rating inc/dec service {HC.UNICODE_APPROX_EQUAL} 123', f'system:rating for example local rating inc/dec service {HC.UNICODE_APPROX_EQUAL} 123' ),
- ( f'system:rating for example local rating inc/dec service {HC.UNICODE_APPROX_EQUAL} 123', 'system:rating for example local rating inc/dec service about 123' ),
+ ( 'system:has rating for example local rating numerical service', 'system:has rating example local rating numerical service' ),
+ ( 'system:has rating for example local rating numerical service', 'system:has a rating for example local rating numerical service' ),
+ ( 'system:no rating for example local rating numerical service', 'system:no rating example local rating numerical service' ),
+ ( 'system:no rating for example local rating numerical service', 'system:does not have a rating for example local rating numerical service' ),
+ ( 'system:rating for example local rating numerical service is 3/5', 'system:rating for example local rating numerical service = 3/5' ),
+ ( 'system:rating for example local rating numerical service is 3/5', 'system:rating for example local rating numerical service is 3/5' ),
+ ( f'system:rating for example local rating numerical service is about 3/5', f'system:rating for example local rating numerical service {HC.UNICODE_APPROX_EQUAL} 3/5' ),
+ ( f'system:rating for example local rating numerical service is about 3/5', 'system:rating for example local rating numerical service about 3/5' ),
+ ( 'system:rating for example local rating numerical service less than 3/5', 'system:rating for example local rating numerical service < 3/5' ),
+ ( 'system:rating for example local rating numerical service less than 3/5', 'system:rating for example local rating numerical service less than 3/5' ),
+ ( 'system:rating for example local rating numerical service more than 3/5', 'system:rating for example local rating numerical service > 3/5' ),
+ ( 'system:rating for example local rating numerical service more than 3/5', 'system:rating for example local rating numerical service more than 3/5' ),
+ ( 'system:rating for example local rating like service is like', 'system:rating for example local rating like service = like' ),
+ ( 'system:rating for example local rating like service is dislike', 'system:rating for example local rating like service = dislike' ),
+ ( 'system:has count for example local rating inc/dec service', 'system:has rating for example local rating inc/dec service' ),
+ ( 'system:has count for example local rating inc/dec service', 'system:has a rating for example local rating inc/dec service' ),
+ ( 'system:no count for example local rating inc/dec service', 'system:no rating for example local rating inc/dec service' ),
+ ( 'system:no count for example local rating inc/dec service', 'system:does not have a rating for example local rating inc/dec service' ),
+ ( 'system:count for example local rating inc/dec service is 123', 'system:rating for example local rating inc/dec service = 123' ),
+ ( 'system:count for example local rating inc/dec service more than 123', 'system:rating for example local rating inc/dec service > 123' ),
+ ( 'system:count for example local rating inc/dec service more than 123', 'system:rating example local rating inc/dec service more than 123' ),
+ ( 'system:count for example local rating inc/dec service less than 123', 'system:rating for example local rating inc/dec service < 123' ),
+ ( 'system:count for example local rating inc/dec service less than 123', 'system:rating for example local rating inc/dec service less than 123' ),
+ ( f'system:count for example local rating inc/dec service is about 123', f'system:rating for example local rating inc/dec service {HC.UNICODE_APPROX_EQUAL} 123' ),
+ ( f'system:count for example local rating inc/dec service is about 123', 'system:rating for example local rating inc/dec service is about 123' ),
]:
( sys_pred, ) = ClientSearchParseSystemPredicates.ParseSystemPredicateStringsToPredicates( ( sys_pred_text, ) )
diff --git a/hydrus/test/TestDialogs.py b/hydrus/test/TestDialogs.py
index 1c254a2a2..69409a389 100644
--- a/hydrus/test/TestDialogs.py
+++ b/hydrus/test/TestDialogs.py
@@ -48,7 +48,7 @@ def OKChildDialog( window ):
def PressKey( window, key ):
- window.setFocus( QC.Qt.OtherFocusReason )
+ window.setFocus( QC.Qt.FocusReason.OtherFocusReason )
uias = QP.UIActionSimulator()