Compare commits

..

154 Commits

Author SHA1 Message Date
blitzman
4a6c0db4fa Bump to stable point release 2017-03-20 21:05:09 -04:00
blitzman
f31ec9a154 Merge branch 'master' of https://github.com/pyfa-org/Pyfa 2017-03-19 16:29:27 -04:00
blitzman
b4ad4becd3 Fix for #1060 2017-03-19 16:27:41 -04:00
Ryan Holmes
554807968a Update requirements.txt 2017-03-19 00:23:17 -04:00
Ryan Holmes
5cecb24654 Update README.md 2017-03-19 00:22:50 -04:00
blitzman
5911538e29 Do a version check for logbook 2017-03-19 00:22:17 -04:00
blitzman
2ff66970ad remove __all__ from contect menus 2017-03-16 19:05:07 -04:00
Ryan Holmes
58142664cc Merge pull request #1051 from Ebag333/Fix_PR943
Fix bad merge of #943
2017-03-16 18:58:09 -04:00
Ryan Holmes
73f6a68b9d Merge pull request #1047 from Ebag333/Hotfix_Implant_ContextMenu
Hotfix - Implant ContextMenu
2017-03-16 18:54:04 -04:00
Ryan Holmes
b88f4bc77f Merge pull request #1050 from Ebag333/Hotfix_Matplotlib
Hotfix - Matplotlib import
2017-03-16 18:52:48 -04:00
Ebag333
e3c2220a1c Fix matplotlib importing 2017-03-16 14:09:10 -07:00
Ebag333
26238e731d Fix matplotlib importing 2017-03-16 10:22:40 -07:00
Ebag333
1c59a27902 Fix typo and missing setting
(cherry picked from commit c122047)
2017-03-16 08:40:18 -07:00
Ebag333
6ce52a1da9 PEP8 cleanup
(cherry picked from commit 63f2f85)
2017-03-16 08:40:14 -07:00
blitzman
20ac2ad847 bump dev 2017-03-15 20:19:05 -04:00
blitzman
3b5fc7e13c Bump stable 2017-03-15 20:10:08 -04:00
blitzman
73a12aedeb Fix for #1041 2017-03-15 20:05:38 -04:00
blitzman
2ce6592048 Merge branch 'development' 2017-03-15 20:01:01 -04:00
blitzman
8b124f3194 Merge branch 'development' of https://github.com/pyfa-org/Pyfa into development 2017-03-15 00:03:01 -04:00
blitzman
588ac62b6f fix for #1039 2017-03-15 00:02:48 -04:00
Ryan Holmes
63001d83b9 Merge pull request #1037 from Ebag333/Fix_Outgoing_Minimal_View
Fix outgoingMinimal view
2017-03-15 00:00:43 -04:00
blitzman
1112415f93 update eve database 2017-03-14 19:40:44 -04:00
blitzman
4633a1c136 some clean up 2017-03-14 19:22:18 -04:00
Ebag333
8991e1dbad Fix the view. Not sure why it broke (it was working at one point). 2017-03-13 20:09:37 -07:00
blitzman
d5f6f7b37c Disable "minimal" selection for remote reps pane (doesn't work) 2017-03-13 21:49:31 -04:00
blitzman
4ffec21cd3 Add real icons for the new pref pages 2017-03-13 21:46:56 -04:00
blitzman
a4d07a4611 Revert "Merge pull request #1008 from Ebag333/local_tests"
This reverts commit 91b1b1dfc2, reversing
changes made to 53937d1723.
2017-03-11 20:02:10 -05:00
Ryan Holmes
91b1b1dfc2 Merge pull request #1008 from Ebag333/local_tests
Locale Testing
2017-03-11 19:34:53 -05:00
Ryan Holmes
53937d1723 Merge pull request #1023 from Ebag333/EosLoggingCleanup
Quick Eos cleanup and logging fix
2017-03-11 19:33:58 -05:00
Ryan Holmes
ad973dd51a Merge pull request #1034 from Ebag333/SpellingIsHurd
Spelling Is Hurd
2017-03-09 10:40:59 -05:00
Ebag333
9da485da55 Cleaning up tox errors that crept in by the pep8 gremlins. 2017-03-08 21:02:57 -08:00
Ebag333
da922601cd You say Potatoe, I say tomahtie 2017-03-08 20:59:53 -08:00
Ryan Holmes
58977810f9 Merge pull request #1030 from Will-W/new_tab
Add option to open fit in new tab
2017-03-06 21:14:51 -05:00
Will Wykeham
05facc6961 Remove semicolon caused by too much C programming 2017-03-06 14:03:27 +00:00
Will Wykeham
f8db54136e Add option to open fit in new tab
Works from context menu and on middle click - like a browser
2017-03-06 13:52:56 +00:00
Ryan Holmes
ce972ceddd Merge pull request #1029 from bjjerry/development
Fix for first low slot drag and drop
2017-03-06 00:09:23 -05:00
JJ Berry
9ad61c1e23 Need to enter if block in case of modPosition 0 as it refers to the first low slot 2017-03-05 18:54:17 -08:00
blitzman
1c4128b9a1 Change probe size cap to 1.08 (previous testing done apparently showed 1.1 - 1.12, however most people refer to 1.08 as the cap. 2017-03-05 17:01:00 -05:00
blitzman
2c4a2f7dd9 Fix probe size not updating #1026 2017-03-05 16:38:00 -05:00
blitzman
9befaf7c91 Test implementation of eos settings 2017-03-05 02:01:10 -05:00
Ryan Holmes
ab34b31219 Merge pull request #1022 from Ebag333/MoreLogging
Add additional logging to services
2017-03-01 00:44:43 -05:00
Ebag333
b9b56f5e73 kill another hidden import. 2017-02-28 19:23:01 -08:00
Ebag333
d28312f13b Slightly correction to the effective reload time for cycle time. 2017-02-28 18:57:12 -08:00
Ebag333
9134464a39 Fixes case that would generate a exception on the first fit calc (would trigger logging dumps for users). Also clean up a bunch of unnecessary try/exceptions, and some logging improvements 2017-02-28 11:28:41 -08:00
Ebag333
ff5d40dcd3 Add additional logging to services 2017-02-28 10:32:03 -08:00
Ryan Holmes
c97a388fc4 Merge pull request #1018 from Ebag333/Merge_New_Pane
Changes required to implement the remote reps pane
2017-02-26 23:48:27 -05:00
Ebag333
370b58d500 Changes required to implement the remote reps pane 2017-02-26 12:42:39 -08:00
Ryan Holmes
dbffdedc92 Merge pull request #996 from Ebag333/PreferencesPaneV2
Preferences Pane V4
2017-02-26 15:14:05 -05:00
Ebag333
712b1811b3 Tox fixes 2017-02-26 11:30:23 -08:00
Ebag333
f2fa598ed7 Merge branch 'PreferencesPaneV2' of https://github.com/Ebag333/Pyfa into PreferencesPaneV2 2017-02-26 11:29:52 -08:00
blitzman
5283c50f84 Merge branch 'master' into development
Conflicts:
	config.py
	gui/utils/exportHtml.py
2017-02-26 14:28:46 -05:00
Ebag333
ee7dbd5208 Tox fixes 2017-02-26 11:28:18 -08:00
blitzman
821298b6d0 tox 2017-02-26 14:26:32 -05:00
Ebag333
dc6cde86c9 Merge branch 'development' into PreferencesPaneV2 2017-02-26 11:22:10 -08:00
blitzman
1826122381 Clean up some syntax (use isinstance) and tox stuff 2017-02-26 14:17:46 -05:00
Ebag333
fbf35edae2 Fix capitalization and set up some logging to help catch errors. 2017-02-26 11:09:56 -08:00
Ryan Holmes
70934e05e1 Merge pull request #943 from Ebag333/More_Variations
Add variations for: drones, fighters, boosters, and implants
2017-02-26 13:33:13 -05:00
blitzman
b235dddbe0 Clean up to the sorting of implants and bosoters 2017-02-26 13:31:56 -05:00
blitzman
fc45cef834 Change order of RR pane 2017-02-26 12:56:31 -05:00
Ebag333
a43de00153 Tox fixes 2017-02-26 09:55:32 -08:00
Ebag333
949144ab25 Embiggen the preference pane window. Change text, and clean up some unused imports. 2017-02-26 09:54:58 -08:00
Ebag333
68e75025be Finish migrate from #868. Lots of tox/formatting/inspection fixes to the files touched. 2017-02-26 09:54:58 -08:00
Ebag333
77e5bebf13 Migrate files from #868 2017-02-26 09:54:57 -08:00
Ebag333
2a72d10bfc Commit file so we can migrate over from old PR 2017-02-26 09:54:57 -08:00
Ebag333
5aa5732606 Initial rollup of #868 2017-02-26 09:54:57 -08:00
Ryan Holmes
3209459e6d Merge pull request #995 from Ebag333/Remote_Repair_Pane_v2
Remote Repair Pane v2
2017-02-26 12:51:13 -05:00
blitzman
0bc0cb7f82 wx tweaks 2017-02-26 12:27:14 -05:00
Ebag333
4e9d2a5e18 Rename file so pytest doesn't think it's a test. 2017-02-26 08:55:29 -08:00
Ebag333
915df1c731 Merge branch 'development' into PreferencesPaneV2 2017-02-26 08:53:56 -08:00
Ebag333
7eb193cbbb Delete test, the method being tested no longer exists. 2017-02-26 08:52:27 -08:00
Ebag333
b63c5fa1ff Tox fixes 2017-02-26 08:50:13 -08:00
Ebag333
e4d0f2dc6f Fix case where property gets reran multiple times. 2017-02-26 08:46:23 -08:00
blitzmann
767cb46d40 Merge branch 'development' into More_Variations
Conflicts:
	gui/builtinContextMenus/metaSwap.py
2017-02-26 02:24:38 -05:00
blitzmann
c430a2d798 Merge branch 'development' into Remote_Repair_Pane_v2
Conflicts:
	eos/mathUtils.py
	service/crest.py
2017-02-26 01:40:53 -05:00
blitzman
1c273210e9 tox fixes 2017-02-26 00:44:37 -05:00
Ryan Holmes
db4e5e05a4 Merge pull request #1012 from Ebag333/PropertiesPane
Add Properties Pane
2017-02-26 00:17:38 -05:00
Ryan Holmes
c6483c4b98 Merge pull request #986 from Ebag333/LogBook_v2
Logbook V2
2017-02-25 23:59:30 -05:00
blitzman
94c098cce3 Fix path and graphFrame issue. I could have sworn these were already fixed during the previous merge... 2017-02-25 19:06:39 -05:00
blitzman
6eddfd2694 Merge branch 'development' into LogBook_v2
Conflicts:
	gui/bitmapLoader.py
2017-02-25 19:03:56 -05:00
blitzman
cb15a3a1d6 Merge branch 'master' into development 2017-02-25 19:02:15 -05:00
blitzman
cd516f80bb bump dev 2017-02-25 19:01:57 -05:00
blitzman
1d9b85c043 Merge branch 'master' into development 2017-02-25 19:01:28 -05:00
blitzman
1ae5f64696 Here we go again! 2017-02-25 18:50:20 -05:00
blitzman
bf24e5f518 Fix for #1014 2017-02-25 18:47:03 -05:00
blitzman
7115cd178c Merge branch 'development' into LogBook_v2
Conflicts:
	config.py
	eos/saveddata/fit.py
	gui/bitmapLoader.py
	gui/graphFrame.py
	gui/utils/exportHtml.py
	pyfa.py
	service/crest.py
	service/price.py
	service/server.py
2017-02-25 18:21:07 -05:00
blitzman
6da806c9c5 Merge branch 'master' into development 2017-02-25 17:49:26 -05:00
blitzman
4895d76bea bump dev 2017-02-25 17:49:06 -05:00
blitzman
f527f9533e Bump stable for real 2017-02-25 17:38:14 -05:00
blitzman
5adfcbf343 Add links in the error dialog 2017-02-25 17:37:38 -05:00
blitzman
c0e0612e02 Merge branch 'errortest' 2017-02-25 17:12:19 -05:00
blitzman
4ba4a753cb Implement and error message if config fails 2017-02-25 17:12:09 -05:00
blitzman
3cbd154c8a Fix for #1013 2017-02-25 16:44:31 -05:00
blitzman
6ab936ef09 lol jk 2017-02-25 14:41:40 -05:00
Ebag333
3383153b66 Little cleanup 2017-02-25 11:23:07 -08:00
Ebag333
04b412dd5b Test walking subdirectories, and using new unicode path 2017-02-25 11:20:40 -08:00
Ebag333
ac20030612 Simple wx file dialog test 2017-02-25 10:52:52 -08:00
blitzman
083c5d1722 Bump stable 2017-02-25 13:23:21 -05:00
Ebag333
baec24ef14 get rid of unused method 2017-02-24 21:28:23 -08:00
Ebag333
df7f0b20f5 Tox cleanup 2017-02-24 21:25:13 -08:00
Ebag333
e5e3133869 Catch exceptions if we get them from a property. Exceptions are bad, but not showing the window is worse. 2017-02-24 21:18:38 -08:00
Ebag333
ca6ef546e5 Add debug pane for item stats 2017-02-24 18:09:22 -08:00
Ebag333
c24798ad2f Clean up last reference and finish nuking floatfloor. One less method! (ouch my test!) 2017-02-24 17:07:05 -08:00
Ebag333
3411eca1e7 Fix a couple pep8 issues, and clean up other folks pep8 misses. :) 2017-02-24 16:34:32 -08:00
Ebag333
43073069c7 Fix merge conflict. 2017-02-24 16:25:31 -08:00
Ebag333
db330ad035 Merge remote-tracking branch 'origin/Remote_Repair_Pane_v2' into Remote_Repair_Pane_v2
# Conflicts:
#	eos/saveddata/fit.py
2017-02-24 16:15:39 -08:00
Ebag333
b3c7273681 implement a suggestion or six from @blitzmann. Fix a bunch of low level bugs (unlikely to have been noticed since we don't really expose those stats). Add some properties instead of referencing the attribute directly. 2017-02-24 16:10:54 -08:00
blitzman
3892ac5996 Change functionality of remote rep calculations to be more efficient.
* Implement a property per attribute
* Use None as our cleared value (signifies that it really hasn't been calculated, not just that the calc was 0)
2017-02-24 18:33:42 -05:00
Ryan Holmes
249841c0db Update pyfa.spec 2017-02-24 16:55:22 -05:00
Ryan Holmes
8900674b72 Merge pull request #978 from Ebag333/OneClickBuilds
One Click Builds
2017-02-24 16:52:58 -05:00
Ebag333
5ca882d3ea implement a suggestion from @blitzmann 2017-02-24 13:06:02 -08:00
Ebag333
516ff6f8e6 Fix .gitignore so it stops ignoring pyfa.spec. RUDE! 2017-02-24 12:14:39 -08:00
Ryan Holmes
dc9c481b80 Merge remote-tracking branch 'blitzmann/unicodetests' 2017-02-24 09:54:50 -05:00
Ebag333
5c55290944 rename some tests 2017-02-24 04:04:43 -08:00
Ebag333
86a145f01c some cleanup 2017-02-24 03:47:21 -08:00
Ebag333
78597a8554 Add hebrew and chinese 2017-02-24 03:34:16 -08:00
Ebag333
0288ba6012 Initial submission for locale testing 2017-02-24 03:27:29 -08:00
blitzman
bada45ec69 Merge branch 'master' of https://github.com/pyfa-org/Pyfa 2017-02-24 00:11:30 -05:00
blitzman
ee407913c3 Fix for #1005 2017-02-24 00:11:24 -05:00
blitzman
6e643d7579 Encode the SSL paths for frozen environments (to fix #548) 2017-02-23 23:53:57 -05:00
Ryan Holmes
b8c771f747 Merge pull request #1006 from Ebag333/Pyfa_tests
Tests for Pyfa
2017-02-21 11:04:50 -05:00
Ebag333
a07e1d8f3a Test actual correct value, not the wrong value that was being generated by old hacky method. 2017-02-21 07:43:04 -08:00
Ebag333
565a78610e Fix floorfloat, and pep8 fixes for tests 2017-02-21 07:38:32 -08:00
Ebag333
cdbf4cf5ec Initial pass at tests 2017-02-20 16:35:53 -08:00
Ryan Holmes
ec5c7e5d0e Update README.md 2017-02-20 00:43:58 -05:00
blitzman
b583589849 Remove unicode path fixes for now to better test original problems 2017-02-19 01:01:12 -05:00
blitzman
c3144088ca Fix CREST login (#998). Also flesh out a two-step process:
Step 1: get a page that lets the user know we're processing request. Do whatever we need to do with the hash (in the case of implicit mode), and then refresh to step 2
Step 2: process response, and return error / success message.
2017-02-18 21:59:18 -05:00
Ryan Holmes
7df3fe266e Merge pull request #1002 from Ebag333/matplotlib_import_fix
Matplotlib Import Fix
2017-02-18 18:17:17 -05:00
blitzman
b351befa97 Fix for #1000 2017-02-18 18:14:21 -05:00
blitzman
3925386109 bump dev 2017-02-18 18:14:03 -05:00
Ebag333
cdf392e3e5 Change matplotlib to be imported at the top and not use the import hack. Slows down the startup times slightly, but much simpler. 2017-02-18 10:15:06 -08:00
Ebag333
e8101f9410 Embiggen the preference pane window. Change text, and clean up some unused imports. 2017-02-16 10:45:28 -08:00
Ebag333
56f1e9ed3a Finish migrate from #868. Lots of tox/formatting/inspection fixes to the files touched. 2017-02-16 09:59:56 -08:00
Ebag333
fff67906d6 Migrate files from #868 2017-02-16 08:20:09 -08:00
Ebag333
343331c667 Commit file so we can migrate over from old PR 2017-02-16 08:16:08 -08:00
Ebag333
201d596c80 Initial rollup of #868 2017-02-16 07:38:43 -08:00
Ebag333
eb97a1c11c Add tooltip and also a minimal version of the view (for future state). 2017-02-15 19:11:32 -08:00
Ebag333
13996d7770 Stupid change list making me miss files. 2017-02-15 18:50:35 -08:00
Ebag333
6e04707457 Commit *ALL* the files. 2017-02-15 18:25:11 -08:00
Ebag333
25b17a221c Update to current version 2017-02-15 18:23:14 -08:00
Ebag333
93e7b4f5cf Cherry pick d033e4a8d919664d3ae210098291e38df71d3256 2017-02-15 17:23:53 -08:00
Ebag333
159472f82c Tox fixes, and convert all logging string replacement to a standard style. 2017-02-13 13:38:21 -08:00
Ebag333
be9eaa0859 Fix timer, make logging less annoying when at Info or higher level. Only show command bonus logspam if there is any. 2017-02-13 13:25:42 -08:00
Ebag333
35e330f574 Improve handling for exceptions, output more information so we can find and fix problems. 2017-02-12 13:09:26 -08:00
Ebag333
e7a5cb4b1d Don't show both messages if we don't have the zip files 2017-02-12 12:21:57 -08:00
Ebag333
441b3f1646 Handle print() messages when not frozen and in debug. We only want to exclude handling stacktraces, so the IDE gets clickable line numbers. 2017-02-11 11:59:49 -08:00
Ebag333
3b185e1bcb Roll up all changes from #962 as the base has been massively changed by code cleanups. Rename our logging to a universal pyfalog to avoid shadowing either Logging or Logbook loggers 2017-02-11 11:51:53 -08:00
Ebag333
a3501b925d update reqs
(cherry picked from commit 25c6b47)
2017-02-08 13:19:03 -08:00
Ebag333
e47b0f3c44 Add individual builds
(cherry picked from commit 0c30dba)
2017-02-08 13:18:59 -08:00
Ebag333
518f7c9e03 build improvements
(cherry picked from commit 4500bc1)
2017-02-08 13:18:51 -08:00
Ebag333
91d57ded8a Add cert
(cherry picked from commit b12cbaf)
2017-02-08 13:18:48 -08:00
Ebag333
86fd1be8b2 requirements for build
(cherry picked from commit 6992a15)
2017-02-08 13:18:34 -08:00
Ebag333
ade47bed8b Add filtering for implants and boosters 2017-01-12 12:30:25 -08:00
Ebag333
ea12ec9cd1 Now can add 1000 ammo stacks or 5 drone stacks through context menu 2017-01-12 08:45:16 -08:00
Ebag333
3774e3bca0 add variations for drones, fighters, boosters, and implants 2017-01-12 01:24:26 -08:00
119 changed files with 8586 additions and 711 deletions

1
.gitignore vendored
View File

@@ -49,7 +49,6 @@ Pyfa.egg-info/
# Usually these files are written by a python script from a template # Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it. # before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest *.manifest
*.spec
# Installer logs # Installer logs
pip-log.txt pip-log.txt

View File

@@ -42,6 +42,7 @@ If you wish to help with development or simply need to run pyfa through a Python
* `dateutil` * `dateutil`
* `matplotlib` (for some Linux distributions you may need to install separate wxPython bindings such as `python-matplotlib-wx`) * `matplotlib` (for some Linux distributions you may need to install separate wxPython bindings such as `python-matplotlib-wx`)
* `requests` * `requests`
* `logbook` >= 1.0.0
## Bug Reporting ## Bug Reporting
The preferred method of reporting bugs is through the project's [GitHub Issues interface](https://github.com/pyfa-org/Pyfa/issues). Alternatively, posting a report in the [pyfa thread](http://forums.eveonline.com/default.aspx?g=posts&t=247609) on the official EVE Online forums is acceptable. Guidelines for bug reporting can be found on [this wiki page](https://github.com/DarkFenX/Pyfa/wiki/Bug-Reporting). The preferred method of reporting bugs is through the project's [GitHub Issues interface](https://github.com/pyfa-org/Pyfa/issues). Alternatively, posting a report in the [pyfa thread](http://forums.eveonline.com/default.aspx?g=posts&t=247609) on the official EVE Online forums is acceptable. Guidelines for bug reporting can be found on [this wiki page](https://github.com/DarkFenX/Pyfa/wiki/Bug-Reporting).
@@ -51,8 +52,7 @@ pyfa is licensed under the GNU GPL v3.0, see LICENSE
## Resources ## Resources
* Development repository: [https://github.com/pyfa-org/Pyfa](https://github.com/pyfa-org/Pyfa) * Development repository: [https://github.com/pyfa-org/Pyfa](https://github.com/pyfa-org/Pyfa)
* XMPP conference: [pyfa@conference.jabber.org](pyfa@conference.jabber.org) * [EVE forum thread](https://forums.eveonline.com/default.aspx?g=posts&t=466425)
* [EVE forum thread](http://forums.eveonline.com/default.aspx?g=posts&t=247609)
* [EVE University guide using pyfa](http://wiki.eveuniversity.org/Guide_to_using_PYFA) * [EVE University guide using pyfa](http://wiki.eveuniversity.org/Guide_to_using_PYFA)
* [EVE Online website](http://www.eveonline.com/) * [EVE Online website](http://www.eveonline.com/)

123
config.py
View File

@@ -1,27 +1,28 @@
import os import os
import sys import sys
# TODO: move all logging back to pyfa.py main loop from logbook import Logger
# We moved it here just to avoid rebuilding windows skeleton for now (any change to pyfa.py needs it)
import logging pyfalog = Logger(__name__)
import logging.handlers
# Load variable overrides specific to distribution type # Load variable overrides specific to distribution type
try: try:
import configforced import configforced
except ImportError: except ImportError:
pyfalog.warning("Failed to import: configforced")
configforced = None configforced = None
# Turns on debug mode # Turns on debug mode
debug = False debug = False
# Defines if our saveddata will be in pyfa root or not # Defines if our saveddata will be in pyfa root or not
saveInRoot = False saveInRoot = False
# Version data # Version data
version = "1.27.0" version = "1.28.1"
tag = "Stable" tag = "Stable"
expansionName = "YC119.2" expansionName = "YC119.3"
expansionVersion = "1.2" expansionVersion = "1.0"
evemonMinVersion = "4081" evemonMinVersion = "4081"
pyfaPath = None pyfaPath = None
@@ -30,22 +31,6 @@ saveDB = None
gameDB = None gameDB = None
class StreamToLogger(object):
"""
Fake file-like stream object that redirects writes to a logger instance.
From: http://www.electricmonk.nl/log/2011/08/14/redirect-stdout-and-stderr-to-a-logger-in-python/
"""
def __init__(self, logger, log_level=logging.INFO):
self.logger = logger
self.log_level = log_level
self.linebuf = ''
def write(self, buf):
for line in buf.rstrip().splitlines():
self.logger.log(self.log_level, line.rstrip())
def isFrozen(): def isFrozen():
if hasattr(sys, 'frozen'): if hasattr(sys, 'frozen'):
return True return True
@@ -58,6 +43,17 @@ def __createDirs(path):
os.makedirs(path) os.makedirs(path)
def getPyfaRoot():
base = getattr(sys.modules['__main__'], "__file__", sys.executable) if isFrozen() else sys.argv[0]
root = os.path.dirname(os.path.realpath(os.path.abspath(base)))
root = unicode(root, sys.getfilesystemencoding())
return root
def getDefaultSave():
return unicode(os.path.expanduser(os.path.join("~", ".pyfa")), sys.getfilesystemencoding())
def defPaths(customSavePath): def defPaths(customSavePath):
global debug global debug
global pyfaPath global pyfaPath
@@ -66,65 +62,42 @@ def defPaths(customSavePath):
global gameDB global gameDB
global saveInRoot global saveInRoot
if debug: pyfalog.debug("Configuring Pyfa")
logLevel = logging.DEBUG
else:
logLevel = logging.WARN
# The main pyfa directory which contains run.py # The main pyfa directory which contains run.py
# Python 2.X uses ANSI by default, so we need to convert the character encoding # Python 2.X uses ANSI by default, so we need to convert the character encoding
pyfaPath = getattr(configforced, "pyfaPath", pyfaPath) pyfaPath = getattr(configforced, "pyfaPath", pyfaPath)
if pyfaPath is None: if pyfaPath is None:
pyfaPath = getPyfaPath() pyfaPath = getPyfaRoot()
# Where we store the saved fits etc, default is the current users home directory # Where we store the saved fits etc, default is the current users home directory
if saveInRoot is True: if saveInRoot is True:
savePath = getattr(configforced, "savePath", None) savePath = getattr(configforced, "savePath", None)
if savePath is None: if savePath is None:
savePath = getPyfaPath("saveddata") savePath = os.path.join(pyfaPath, "saveddata")
else: else:
savePath = getattr(configforced, "savePath", None) savePath = getattr(configforced, "savePath", None)
if savePath is None: if savePath is None:
if customSavePath is None: # customSavePath is not overriden if customSavePath is None: # customSavePath is not overriden
savePath = os.path.expanduser(os.path.join("~", ".pyfa")) savePath = getDefaultSave()
else: else:
savePath = customSavePath savePath = customSavePath
__createDirs(savePath) __createDirs(savePath)
if isFrozen(): if isFrozen():
certName = "cacert.pem" os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(pyfaPath, "cacert.pem").encode('utf8')
os.environ["REQUESTS_CA_BUNDLE"] = getPyfaPath(certName).encode('utf8') os.environ["SSL_CERT_FILE"] = os.path.join(pyfaPath, "cacert.pem").encode('utf8')
os.environ["SSL_CERT_FILE"] = getPyfaPath(certName).encode('utf8')
loggingFormat = '%(asctime)s %(name)-24s %(levelname)-8s %(message)s'
logging.basicConfig(format=loggingFormat, level=logLevel)
handler = logging.handlers.RotatingFileHandler(getSavePath("log.txt"), maxBytes=1000000, backupCount=3)
formatter = logging.Formatter(loggingFormat)
handler.setFormatter(formatter)
logging.getLogger('').addHandler(handler)
logging.info("Starting pyfa")
if hasattr(sys, 'frozen'):
stdout_logger = logging.getLogger('STDOUT')
sl = StreamToLogger(stdout_logger, logging.INFO)
sys.stdout = sl
# This interferes with cx_Freeze's own handling of exceptions. Find a way to fix this.
# stderr_logger = logging.getLogger('STDERR')
# sl = StreamToLogger(stderr_logger, logging.ERROR)
# sys.stderr = sl
# The database where we store all the fits etc # The database where we store all the fits etc
saveDB = getSavePath("saveddata.db") saveDB = os.path.join(savePath, "saveddata.db")
# The database where the static EVE data from the datadump is kept. # The database where the static EVE data from the datadump is kept.
# This is not the standard sqlite datadump but a modified version created by eos # This is not the standard sqlite datadump but a modified version created by eos
# maintenance script # maintenance script
gameDB = getPyfaPath("eve.db") gameDB = os.path.join(pyfaPath, "eve.db")
# DON'T MODIFY ANYTHING BELOW! # DON'T MODIFY ANYTHING BELOW
import eos.config import eos.config
# Caching modifiers, disable all gamedata caching, its unneeded. # Caching modifiers, disable all gamedata caching, its unneeded.
@@ -133,40 +106,6 @@ def defPaths(customSavePath):
eos.config.saveddata_connectionstring = "sqlite:///" + saveDB + "?check_same_thread=False" eos.config.saveddata_connectionstring = "sqlite:///" + saveDB + "?check_same_thread=False"
eos.config.gamedata_connectionstring = "sqlite:///" + gameDB + "?check_same_thread=False" eos.config.gamedata_connectionstring = "sqlite:///" + gameDB + "?check_same_thread=False"
# initialize the settings
def getPyfaPath(Append=None): from service.settings import EOSSettings
base = getattr(sys.modules['__main__'], "__file__", sys.executable) if isFrozen() else sys.argv[0] eos.config.settings = EOSSettings.getInstance().EOSSettings # this is kind of confusing, but whatever
root = os.path.dirname(os.path.realpath(os.path.abspath(base)))
if Append:
path = parsePath(root, Append)
else:
path = parsePath(root)
return path
def getSavePath(Append=None):
root = savePath
if Append:
path = parsePath(root, Append)
else:
path = parsePath(root)
return path
def parsePath(root, Append=None):
if Append:
path = os.path.join(root, Append)
else:
path = root
if type(path) == str: # leave unicode ones alone
try:
path = path.decode('utf8')
except UnicodeDecodeError:
path = path.decode('windows-1252')
return path

5666
dist_assets/cacert.pem Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,9 @@ gamedata_connectionstring = 'sqlite:///' + unicode(realpath(join(dirname(abspath
saveddata_connectionstring = 'sqlite:///' + unicode( saveddata_connectionstring = 'sqlite:///' + unicode(
realpath(join(dirname(abspath(__file__)), "..", "saveddata", "saveddata.db")), sys.getfilesystemencoding()) realpath(join(dirname(abspath(__file__)), "..", "saveddata", "saveddata.db")), sys.getfilesystemencoding())
settings = {
"setting1": True
}
# Autodetect path, only change if the autodetection bugs out. # Autodetect path, only change if the autodetection bugs out.
path = dirname(unicode(__file__, sys.getfilesystemencoding())) path = dirname(unicode(__file__, sys.getfilesystemencoding()))

View File

@@ -24,6 +24,9 @@ from sqlalchemy.orm import sessionmaker
import migration import migration
from eos import config from eos import config
from logbook import Logger
pyfalog = Logger(__name__)
class ReadOnlyException(Exception): class ReadOnlyException(Exception):
@@ -46,7 +49,9 @@ try:
config.gamedata_version = gamedata_session.execute( config.gamedata_version = gamedata_session.execute(
"SELECT `field_value` FROM `metadata` WHERE `field_name` LIKE 'client_build'" "SELECT `field_value` FROM `metadata` WHERE `field_name` LIKE 'client_build'"
).fetchone()[0] ).fetchone()[0]
except: except Exception as e:
pyfalog.warning("Missing gamedata version.")
pyfalog.critical(e)
config.gamedata_version = None config.gamedata_version = None
saveddata_connectionstring = config.saveddata_connectionstring saveddata_connectionstring = config.saveddata_connectionstring

View File

@@ -23,6 +23,7 @@ from sqlalchemy.sql import and_, or_, select
import eos.config import eos.config
from eos.db import gamedata_session from eos.db import gamedata_session
from eos.db.gamedata.metaGroup import metatypes_table, items_table from eos.db.gamedata.metaGroup import metatypes_table, items_table
from eos.db.gamedata.group import groups_table
from eos.db.util import processEager, processWhere from eos.db.util import processEager, processWhere
from eos.gamedata import AlphaClone, Attribute, Category, Group, Item, MarketGroup, MetaGroup, AttributeInfo, MetaData from eos.gamedata import AlphaClone, Attribute, Category, Group, Item, MarketGroup, MetaGroup, AttributeInfo, MetaData
@@ -249,7 +250,7 @@ def searchItems(nameLike, where=None, join=None, eager=None):
@cachedQuery(2, "where", "itemids") @cachedQuery(2, "where", "itemids")
def getVariations(itemids, where=None, eager=None): def getVariations(itemids, groupIDs=None, where=None, eager=None):
for itemid in itemids: for itemid in itemids:
if not isinstance(itemid, int): if not isinstance(itemid, int):
raise TypeError("All passed item IDs must be integers") raise TypeError("All passed item IDs must be integers")
@@ -262,6 +263,16 @@ def getVariations(itemids, where=None, eager=None):
joinon = items_table.c.typeID == metatypes_table.c.typeID joinon = items_table.c.typeID == metatypes_table.c.typeID
vars = gamedata_session.query(Item).options(*processEager(eager)).join((metatypes_table, joinon)).filter( vars = gamedata_session.query(Item).options(*processEager(eager)).join((metatypes_table, joinon)).filter(
filter).all() filter).all()
if vars:
return vars
elif groupIDs:
itemfilter = or_(*(groups_table.c.groupID == groupID for groupID in groupIDs))
filter = processWhere(itemfilter, where)
joinon = items_table.c.groupID == groups_table.c.groupID
vars = gamedata_session.query(Item).options(*processEager(eager)).join((groups_table, joinon)).filter(
filter).all()
return vars return vars

View File

@@ -1,11 +1,11 @@
import logging from logbook import Logger
import shutil import shutil
import time import time
import config import config
import migrations import migrations
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
def getVersion(db): def getVersion(db):
@@ -37,7 +37,7 @@ def update(saveddata_engine):
for version in xrange(dbVersion, appVersion): for version in xrange(dbVersion, appVersion):
func = migrations.updates[version + 1] func = migrations.updates[version + 1]
if func: if func:
logger.info("Applying database update: %d", version + 1) pyfalog.info("Applying database update: {0}", version + 1)
func(saveddata_engine) func(saveddata_engine)
# when all is said and done, set version to current # when all is said and done, set version to current

View File

@@ -18,9 +18,9 @@
# =============================================================================== # ===============================================================================
from sqlalchemy.exc import DatabaseError from sqlalchemy.exc import DatabaseError
import logging from logbook import Logger
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class DatabaseCleanup(object): class DatabaseCleanup(object):
@@ -33,7 +33,7 @@ class DatabaseCleanup(object):
results = saveddata_engine.execute(query) results = saveddata_engine.execute(query)
return results return results
except DatabaseError: except DatabaseError:
logger.error("Failed to connect to database or error executing query:\n%s", query) pyfalog.error("Failed to connect to database or error executing query:\n{0}", query)
return None return None
@staticmethod @staticmethod
@@ -41,7 +41,7 @@ class DatabaseCleanup(object):
# Find orphaned character skills. # Find orphaned character skills.
# This solves an issue where the character doesn't exist, but skills for that character do. # This solves an issue where the character doesn't exist, but skills for that character do.
# See issue #917 # See issue #917
logger.debug("Running database cleanup for character skills.") pyfalog.debug("Running database cleanup for character skills.")
query = "SELECT COUNT(*) AS num FROM characterSkills WHERE characterID NOT IN (SELECT ID from characters)" query = "SELECT COUNT(*) AS num FROM characterSkills WHERE characterID NOT IN (SELECT ID from characters)"
results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
@@ -53,14 +53,14 @@ class DatabaseCleanup(object):
if row and row['num']: if row and row['num']:
query = "DELETE FROM characterSkills WHERE characterID NOT IN (SELECT ID from characters)" query = "DELETE FROM characterSkills WHERE characterID NOT IN (SELECT ID from characters)"
delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
logger.error("Database corruption found. Cleaning up %d records.", delete.rowcount) pyfalog.error("Database corruption found. Cleaning up {0} records.", delete.rowcount)
@staticmethod @staticmethod
def OrphanedFitDamagePatterns(saveddata_engine): def OrphanedFitDamagePatterns(saveddata_engine):
# Find orphaned damage patterns. # Find orphaned damage patterns.
# This solves an issue where the damage pattern doesn't exist, but fits reference the pattern. # This solves an issue where the damage pattern doesn't exist, but fits reference the pattern.
# See issue #777 # See issue #777
logger.debug("Running database cleanup for orphaned damage patterns attached to fits.") pyfalog.debug("Running database cleanup for orphaned damage patterns attached to fits.")
query = "SELECT COUNT(*) AS num FROM fits WHERE damagePatternID NOT IN (SELECT ID FROM damagePatterns) OR damagePatternID IS NULL" query = "SELECT COUNT(*) AS num FROM fits WHERE damagePatternID NOT IN (SELECT ID FROM damagePatterns) OR damagePatternID IS NULL"
results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
@@ -80,20 +80,20 @@ class DatabaseCleanup(object):
rows = uniform_results.fetchall() rows = uniform_results.fetchall()
if len(rows) == 0: if len(rows) == 0:
logger.error("Missing uniform damage pattern.") pyfalog.error("Missing uniform damage pattern.")
elif len(rows) > 1: elif len(rows) > 1:
logger.error("More than one uniform damage pattern found.") pyfalog.error("More than one uniform damage pattern found.")
else: else:
uniform_damage_pattern_id = rows[0]['ID'] uniform_damage_pattern_id = rows[0]['ID']
update_query = "UPDATE 'fits' SET 'damagePatternID' = {} " \ update_query = "UPDATE 'fits' SET 'damagePatternID' = {} " \
"WHERE damagePatternID NOT IN (SELECT ID FROM damagePatterns) OR damagePatternID IS NULL".format(uniform_damage_pattern_id) "WHERE damagePatternID NOT IN (SELECT ID FROM damagePatterns) OR damagePatternID IS NULL".format(uniform_damage_pattern_id)
update_results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, update_query) update_results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, update_query)
logger.error("Database corruption found. Cleaning up %d records.", update_results.rowcount) pyfalog.error("Database corruption found. Cleaning up {0} records.", update_results.rowcount)
@staticmethod @staticmethod
def OrphanedFitCharacterIDs(saveddata_engine): def OrphanedFitCharacterIDs(saveddata_engine):
# Find orphaned character IDs. This solves an issue where the character doesn't exist, but fits reference the pattern. # Find orphaned character IDs. This solves an issue where the character doesn't exist, but fits reference the pattern.
logger.debug("Running database cleanup for orphaned characters attached to fits.") pyfalog.debug("Running database cleanup for orphaned characters attached to fits.")
query = "SELECT COUNT(*) AS num FROM fits WHERE characterID NOT IN (SELECT ID FROM characters) OR characterID IS NULL" query = "SELECT COUNT(*) AS num FROM fits WHERE characterID NOT IN (SELECT ID FROM characters) OR characterID IS NULL"
results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
@@ -113,22 +113,22 @@ class DatabaseCleanup(object):
rows = all5_results.fetchall() rows = all5_results.fetchall()
if len(rows) == 0: if len(rows) == 0:
logger.error("Missing 'All 5' character.") pyfalog.error("Missing 'All 5' character.")
elif len(rows) > 1: elif len(rows) > 1:
logger.error("More than one 'All 5' character found.") pyfalog.error("More than one 'All 5' character found.")
else: else:
all5_id = rows[0]['ID'] all5_id = rows[0]['ID']
update_query = "UPDATE 'fits' SET 'characterID' = " + str(all5_id) + \ update_query = "UPDATE 'fits' SET 'characterID' = " + str(all5_id) + \
" WHERE characterID not in (select ID from characters) OR characterID IS NULL" " WHERE characterID not in (select ID from characters) OR characterID IS NULL"
update_results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, update_query) update_results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, update_query)
logger.error("Database corruption found. Cleaning up %d records.", update_results.rowcount) pyfalog.error("Database corruption found. Cleaning up {0} records.", update_results.rowcount)
@staticmethod @staticmethod
def NullDamagePatternNames(saveddata_engine): def NullDamagePatternNames(saveddata_engine):
# Find damage patterns that are missing the name. # Find damage patterns that are missing the name.
# This solves an issue where the damage pattern ends up with a name that is null. # This solves an issue where the damage pattern ends up with a name that is null.
# See issue #949 # See issue #949
logger.debug("Running database cleanup for missing damage pattern names.") pyfalog.debug("Running database cleanup for missing damage pattern names.")
query = "SELECT COUNT(*) AS num FROM damagePatterns WHERE name IS NULL OR name = ''" query = "SELECT COUNT(*) AS num FROM damagePatterns WHERE name IS NULL OR name = ''"
results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
@@ -140,14 +140,14 @@ class DatabaseCleanup(object):
if row and row['num']: if row and row['num']:
query = "DELETE FROM damagePatterns WHERE name IS NULL OR name = ''" query = "DELETE FROM damagePatterns WHERE name IS NULL OR name = ''"
delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
logger.error("Database corruption found. Cleaning up %d records.", delete.rowcount) pyfalog.error("Database corruption found. Cleaning up {0} records.", delete.rowcount)
@staticmethod @staticmethod
def NullTargetResistNames(saveddata_engine): def NullTargetResistNames(saveddata_engine):
# Find target resists that are missing the name. # Find target resists that are missing the name.
# This solves an issue where the target resist ends up with a name that is null. # This solves an issue where the target resist ends up with a name that is null.
# See issue #949 # See issue #949
logger.debug("Running database cleanup for missing target resist names.") pyfalog.debug("Running database cleanup for missing target resist names.")
query = "SELECT COUNT(*) AS num FROM targetResists WHERE name IS NULL OR name = ''" query = "SELECT COUNT(*) AS num FROM targetResists WHERE name IS NULL OR name = ''"
results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
@@ -159,14 +159,14 @@ class DatabaseCleanup(object):
if row and row['num']: if row and row['num']:
query = "DELETE FROM targetResists WHERE name IS NULL OR name = ''" query = "DELETE FROM targetResists WHERE name IS NULL OR name = ''"
delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
logger.error("Database corruption found. Cleaning up %d records.", delete.rowcount) pyfalog.error("Database corruption found. Cleaning up {0} records.", delete.rowcount)
@staticmethod @staticmethod
def OrphanedFitIDItemID(saveddata_engine): def OrphanedFitIDItemID(saveddata_engine):
# Orphaned items that are missing the fit ID or item ID. # Orphaned items that are missing the fit ID or item ID.
# See issue #954 # See issue #954
for table in ['drones', 'cargo', 'fighters']: for table in ['drones', 'cargo', 'fighters']:
logger.debug("Running database cleanup for orphaned %s items.", table) pyfalog.debug("Running database cleanup for orphaned {0} items.", table)
query = "SELECT COUNT(*) AS num FROM {} WHERE itemID IS NULL OR itemID = '' or itemID = '0' or fitID IS NULL OR fitID = '' or fitID = '0'".format( query = "SELECT COUNT(*) AS num FROM {} WHERE itemID IS NULL OR itemID = '' or itemID = '0' or fitID IS NULL OR fitID = '' or fitID = '0'".format(
table) table)
results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
@@ -180,10 +180,10 @@ class DatabaseCleanup(object):
query = "DELETE FROM {} WHERE itemID IS NULL OR itemID = '' or itemID = '0' or fitID IS NULL OR fitID = '' or fitID = '0'".format( query = "DELETE FROM {} WHERE itemID IS NULL OR itemID = '' or itemID = '0' or fitID IS NULL OR fitID = '' or fitID = '0'".format(
table) table)
delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
logger.error("Database corruption found. Cleaning up %d records.", delete.rowcount) pyfalog.error("Database corruption found. Cleaning up {0} records.", delete.rowcount)
for table in ['modules']: for table in ['modules']:
logger.debug("Running database cleanup for orphaned %s items.", table) pyfalog.debug("Running database cleanup for orphaned {0} items.", table)
query = "SELECT COUNT(*) AS num FROM {} WHERE itemID = '0' or fitID IS NULL OR fitID = '' or fitID = '0'".format( query = "SELECT COUNT(*) AS num FROM {} WHERE itemID = '0' or fitID IS NULL OR fitID = '' or fitID = '0'".format(
table) table)
results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
@@ -196,7 +196,7 @@ class DatabaseCleanup(object):
if row and row['num']: if row and row['num']:
query = "DELETE FROM {} WHERE itemID = '0' or fitID IS NULL OR fitID = '' or fitID = '0'".format(table) query = "DELETE FROM {} WHERE itemID = '0' or fitID IS NULL OR fitID = '' or fitID = '0'".format(table)
delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
logger.error("Database corruption found. Cleaning up %d records.", delete.rowcount) pyfalog.error("Database corruption found. Cleaning up {0} records.", delete.rowcount)
@staticmethod @staticmethod
def NullDamageTargetPatternValues(saveddata_engine): def NullDamageTargetPatternValues(saveddata_engine):
@@ -204,7 +204,7 @@ class DatabaseCleanup(object):
# See issue #954 # See issue #954
for profileType in ['damagePatterns', 'targetResists']: for profileType in ['damagePatterns', 'targetResists']:
for damageType in ['em', 'thermal', 'kinetic', 'explosive']: for damageType in ['em', 'thermal', 'kinetic', 'explosive']:
logger.debug("Running database cleanup for null %s values. (%s)", profileType, damageType) pyfalog.debug("Running database cleanup for null {0} values. ({1})", profileType, damageType)
query = "SELECT COUNT(*) AS num FROM {0} WHERE {1}Amount IS NULL OR {1}Amount = ''".format(profileType, query = "SELECT COUNT(*) AS num FROM {0} WHERE {1}Amount IS NULL OR {1}Amount = ''".format(profileType,
damageType) damageType)
results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
@@ -218,13 +218,13 @@ class DatabaseCleanup(object):
query = "UPDATE '{0}' SET '{1}Amount' = '0' WHERE {1}Amount IS NULL OR Amount = ''".format(profileType, query = "UPDATE '{0}' SET '{1}Amount' = '0' WHERE {1}Amount IS NULL OR Amount = ''".format(profileType,
damageType) damageType)
delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
logger.error("Database corruption found. Cleaning up %d records.", delete.rowcount) pyfalog.error("Database corruption found. Cleaning up {0} records.", delete.rowcount)
@staticmethod @staticmethod
def DuplicateSelectedAmmoName(saveddata_engine): def DuplicateSelectedAmmoName(saveddata_engine):
# Orphaned items that are missing the fit ID or item ID. # Orphaned items that are missing the fit ID or item ID.
# See issue #954 # See issue #954
logger.debug("Running database cleanup for duplicated selected ammo profiles.") pyfalog.debug("Running database cleanup for duplicated selected ammo profiles.")
query = "SELECT COUNT(*) AS num FROM damagePatterns WHERE name = 'Selected Ammo'" query = "SELECT COUNT(*) AS num FROM damagePatterns WHERE name = 'Selected Ammo'"
results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) results = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
@@ -236,4 +236,4 @@ class DatabaseCleanup(object):
if row and row['num'] > 1: if row and row['num'] > 1:
query = "DELETE FROM damagePatterns WHERE name = 'Selected Ammo'" query = "DELETE FROM damagePatterns WHERE name = 'Selected Ammo'"
delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query) delete = DatabaseCleanup.ExecuteSQLQuery(saveddata_engine, query)
logger.error("Database corruption found. Cleaning up %d records.", delete.rowcount) pyfalog.error("Database corruption found. Cleaning up {0} records.", delete.rowcount)

View File

@@ -17,9 +17,9 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>. # along with eos. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================== # ===============================================================================
import logging from logbook import Logger
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class HandledList(list): class HandledList(list):
@@ -198,7 +198,7 @@ class HandledImplantBoosterList(HandledList):
# if needed, remove booster that was occupying slot # if needed, remove booster that was occupying slot
oldObj = next((m for m in self if m.slot == thing.slot), None) oldObj = next((m for m in self if m.slot == thing.slot), None)
if oldObj: if oldObj:
logging.info("Slot %d occupied with %s, replacing with %s", thing.slot, oldObj.item.name, thing.item.name) pyfalog.info("Slot {0} occupied with {1}, replacing with {2}", thing.slot, oldObj.item.name, thing.item.name)
oldObj.itemID = 0 # hack to remove from DB. See GH issue #324 oldObj.itemID = 0 # hack to remove from DB. See GH issue #324
self.remove(oldObj) self.remove(oldObj)
@@ -222,7 +222,7 @@ class HandledProjectedModList(HandledList):
oldEffect = next((m for m in self if m.item.group.name == "Effect Beacon"), None) oldEffect = next((m for m in self if m.item.group.name == "Effect Beacon"), None)
if oldEffect: if oldEffect:
logging.info("System effect occupied with %s, replacing with %s", oldEffect.item.name, proj.item.name) pyfalog.info("System effect occupied with {0}, replacing with {1}", oldEffect.item.name, proj.item.name)
self.remove(oldEffect) self.remove(oldEffect)
HandledList.append(self, proj) HandledList.append(self, proj)

View File

@@ -2,9 +2,9 @@
# #
# Used by: # Used by:
# Module: Reactive Armor Hardener # Module: Reactive Armor Hardener
import logging from logbook import Logger
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
runTime = "late" runTime = "late"
type = "active" type = "active"
@@ -23,7 +23,7 @@ def handler(fit, module, context):
damagePattern.kineticAmount * fit.ship.getModifiedItemAttr('armorKineticDamageResonance'), damagePattern.kineticAmount * fit.ship.getModifiedItemAttr('armorKineticDamageResonance'),
damagePattern.explosiveAmount * fit.ship.getModifiedItemAttr('armorExplosiveDamageResonance'), damagePattern.explosiveAmount * fit.ship.getModifiedItemAttr('armorExplosiveDamageResonance'),
) )
# logger.debug("Damage Adjusted for Armor Resists: %f/%f/%f/%f", baseDamageTaken[0], baseDamageTaken[1], baseDamageTaken[2], baseDamageTaken[3]) # pyfalog.debug("Damage Adjusted for Armor Resists: %f/%f/%f/%f", baseDamageTaken[0], baseDamageTaken[1], baseDamageTaken[2], baseDamageTaken[3])
resistanceShiftAmount = module.getModifiedItemAttr( resistanceShiftAmount = module.getModifiedItemAttr(
'resistanceShiftAmount') / 100 # The attribute is in percent and we want a fraction 'resistanceShiftAmount') / 100 # The attribute is in percent and we want a fraction
@@ -39,7 +39,7 @@ def handler(fit, module, context):
cycleList = [] cycleList = []
loopStart = -20 loopStart = -20
for num in range(50): for num in range(50):
# logger.debug("Starting cycle %d.", num) # pyfalog.debug("Starting cycle %d.", num)
# The strange order is to emulate the ingame sorting when different types have taken the same amount of damage. # The strange order is to emulate the ingame sorting when different types have taken the same amount of damage.
# This doesn't take into account stacking penalties. In a few cases fitting a Damage Control causes an inaccurate result. # This doesn't take into account stacking penalties. In a few cases fitting a Damage Control causes an inaccurate result.
damagePattern_tuples = [ damagePattern_tuples = [
@@ -77,7 +77,7 @@ def handler(fit, module, context):
RAHResistance[sortedDamagePattern_tuples[1][0]] = sortedDamagePattern_tuples[1][2] + change1 RAHResistance[sortedDamagePattern_tuples[1][0]] = sortedDamagePattern_tuples[1][2] + change1
RAHResistance[sortedDamagePattern_tuples[2][0]] = sortedDamagePattern_tuples[2][2] + change2 RAHResistance[sortedDamagePattern_tuples[2][0]] = sortedDamagePattern_tuples[2][2] + change2
RAHResistance[sortedDamagePattern_tuples[3][0]] = sortedDamagePattern_tuples[3][2] + change3 RAHResistance[sortedDamagePattern_tuples[3][0]] = sortedDamagePattern_tuples[3][2] + change3
# logger.debug("Resistances shifted to %f/%f/%f/%f", RAHResistance[0], RAHResistance[1], RAHResistance[2], RAHResistance[3]) # pyfalog.debug("Resistances shifted to %f/%f/%f/%f", RAHResistance[0], RAHResistance[1], RAHResistance[2], RAHResistance[3])
# See if the current RAH profile has been encountered before, indicating a loop. # See if the current RAH profile has been encountered before, indicating a loop.
for i, val in enumerate(cycleList): for i, val in enumerate(cycleList):
@@ -87,7 +87,7 @@ def handler(fit, module, context):
abs(RAHResistance[2] - val[2]) <= tolerance and \ abs(RAHResistance[2] - val[2]) <= tolerance and \
abs(RAHResistance[3] - val[3]) <= tolerance: abs(RAHResistance[3] - val[3]) <= tolerance:
loopStart = i loopStart = i
# logger.debug("Loop found: %d-%d", loopStart, num) # pyfalog.debug("Loop found: %d-%d", loopStart, num)
break break
if loopStart >= 0: if loopStart >= 0:
break break
@@ -95,7 +95,7 @@ def handler(fit, module, context):
cycleList.append(list(RAHResistance)) cycleList.append(list(RAHResistance))
if loopStart < 0: if loopStart < 0:
logger.error("Reactive Armor Hardener failed to find equilibrium. Damage profile after armor: %f/%f/%f/%f", pyfalog.error("Reactive Armor Hardener failed to find equilibrium. Damage profile after armor: {0}/{1}/{2}/{3}",
baseDamageTaken[0], baseDamageTaken[1], baseDamageTaken[2], baseDamageTaken[3]) baseDamageTaken[0], baseDamageTaken[1], baseDamageTaken[2], baseDamageTaken[3])
# Average the profiles in the RAH loop, or the last 20 if it didn't find a loop. # Average the profiles in the RAH loop, or the last 20 if it didn't find a loop.
@@ -110,7 +110,7 @@ def handler(fit, module, context):
average[i] = round(average[i] / numCycles, 3) average[i] = round(average[i] / numCycles, 3)
# Set the new resistances # Set the new resistances
# logger.debug("Setting new resist profile: %f/%f/%f/%f", average[0], average[1], average[2],average[3]) # pyfalog.debug("Setting new resist profile: %f/%f/%f/%f", average[0], average[1], average[2],average[3])
for i, attr in enumerate(( for i, attr in enumerate((
'armorEmDamageResonance', 'armorThermalDamageResonance', 'armorKineticDamageResonance', 'armorEmDamageResonance', 'armorThermalDamageResonance', 'armorKineticDamageResonance',
'armorExplosiveDamageResonance')): 'armorExplosiveDamageResonance')):

View File

@@ -2,8 +2,10 @@
# #
# Used by: # Used by:
# Modules from group: Tractor Beam (4 of 4) # Modules from group: Tractor Beam (4 of 4)
from eos.config import settings
type = "active" type = "active"
def handler(fit, module, context): def handler(fit, module, context):
print settings['setting1']
pass pass

View File

@@ -18,7 +18,6 @@
# =============================================================================== # ===============================================================================
import re import re
import traceback
from sqlalchemy.orm import reconstructor from sqlalchemy.orm import reconstructor
@@ -30,6 +29,10 @@ try:
except ImportError: except ImportError:
from utils.compat import OrderedDict from utils.compat import OrderedDict
from logbook import Logger
pyfalog = Logger(__name__)
class Effect(EqBase): class Effect(EqBase):
""" """
@@ -64,6 +67,8 @@ class Effect(EqBase):
if not self.__generated: if not self.__generated:
self.__generateHandler() self.__generateHandler()
pyfalog.debug("Generating effect: {0} ({1}) [runTime: {2}]", self.name, self.effectID, self.runTime)
return self.__handler return self.__handler
@property @property
@@ -138,7 +143,7 @@ class Effect(EqBase):
@property @property
def isImplemented(self): def isImplemented(self):
""" """
Wether this effect is implemented in code or not, Whether this effect is implemented in code or not,
unimplemented effects simply do nothing at all when run unimplemented effects simply do nothing at all when run
""" """
return self.handler != effectDummy return self.handler != effectDummy
@@ -156,36 +161,30 @@ class Effect(EqBase):
""" """
try: try:
self.__effectModule = effectModule = __import__('eos.effects.' + self.handlerName, fromlist=True) self.__effectModule = effectModule = __import__('eos.effects.' + self.handlerName, fromlist=True)
try: self.__handler = getattr(effectModule, "handler", effectDummy)
self.__handler = getattr(effectModule, "handler") self.__runTime = getattr(effectModule, "runTime", "normal")
except AttributeError: self.__activeByDefault = getattr(effectModule, "activeByDefault", True)
print "effect {} exists, but no handler".format(self.handlerName) t = getattr(effectModule, "type", None)
raise
try:
self.__runTime = getattr(effectModule, "runTime") or "normal"
except AttributeError:
self.__runTime = "normal"
try:
self.__activeByDefault = getattr(effectModule, "activeByDefault")
except AttributeError:
self.__activeByDefault = True
try:
t = getattr(effectModule, "type")
except AttributeError:
t = None
t = t if isinstance(t, tuple) or t is None else (t,) t = t if isinstance(t, tuple) or t is None else (t,)
self.__type = t self.__type = t
except (ImportError, AttributeError): except (ImportError) as e:
# Effect probably doesn't exist, so create a dummy effect and flag it with a warning.
self.__handler = effectDummy self.__handler = effectDummy
self.__runTime = "normal" self.__runTime = "normal"
self.__activeByDefault = True self.__activeByDefault = True
self.__type = None self.__type = None
pyfalog.warning("ImportError generating handler: {0}", e)
except (AttributeError) as e:
# Effect probably exists but there is an issue with it. Turn it into a dummy effect so we can continue, but flag it with an error.
self.__handler = effectDummy
self.__runTime = "normal"
self.__activeByDefault = True
self.__type = None
pyfalog.error("AttributeError generating handler: {0}", e)
except Exception as e: except Exception as e:
traceback.print_exc(e) pyfalog.critical("Exception generating handler:")
pyfalog.critical(e)
self.__generated = True self.__generated = True
@@ -285,8 +284,6 @@ class Item(EqBase):
@property @property
def requiredSkills(self): def requiredSkills(self):
if self.__requiredSkills is None: if self.__requiredSkills is None:
# This import should be here to make sure it's fully initialized
from eos import db
requiredSkills = OrderedDict() requiredSkills = OrderedDict()
self.__requiredSkills = requiredSkills self.__requiredSkills = requiredSkills
# Map containing attribute IDs we may need for required skills # Map containing attribute IDs we may need for required skills
@@ -297,7 +294,7 @@ class Item(EqBase):
# { attributeID : attributeValue } # { attributeID : attributeValue }
skillAttrs = {} skillAttrs = {}
# Get relevant attribute values from db (required skill IDs and levels) for our item # Get relevant attribute values from db (required skill IDs and levels) for our item
for attrInfo in db.directAttributeRequest((self.ID,), tuple(combinedAttrIDs)): for attrInfo in eos.db.directAttributeRequest((self.ID,), tuple(combinedAttrIDs)):
attrID = attrInfo[1] attrID = attrInfo[1]
attrVal = attrInfo[2] attrVal = attrInfo[2]
skillAttrs[attrID] = attrVal skillAttrs[attrID] = attrVal
@@ -308,7 +305,7 @@ class Item(EqBase):
skillID = int(skillAttrs[srqIDAtrr]) skillID = int(skillAttrs[srqIDAtrr])
skillLvl = skillAttrs[srqLvlAttr] skillLvl = skillAttrs[srqLvlAttr]
# Fetch item from database and fill map # Fetch item from database and fill map
item = db.getItem(skillID) item = eos.db.getItem(skillID)
requiredSkills[item] = skillLvl requiredSkills[item] = skillLvl
return self.__requiredSkills return self.__requiredSkills
@@ -341,18 +338,20 @@ class Item(EqBase):
# thus keep old mechanism for now # thus keep old mechanism for now
except KeyError: except KeyError:
# Define race map # Define race map
map = {1: "caldari", map = {
2: "minmatar", 1 : "caldari",
4: "amarr", 2 : "minmatar",
5: "sansha", # Caldari + Amarr 4 : "amarr",
6: "blood", # Minmatar + Amarr 5 : "sansha", # Caldari + Amarr
8: "gallente", 6 : "blood", # Minmatar + Amarr
9: "guristas", # Caldari + Gallente 8 : "gallente",
10: "angelserp", # Minmatar + Gallente, final race depends on the order of skills 9 : "guristas", # Caldari + Gallente
12: "sisters", # Amarr + Gallente 10 : "angelserp", # Minmatar + Gallente, final race depends on the order of skills
16: "jove", 12 : "sisters", # Amarr + Gallente
32: "sansha", # Incrusion Sansha 16 : "jove",
128: "ore"} 32 : "sansha", # Incrusion Sansha
128: "ore"
}
# Race is None by default # Race is None by default
race = None race = None
# Check primary and secondary required skills' races # Check primary and secondary required skills' races
@@ -447,7 +446,6 @@ class Category(EqBase):
class AlphaClone(EqBase): class AlphaClone(EqBase):
@reconstructor @reconstructor
def init(self): def init(self):
self.skillCache = {} self.skillCache = {}
@@ -475,7 +473,6 @@ class Icon(EqBase):
class MarketGroup(EqBase): class MarketGroup(EqBase):
def __repr__(self): def __repr__(self):
return u"MarketGroup(ID={}, name={}, parent={}) at {}".format( return u"MarketGroup(ID={}, name={}, parent={}) at {}".format(
self.ID, self.name, getattr(self.parent, "name", None), self.name, hex(id(self)) self.ID, self.name, getattr(self.parent, "name", None), self.name, hex(id(self))

View File

@@ -21,6 +21,9 @@ from math import log, sin, radians, exp
from eos.graph import Graph from eos.graph import Graph
from eos.saveddata.module import State, Hardpoint from eos.saveddata.module import State, Hardpoint
from logbook import Logger
pyfalog = Logger(__name__)
class FitDpsGraph(Graph): class FitDpsGraph(Graph):
@@ -65,8 +68,9 @@ class FitDpsGraph(Graph):
bonus = values[i] bonus = values[i]
val *= 1 + (bonus - 1) * exp(- i ** 2 / 7.1289) val *= 1 + (bonus - 1) * exp(- i ** 2 / 7.1289)
data[attr] = val data[attr] = val
except: except Exception as e:
pass pyfalog.critical("Caught exception in calcDPS.")
pyfalog.critical(e)
for mod in fit.modules: for mod in fit.modules:
dps, _ = mod.damageStats(fit.targetResists) dps, _ = mod.damageStats(fit.targetResists)

View File

@@ -1,31 +0,0 @@
# ===============================================================================
# Copyright (C) 2010 Anton Vorobyov
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
import decimal
def floorFloat(value):
"""Round float down to integer"""
# We have to convert float to str to keep compatibility with
# decimal module in python 2.6
value = str(value)
# Do the conversions for proper rounding down, avoiding float
# representation errors
result = int(decimal.Decimal(value).to_integral_value(rounding=decimal.ROUND_DOWN))
return result

View File

@@ -17,7 +17,7 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>. # along with eos. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================== # ===============================================================================
import logging from logbook import Logger
from sqlalchemy.orm import reconstructor, validates from sqlalchemy.orm import reconstructor, validates
@@ -25,7 +25,7 @@ import eos.db
from eos.effectHandlerHelpers import HandledItem from eos.effectHandlerHelpers import HandledItem
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class Booster(HandledItem, ItemAttrShortcut): class Booster(HandledItem, ItemAttrShortcut):
@@ -47,11 +47,11 @@ class Booster(HandledItem, ItemAttrShortcut):
if self.itemID: if self.itemID:
self.__item = eos.db.getItem(self.itemID) self.__item = eos.db.getItem(self.itemID)
if self.__item is None: if self.__item is None:
logger.error("Item (id: %d) does not exist", self.itemID) pyfalog.error("Item (id: {0}) does not exist", self.itemID)
return return
if self.isInvalid: if self.isInvalid:
logger.error("Item (id: %d) is not a Booser", self.itemID) pyfalog.error("Item (id: {0}) is not a Booster", self.itemID)
return return
self.build() self.build()

View File

@@ -18,7 +18,7 @@
# =============================================================================== # ===============================================================================
import sys import sys
import logging from logbook import Logger
from sqlalchemy.orm import validates, reconstructor from sqlalchemy.orm import validates, reconstructor
@@ -26,7 +26,7 @@ import eos.db
from eos.effectHandlerHelpers import HandledItem from eos.effectHandlerHelpers import HandledItem
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class Cargo(HandledItem, ItemAttrShortcut): class Cargo(HandledItem, ItemAttrShortcut):
@@ -47,7 +47,7 @@ class Cargo(HandledItem, ItemAttrShortcut):
if self.itemID: if self.itemID:
self.__item = eos.db.getItem(self.itemID) self.__item = eos.db.getItem(self.itemID)
if self.__item is None: if self.__item is None:
logger.error("Item (id: %d) does not exist", self.itemID) pyfalog.error("Item (id: {0}) does not exist", self.itemID)
return return
self.__itemModifiedAttributes = ModifiedAttributeDict() self.__itemModifiedAttributes = ModifiedAttributeDict()

View File

@@ -18,7 +18,7 @@
# =============================================================================== # ===============================================================================
import logging from logbook import Logger
from itertools import chain from itertools import chain
from sqlalchemy.orm import validates, reconstructor from sqlalchemy.orm import validates, reconstructor
@@ -27,7 +27,7 @@ import eos
import eos.db import eos.db
from eos.effectHandlerHelpers import HandledItem, HandledImplantBoosterList from eos.effectHandlerHelpers import HandledItem, HandledImplantBoosterList
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class Character(object): class Character(object):

View File

@@ -17,16 +17,17 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>. # along with eos. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================== # ===============================================================================
import logging from logbook import Logger
from eos.saveddata.ship import Ship from eos.saveddata.ship import Ship
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class Citadel(Ship): class Citadel(Ship):
def validate(self, item): def validate(self, item):
if item.category.name != "Structure": if item.category.name != "Structure":
pyfalog.error("Passed item '{0}' (category: {1}) is not under Structure category", item.name, item.category.name)
raise ValueError( raise ValueError(
'Passed item "%s" (category: (%s)) is not under Structure category' % (item.name, item.category.name)) 'Passed item "%s" (category: (%s)) is not under Structure category' % (item.name, item.category.name))

View File

@@ -17,7 +17,7 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>. # along with eos. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================== # ===============================================================================
import logging from logbook import Logger
from sqlalchemy.orm import validates, reconstructor from sqlalchemy.orm import validates, reconstructor
@@ -25,7 +25,7 @@ import eos.db
from eos.effectHandlerHelpers import HandledItem, HandledCharge from eos.effectHandlerHelpers import HandledItem, HandledCharge
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@@ -53,11 +53,11 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if self.itemID: if self.itemID:
self.__item = eos.db.getItem(self.itemID) self.__item = eos.db.getItem(self.itemID)
if self.__item is None: if self.__item is None:
logger.error("Item (id: %d) does not exist", self.itemID) pyfalog.error("Item (id: {0}) does not exist", self.itemID)
return return
if self.isInvalid: if self.isInvalid:
logger.error("Item (id: %d) is not a Drone", self.itemID) pyfalog.error("Item (id: {0}) is not a Drone", self.itemID)
return return
self.build() self.build()

View File

@@ -17,7 +17,7 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>. # along with eos. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================== # ===============================================================================
import logging from logbook import Logger
from sqlalchemy.orm import validates, reconstructor from sqlalchemy.orm import validates, reconstructor
@@ -27,7 +27,7 @@ from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, C
from eos.saveddata.fighterAbility import FighterAbility from eos.saveddata.fighterAbility import FighterAbility
from eos.saveddata.module import Slot from eos.saveddata.module import Slot
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@@ -61,11 +61,11 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if self.itemID: if self.itemID:
self.__item = eos.db.getItem(self.itemID) self.__item = eos.db.getItem(self.itemID)
if self.__item is None: if self.__item is None:
logger.error("Item (id: %d) does not exist", self.itemID) pyfalog.error("Item (id: {0}) does not exist", self.itemID)
return return
if self.isInvalid: if self.isInvalid:
logger.error("Item (id: %d) is not a Fighter", self.itemID) pyfalog.error("Item (id: {0}) is not a Fighter", self.itemID)
return return
self.build() self.build()

View File

@@ -17,11 +17,11 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>. # along with eos. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================== # ===============================================================================
import logging from logbook import Logger
from sqlalchemy.orm import reconstructor from sqlalchemy.orm import reconstructor
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class FighterAbility(object): class FighterAbility(object):
@@ -59,7 +59,7 @@ class FighterAbility(object):
if self.effectID: if self.effectID:
self.__effect = next((x for x in self.fighter.item.effects.itervalues() if x.ID == self.effectID), None) self.__effect = next((x for x in self.fighter.item.effects.itervalues() if x.ID == self.effectID), None)
if self.__effect is None: if self.__effect is None:
logger.error("Effect (id: %d) does not exist", self.effectID) pyfalog.error("Effect (id: {0}) does not exist", self.effectID)
return return
self.build() self.build()

View File

@@ -33,9 +33,9 @@ from eos.saveddata.character import Character
from eos.saveddata.citadel import Citadel from eos.saveddata.citadel import Citadel
from eos.saveddata.module import Module, State, Slot, Hardpoint from eos.saveddata.module import Module, State, Slot, Hardpoint
from utils.timer import Timer from utils.timer import Timer
import logging from logbook import Logger
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class ImplantLocation(Enum): class ImplantLocation(Enum):
@@ -84,7 +84,7 @@ class Fit(object):
if self.shipID: if self.shipID:
item = eos.db.getItem(self.shipID) item = eos.db.getItem(self.shipID)
if item is None: if item is None:
logger.error("Item (id: %d) does not exist", self.shipID) pyfalog.error("Item (id: {0}) does not exist", self.shipID)
return return
try: try:
@@ -97,7 +97,7 @@ class Fit(object):
# change all instances in source). Remove this at some point # change all instances in source). Remove this at some point
self.extraAttributes = self.__ship.itemModifiedAttributes self.extraAttributes = self.__ship.itemModifiedAttributes
except ValueError: except ValueError:
logger.error("Item (id: %d) is not a Ship", self.shipID) pyfalog.error("Item (id: {0}) is not a Ship", self.shipID)
return return
if self.modeID and self.__ship: if self.modeID and self.__ship:
@@ -127,6 +127,12 @@ class Fit(object):
self.__capUsed = None self.__capUsed = None
self.__capRecharge = None self.__capRecharge = None
self.__calculatedTargets = [] self.__calculatedTargets = []
self.__remoteReps = {
"Armor" : None,
"Shield" : None,
"Hull" : None,
"Capacitor": None,
}
self.factorReload = False self.factorReload = False
self.boostsFits = set() self.boostsFits = set()
self.gangBoosts = None self.gangBoosts = None
@@ -364,9 +370,11 @@ class Fit(object):
@validates("ID", "ownerID", "shipID") @validates("ID", "ownerID", "shipID")
def validator(self, key, val): def validator(self, key, val):
map = {"ID": lambda _val: isinstance(_val, int), map = {
"ID" : lambda _val: isinstance(_val, int),
"ownerID": lambda _val: isinstance(_val, int) or _val is None, "ownerID": lambda _val: isinstance(_val, int) or _val is None,
"shipID": lambda _val: isinstance(_val, int) or _val is None} "shipID" : lambda _val: isinstance(_val, int) or _val is None
}
if not map[key](val): if not map[key](val):
raise ValueError(str(val) + " is not a valid value for " + key) raise ValueError(str(val) + " is not a valid value for " + key)
@@ -392,6 +400,9 @@ class Fit(object):
self.ecmProjectedStr = 1 self.ecmProjectedStr = 1
self.commandBonuses = {} self.commandBonuses = {}
for remoterep_type in self.__remoteReps:
self.__remoteReps[remoterep_type] = None
del self.__calculatedTargets[:] del self.__calculatedTargets[:]
del self.__extraDrains[:] del self.__extraDrains[:]
@@ -448,7 +459,7 @@ class Fit(object):
self.commandBonuses[warfareBuffID] = (runTime, value, module, effect) self.commandBonuses[warfareBuffID] = (runTime, value, module, effect)
def __runCommandBoosts(self, runTime="normal"): def __runCommandBoosts(self, runTime="normal"):
logger.debug("Applying gang boosts for %r", self) pyfalog.debug("Applying gang boosts for {0}", self)
for warfareBuffID in self.commandBonuses.keys(): for warfareBuffID in self.commandBonuses.keys():
# Unpack all data required to run effect properly # Unpack all data required to run effect properly
effect_runTime, value, thing, effect = self.commandBonuses[warfareBuffID] effect_runTime, value, thing, effect = self.commandBonuses[warfareBuffID]
@@ -538,27 +549,29 @@ class Fit(object):
stackingPenalties=True) stackingPenalties=True)
if warfareBuffID == 22: # Skirmish Burst: Rapid Deployment: AB/MWD Speed Increase if warfareBuffID == 22: # Skirmish Burst: Rapid Deployment: AB/MWD Speed Increase
self.modules.filteredItemBoost( self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Afterburner") or
lambda mod: mod.item.requiresSkill("Afterburner") or mod.item.requiresSkill( mod.item.requiresSkill("High Speed Maneuvering"),
"High Speed Maneuvering"), "speedFactor", value, stackingPenalties=True) "speedFactor", value, stackingPenalties=True)
if warfareBuffID == 23: # Mining Burst: Mining Laser Field Enhancement: Mining/Survey Range if warfareBuffID == 23: # Mining Burst: Mining Laser Field Enhancement: Mining/Survey Range
self.modules.filteredItemBoost( self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining") or
lambda mod: mod.item.requiresSkill("Mining") or mod.item.requiresSkill( mod.item.requiresSkill("Ice Harvesting") or
"Ice Harvesting") or mod.item.requiresSkill("Gas Cloud Harvesting"), "maxRange", mod.item.requiresSkill("Gas Cloud Harvesting"),
value, stackingPenalties=True) "maxRange", value, stackingPenalties=True)
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("CPU Management"), self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("CPU Management"),
"surveyScanRange", value, stackingPenalties=True) "surveyScanRange", value, stackingPenalties=True)
if warfareBuffID == 24: # Mining Burst: Mining Laser Optimization: Mining Capacitor/Duration if warfareBuffID == 24: # Mining Burst: Mining Laser Optimization: Mining Capacitor/Duration
self.modules.filteredItemBoost( self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining") or
lambda mod: mod.item.requiresSkill("Mining") or mod.item.requiresSkill( mod.item.requiresSkill("Ice Harvesting") or
"Ice Harvesting") or mod.item.requiresSkill("Gas Cloud Harvesting"), mod.item.requiresSkill("Gas Cloud Harvesting"),
"capacitorNeed", value, stackingPenalties=True) "capacitorNeed", value, stackingPenalties=True)
self.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill("Mining") or mod.item.requiresSkill( self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining") or
"Ice Harvesting") or mod.item.requiresSkill("Gas Cloud Harvesting"), "duration", mod.item.requiresSkill("Ice Harvesting") or
value, stackingPenalties=True) mod.item.requiresSkill("Gas Cloud Harvesting"),
"duration", value, stackingPenalties=True)
if warfareBuffID == 25: # Mining Burst: Mining Equipment Preservation: Crystal Volatility if warfareBuffID == 25: # Mining Burst: Mining Equipment Preservation: Crystal Volatility
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining"), self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining"),
@@ -630,21 +643,21 @@ class Fit(object):
del self.commandBonuses[warfareBuffID] del self.commandBonuses[warfareBuffID]
def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None): def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None):
timer = Timer(u'Fit: {}, {}'.format(self.ID, self.name), logger) timer = Timer(u'Fit: {}, {}'.format(self.ID, self.name), pyfalog)
logger.debug("Starting fit calculation on: %r, withBoosters: %s", self, withBoosters) pyfalog.debug("Starting fit calculation on: {0}, withBoosters: {1}", self, withBoosters)
shadow = False shadow = False
if targetFit and not withBoosters: if targetFit and not withBoosters:
logger.debug("Applying projections to target: %r", targetFit) pyfalog.debug("Applying projections to target: {0}", targetFit)
projectionInfo = self.getProjectionInfo(targetFit.ID) projectionInfo = self.getProjectionInfo(targetFit.ID)
logger.debug("ProjectionInfo: %s", projectionInfo) pyfalog.debug("ProjectionInfo: {0}", projectionInfo)
if self == targetFit: if self == targetFit:
copied = self # original fit copied = self # original fit
shadow = True shadow = True
# Don't inspect this, we genuinely want to reassign self # Don't inspect this, we genuinely want to reassign self
# noinspection PyMethodFirstArgAssignment # noinspection PyMethodFirstArgAssignment
self = deepcopy(self) self = deepcopy(self)
logger.debug("Handling self projection - making shadow copy of fit. %r => %r", copied, self) pyfalog.debug("Handling self projection - making shadow copy of fit. {0} => {1}", copied, self)
# we delete the fit because when we copy a fit, flush() is # we delete the fit because when we copy a fit, flush() is
# called to properly handle projection updates. However, we do # called to properly handle projection updates. However, we do
# not want to save this fit to the database, so simply remove it # not want to save this fit to the database, so simply remove it
@@ -679,7 +692,7 @@ class Fit(object):
# projection have modifying stuff applied, such as gang boosts and other # projection have modifying stuff applied, such as gang boosts and other
# local modules that may help # local modules that may help
if self.__calculated and not projected and not withBoosters: if self.__calculated and not projected and not withBoosters:
logger.debug("Fit has already been calculated and is not projected, returning: %r", self) pyfalog.debug("Fit has already been calculated and is not projected, returning: {0}", self)
return return
for runTime in ("early", "normal", "late"): for runTime in ("early", "normal", "late"):
@@ -730,6 +743,10 @@ class Fit(object):
# targetFit.register(item, origin=self) # targetFit.register(item, origin=self)
item.calculateModifiedAttributes(targetFit, runTime, False, True) item.calculateModifiedAttributes(targetFit, runTime, False, True)
if len(self.commandBonuses) > 0:
pyfalog.info("Command bonuses applied.")
pyfalog.debug(self.commandBonuses)
if not withBoosters and self.commandBonuses: if not withBoosters and self.commandBonuses:
self.__runCommandBoosts(runTime) self.__runCommandBoosts(runTime)
@@ -747,7 +764,7 @@ class Fit(object):
timer.checkpoint('Done with fit calculation') timer.checkpoint('Done with fit calculation')
if shadow: if shadow:
logger.debug("Delete shadow fit object") pyfalog.debug("Delete shadow fit object")
del self del self
def fill(self): def fill(self):
@@ -831,15 +848,17 @@ class Fit(object):
return amount return amount
slots = {Slot.LOW: "lowSlots", slots = {
Slot.MED: "medSlots", Slot.LOW : "lowSlots",
Slot.HIGH: "hiSlots", Slot.MED : "medSlots",
Slot.RIG: "rigSlots", Slot.HIGH : "hiSlots",
Slot.RIG : "rigSlots",
Slot.SUBSYSTEM: "maxSubSystems", Slot.SUBSYSTEM: "maxSubSystems",
Slot.SERVICE: "serviceSlots", Slot.SERVICE : "serviceSlots",
Slot.F_LIGHT: "fighterLightSlots", Slot.F_LIGHT : "fighterLightSlots",
Slot.F_SUPPORT: "fighterSupportSlots", Slot.F_SUPPORT: "fighterSupportSlots",
Slot.F_HEAVY: "fighterHeavySlots"} Slot.F_HEAVY : "fighterHeavySlots"
}
def getSlotsFree(self, type, countDummies=False): def getSlotsFree(self, type, countDummies=False):
if type in (Slot.MODE, Slot.SYSTEM): if type in (Slot.MODE, Slot.SYSTEM):
@@ -914,20 +933,19 @@ class Fit(object):
return amount return amount
# Expresses how difficult a target is to probe down with scan probes
# If this is <1.08, the ship is unproabeable
@property @property
def probeSize(self): def probeSize(self):
"""
Expresses how difficult a target is to probe down with scan probes
"""
sigRad = self.ship.getModifiedItemAttr("signatureRadius") sigRad = self.ship.getModifiedItemAttr("signatureRadius")
sensorStr = float(self.scanStrength) sensorStr = float(self.scanStrength)
probeSize = sigRad / sensorStr if sensorStr != 0 else None probeSize = sigRad / sensorStr if sensorStr != 0 else None
# http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1532170&page=2#42 # http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1532170&page=2#42
if probeSize is not None: if probeSize is not None:
# http://forum.eve-ru.com/index.php?showtopic=74195&view=findpost&p=1333691 # Probe size is capped at 1.08
# http://forum.eve-ru.com/index.php?showtopic=74195&view=findpost&p=1333763 probeSize = max(probeSize, 1.08)
# Tests by tester128 and several conclusions by me, prove that cap is in range
# from 1.1 to 1.12, we're picking average value
probeSize = max(probeSize, 1.11)
return probeSize return probeSize
@property @property
@@ -989,29 +1007,35 @@ class Fit(object):
def calculateSustainableTank(self, effective=True): def calculateSustainableTank(self, effective=True):
if self.__sustainableTank is None: if self.__sustainableTank is None:
if self.capStable: if self.capStable:
sustainable = {"armorRepair": self.extraAttributes["armorRepair"], sustainable = {
"armorRepair" : self.extraAttributes["armorRepair"],
"shieldRepair": self.extraAttributes["shieldRepair"], "shieldRepair": self.extraAttributes["shieldRepair"],
"hullRepair": self.extraAttributes["hullRepair"]} "hullRepair" : self.extraAttributes["hullRepair"]
}
else: else:
sustainable = {} sustainable = {}
repairers = [] repairers = []
# Map a repairer type to the attribute it uses # Map a repairer type to the attribute it uses
groupAttrMap = {"Armor Repair Unit": "armorDamageAmount", groupAttrMap = {
"Armor Repair Unit" : "armorDamageAmount",
"Ancillary Armor Repairer": "armorDamageAmount", "Ancillary Armor Repairer": "armorDamageAmount",
"Hull Repair Unit": "structureDamageAmount", "Hull Repair Unit" : "structureDamageAmount",
"Shield Booster": "shieldBonus", "Shield Booster" : "shieldBonus",
"Ancillary Shield Booster": "shieldBonus", "Ancillary Shield Booster": "shieldBonus",
"Remote Armor Repairer": "armorDamageAmount", "Remote Armor Repairer" : "armorDamageAmount",
"Remote Shield Booster": "shieldBonus"} "Remote Shield Booster" : "shieldBonus"
}
# Map repairer type to attribute # Map repairer type to attribute
groupStoreMap = {"Armor Repair Unit": "armorRepair", groupStoreMap = {
"Hull Repair Unit": "hullRepair", "Armor Repair Unit" : "armorRepair",
"Shield Booster": "shieldRepair", "Hull Repair Unit" : "hullRepair",
"Shield Booster" : "shieldRepair",
"Ancillary Shield Booster": "shieldRepair", "Ancillary Shield Booster": "shieldRepair",
"Remote Armor Repairer": "armorRepair", "Remote Armor Repairer" : "armorRepair",
"Remote Shield Booster": "shieldRepair", "Remote Shield Booster" : "shieldRepair",
"Ancillary Armor Repairer": "armorRepair", } "Ancillary Armor Repairer": "armorRepair",
}
capUsed = self.capUsed capUsed = self.capUsed
for attr in ("shieldRepair", "armorRepair", "hullRepair"): for attr in ("shieldRepair", "armorRepair", "hullRepair"):
@@ -1147,6 +1171,67 @@ class Fit(object):
self.__capStable = True self.__capStable = True
self.__capState = 100 self.__capState = 100
@property
def remoteReps(self):
force_recalc = False
for remote_type in self.__remoteReps:
if self.__remoteReps[remote_type] is None:
force_recalc = True
break
if force_recalc is False:
return self.__remoteReps
# We are rerunning the recalcs. Explicitly set to 0 to make sure we don't duplicate anything and correctly set all values to 0.
for remote_type in self.__remoteReps:
self.__remoteReps[remote_type] = 0
for module in self.modules:
# Skip empty and non-Active modules
if module.isEmpty or module.state < State.ACTIVE:
continue
# Covert cycleTime to seconds
duration = module.cycleTime / 1000
# Skip modules with no duration.
if not duration:
continue
fueledMultiplier = module.getModifiedItemAttr("chargedArmorDamageMultiplier", 1)
remote_module_groups = {
"Remote Armor Repairer" : "Armor",
"Ancillary Remote Armor Repairer": "Armor",
"Remote Hull Repairer" : "Hull",
"Remote Shield Booster" : "Shield",
"Ancillary Remote Shield Booster": "Shield",
"Remote Capacitor Transmitter" : "Capacitor",
}
module_group = module.item.group.name
if module_group in remote_module_groups:
remote_type = remote_module_groups[module_group]
else:
# Module isn't in our list of remote rep modules, bail
continue
if remote_type == "Hull":
hp = module.getModifiedItemAttr("structureDamageAmount", 0)
elif remote_type == "Armor":
hp = module.getModifiedItemAttr("armorDamageAmount", 0)
elif remote_type == "Shield":
hp = module.getModifiedItemAttr("shieldBonus", 0)
elif remote_type == "Capacitor":
hp = module.getModifiedItemAttr("powerTransferAmount", 0)
else:
hp = 0
self.__remoteReps[remote_type] += (hp * fueledMultiplier) / duration
return self.__remoteReps
@property @property
def hp(self): def hp(self):
hp = {} hp = {}

View File

@@ -17,7 +17,7 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>. # along with eos. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================== # ===============================================================================
import logging from logbook import Logger
from sqlalchemy.orm import validates, reconstructor from sqlalchemy.orm import validates, reconstructor
@@ -25,7 +25,7 @@ import eos.db
from eos.effectHandlerHelpers import HandledItem from eos.effectHandlerHelpers import HandledItem
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class Implant(HandledItem, ItemAttrShortcut): class Implant(HandledItem, ItemAttrShortcut):
@@ -46,11 +46,11 @@ class Implant(HandledItem, ItemAttrShortcut):
if self.itemID: if self.itemID:
self.__item = eos.db.getItem(self.itemID) self.__item = eos.db.getItem(self.itemID)
if self.__item is None: if self.__item is None:
logger.error("Item (id: %d) does not exist", self.itemID) pyfalog.error("Item (id: {0}) does not exist", self.itemID)
return return
if self.isInvalid: if self.isInvalid:
logger.error("Item (id: %d) is not an Implant", self.itemID) pyfalog.error("Item (id: {0}) is not an Implant", self.itemID)
return return
self.build() self.build()

View File

@@ -17,18 +17,18 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>. # along with eos. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================== # ===============================================================================
import logging from logbook import Logger
from sqlalchemy.orm import validates, reconstructor from sqlalchemy.orm import validates, reconstructor
from math import floor
import eos.db import eos.db
from eos.effectHandlerHelpers import HandledItem, HandledCharge from eos.effectHandlerHelpers import HandledItem, HandledCharge
from eos.enum import Enum from eos.enum import Enum
from eos.mathUtils import floorFloat
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
from eos.saveddata.citadel import Citadel from eos.saveddata.citadel import Citadel
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class State(Enum): class State(Enum):
@@ -94,11 +94,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if self.itemID: if self.itemID:
self.__item = eos.db.getItem(self.itemID) self.__item = eos.db.getItem(self.itemID)
if self.__item is None: if self.__item is None:
logger.error("Item (id: %d) does not exist", self.itemID) pyfalog.error("Item (id: {0}) does not exist", self.itemID)
return return
if self.isInvalid: if self.isInvalid:
logger.error("Item (id: %d) is not a Module", self.itemID) pyfalog.error("Item (id: {0}) is not a Module", self.itemID)
return return
if self.chargeID: if self.chargeID:
@@ -172,8 +172,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if chargeVolume is None or containerCapacity is None: if chargeVolume is None or containerCapacity is None:
charges = 0 charges = 0
else: else:
charges = floorFloat(float(containerCapacity) / chargeVolume) charges = floor(containerCapacity / chargeVolume)
return charges return int(charges)
@property @property
def numShots(self): def numShots(self):
@@ -216,9 +216,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def __calculateAmmoShots(self): def __calculateAmmoShots(self):
if self.charge is not None: if self.charge is not None:
# Set number of cycles before reload is needed # Set number of cycles before reload is needed
# numcycles = math.floor(module_capacity / (module_volume * module_chargerate))
chargeRate = self.getModifiedItemAttr("chargeRate") chargeRate = self.getModifiedItemAttr("chargeRate")
numCharges = self.numCharges numCharges = self.numCharges
numShots = floorFloat(float(numCharges) / chargeRate) numShots = floor(numCharges / chargeRate)
else: else:
numShots = None numShots = None
return numShots return numShots
@@ -231,7 +232,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
chance = self.getModifiedChargeAttr("crystalVolatilityChance") chance = self.getModifiedChargeAttr("crystalVolatilityChance")
damage = self.getModifiedChargeAttr("crystalVolatilityDamage") damage = self.getModifiedChargeAttr("crystalVolatilityDamage")
crystals = self.numCharges crystals = self.numCharges
numShots = floorFloat(float(crystals * hp) / (damage * chance)) numShots = floor((crystals * hp) / (damage * chance))
else: else:
# Set 0 (infinite) for permanent crystals like t1 laser crystals # Set 0 (infinite) for permanent crystals like t1 laser crystals
numShots = 0 numShots = 0
@@ -665,32 +666,68 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property @property
def cycleTime(self): def cycleTime(self):
reactivation = (self.getModifiedItemAttr("moduleReactivationDelay") or 0)
# Reactivation time starts counting after end of module cycle
speed = self.rawCycleTime + reactivation
if self.charge:
reload = self.reloadTime
else:
reload = 0.0
# Determine if we'll take into account reload time or not # Determine if we'll take into account reload time or not
factorReload = self.owner.factorReload if self.forceReload is None else self.forceReload factorReload = self.owner.factorReload if self.forceReload is None else self.forceReload
# If reactivation is longer than 10 seconds then module can be reloaded
# during reactivation time, thus we may ignore reload
if factorReload and reactivation < reload:
numShots = self.numShots numShots = self.numShots
# Time it takes to reload module after end of reactivation time, speed = self.rawCycleTime
# given that we started when module cycle has just over
additionalReloadTime = (reload - reactivation) if factorReload and self.charge:
# Speed here already takes into consideration reactivation time raw_reload_time = self.reloadTime
speed = (speed * numShots + additionalReloadTime) / numShots if numShots > 0 else speed else:
raw_reload_time = 0.0
# Module can only fire one shot at a time, think bomb launchers or defender launchers
if self.disallowRepeatingAction:
if numShots > 1:
"""
The actual mechanics behind this is complex. Behavior will be (for 3 ammo):
fire, reactivation delay, fire, reactivation delay, fire, max(reactivation delay, reload)
so your effective reload time depends on where you are at in the cycle.
We can't do that, so instead we'll average it out.
Currently would apply to bomb launchers and defender missiles
"""
effective_reload_time = ((self.reactivationDelay * (numShots - 1)) + max(raw_reload_time, self.reactivationDelay, 0)) / numShots
else:
"""
Applies to MJD/MJFG
"""
effective_reload_time = max(raw_reload_time, self.reactivationDelay, 0)
else:
"""
Currently no other modules would have a reactivation delay, so for sanities sake don't try and account for it.
Okay, technically cloaks do, but they also have 0 cycle time and cap usage so why do you care?
"""
effective_reload_time = raw_reload_time
if numShots > 0 and self.charge:
speed = (speed * numShots + effective_reload_time) / numShots
return speed return speed
@property @property
def rawCycleTime(self): def rawCycleTime(self):
speed = self.getModifiedItemAttr("speed") or self.getModifiedItemAttr("duration") speed = max(
self.getModifiedItemAttr("speed"), # Most weapons
self.getModifiedItemAttr("duration"), # Most average modules
self.getModifiedItemAttr("durationSensorDampeningBurstProjector"),
self.getModifiedItemAttr("durationTargetIlluminationBurstProjector"),
self.getModifiedItemAttr("durationECMJammerBurstProjector"),
self.getModifiedItemAttr("durationWeaponDisruptionBurstProjector"),
0, # Return 0 if none of the above are valid
)
return speed return speed
@property
def disallowRepeatingAction(self):
return self.getModifiedItemAttr("disallowRepeatingAction", 0)
@property
def reactivationDelay(self):
return self.getModifiedItemAttr("moduleReactivationDelay", 0)
@property @property
def capUse(self): def capUse(self):
capNeed = self.getModifiedItemAttr("capacitorNeed") capNeed = self.getModifiedItemAttr("capacitorNeed")

View File

@@ -17,14 +17,14 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>. # along with eos. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================== # ===============================================================================
import logging from logbook import Logger
from sqlalchemy.orm import reconstructor from sqlalchemy.orm import reconstructor
import eos.db import eos.db
from eos.eqBase import EqBase from eos.eqBase import EqBase
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class Override(EqBase): class Override(EqBase):
@@ -43,13 +43,13 @@ class Override(EqBase):
if self.attrID: if self.attrID:
self.__attr = eos.db.getAttributeInfo(self.attrID) self.__attr = eos.db.getAttributeInfo(self.attrID)
if self.__attr is None: if self.__attr is None:
logger.error("Attribute (id: %d) does not exist", self.attrID) pyfalog.error("Attribute (id: {0}) does not exist", self.attrID)
return return
if self.itemID: if self.itemID:
self.__item = eos.db.getItem(self.itemID) self.__item = eos.db.getItem(self.itemID)
if self.__item is None: if self.__item is None:
logger.error("Item (id: %d) does not exist", self.itemID) pyfalog.error("Item (id: {0}) does not exist", self.itemID)
return return
@property @property

View File

@@ -17,14 +17,14 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>. # along with eos. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================== # ===============================================================================
import logging from logbook import Logger
import eos.db import eos.db
from eos.effectHandlerHelpers import HandledItem from eos.effectHandlerHelpers import HandledItem
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, cappingAttrKeyCache from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, cappingAttrKeyCache
from eos.saveddata.mode import Mode from eos.saveddata.mode import Mode
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class Ship(ItemAttrShortcut, HandledItem): class Ship(ItemAttrShortcut, HandledItem):
@@ -63,6 +63,7 @@ class Ship(ItemAttrShortcut, HandledItem):
def validate(self, item): def validate(self, item):
if item.category.name != "Ship": if item.category.name != "Ship":
pyfalog.error("Passed item '{0}' (category: {1}) is not under Ship category", item.name, item.category.name)
raise ValueError( raise ValueError(
'Passed item "%s" (category: (%s)) is not under Ship category' % (item.name, item.category.name)) 'Passed item "%s" (category: (%s)) is not under Ship category' % (item.name, item.category.name))

View File

@@ -18,6 +18,9 @@
# =============================================================================== # ===============================================================================
import re import re
from logbook import Logger
pyfalog = Logger(__name__)
class TargetResists(object): class TargetResists(object):
@@ -43,7 +46,7 @@ class TargetResists(object):
type, data = line.rsplit('=', 1) type, data = line.rsplit('=', 1)
type, data = type.strip(), data.split(',') type, data = type.strip(), data.split(',')
except: except:
# Data isn't in correct format, continue to next line pyfalog.warning("Data isn't in correct format, continue to next line.")
continue continue
if type != "TargetResists": if type != "TargetResists":
@@ -59,6 +62,7 @@ class TargetResists(object):
assert 0 <= val <= 100 assert 0 <= val <= 100
fields["%sAmount" % cls.DAMAGE_TYPES[index]] = val / 100 fields["%sAmount" % cls.DAMAGE_TYPES[index]] = val / 100
except: except:
pyfalog.warning("Caught unhandled exception in import patterns.")
continue continue
if len(fields) == 4: # Avoid possible blank lines if len(fields) == 4: # Avoid possible blank lines

BIN
eve.db

Binary file not shown.

View File

@@ -20,13 +20,15 @@
import cStringIO import cStringIO
import os.path import os.path
import zipfile import zipfile
from config import parsePath
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
import wx import wx
import config import config
from logbook import Logger
logging = Logger(__name__)
try: try:
from collections import OrderedDict from collections import OrderedDict
except ImportError: except ImportError:
@@ -35,8 +37,10 @@ except ImportError:
class BitmapLoader(object): class BitmapLoader(object):
try: try:
archive = zipfile.ZipFile(config.getPyfaPath('imgs.zip'), 'r') archive = zipfile.ZipFile(os.path.join(config.pyfaPath, 'imgs.zip'), 'r')
logging.info("Using zipped image files.")
except IOError: except IOError:
logging.info("Using local image files.")
archive = None archive = None
cachedBitmaps = OrderedDict() cachedBitmaps = OrderedDict()
@@ -78,7 +82,7 @@ class BitmapLoader(object):
filename = "{0}.png".format(name) filename = "{0}.png".format(name)
if cls.archive: if cls.archive:
path = parsePath(location, filename) path = os.path.join(location, filename)
if os.sep != "/" and os.sep in path: if os.sep != "/" and os.sep in path:
path = path.replace(os.sep, "/") path = path.replace(os.sep, "/")
@@ -89,7 +93,7 @@ class BitmapLoader(object):
except KeyError: except KeyError:
print("Missing icon file from zip: {0}".format(path)) print("Missing icon file from zip: {0}".format(path))
else: else:
path = config.getPyfaPath('imgs' + os.sep + location + os.sep + filename) path = os.path.join(config.pyfaPath, 'imgs' + os.sep + location + os.sep + filename)
if os.path.exists(path): if os.path.exists(path):
return wx.Image(path) return wx.Image(path)

View File

@@ -1,25 +0,0 @@
__all__ = [
"openFit",
# "moduleGlobalAmmoPicker",
"moduleAmmoPicker",
"itemStats",
"damagePattern",
"marketJump",
"droneSplit",
"itemRemove",
"droneRemoveStack",
"ammoPattern",
"project",
"factorReload",
"whProjector",
"cargo",
"shipJump",
"changeAffectingSkills",
"tacticalMode",
"targetResists",
"priceClear",
"amount",
"metaSwap",
"implantSets",
"fighterAbilities",
]

View File

@@ -4,13 +4,18 @@ import gui.mainFrame
import wx import wx
import gui.globalEvents as GE import gui.globalEvents as GE
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
class AmmoPattern(ContextMenu): class AmmoPattern(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('ammoPattern'):
return False
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None: if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
return False return False

View File

@@ -7,13 +7,18 @@ import wx
from service.fit import Fit from service.fit import Fit
from eos.saveddata.cargo import Cargo as es_Cargo from eos.saveddata.cargo import Cargo as es_Cargo
from eos.saveddata.fighter import Fighter as es_Fighter from eos.saveddata.fighter import Fighter as es_Fighter
from service.settings import ContextMenuSettings
class ChangeAmount(ContextMenu): class ChangeAmount(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('amount'):
return False
return srcContext in ("cargoItem", "projectedFit", "fighterItem", "projectedFighter") return srcContext in ("cargoItem", "projectedFit", "fighterItem", "projectedFighter")
def getText(self, itmContext, selection): def getText(self, itmContext, selection):

View File

@@ -4,20 +4,28 @@ import gui.globalEvents as GE
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
import wx import wx
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
class Cargo(ContextMenu): class Cargo(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('cargo'):
return False
if srcContext not in ("marketItemGroup", "marketItemMisc"):
return False
sFit = Fit.getInstance() sFit = Fit.getInstance()
fitID = self.mainFrame.getActiveFit() fitID = self.mainFrame.getActiveFit()
fit = sFit.getFit(fitID) fit = sFit.getFit(fitID)
# Make sure context menu registers in the correct view # Make sure context menu registers in the correct view
if srcContext not in ("marketItemGroup", "marketItemMisc") or not fit or fit.isStructure: if not fit or fit.isStructure:
return False return False
return True return True
def getText(self, itmContext, selection): def getText(self, itmContext, selection):

View File

@@ -0,0 +1,41 @@
from gui.contextMenu import ContextMenu
import gui.mainFrame
import service
import gui.globalEvents as GE
import wx
from service.settings import ContextMenuSettings
from service.fit import Fit
class CargoAmmo(ContextMenu):
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection):
if not self.settings.get('cargoAmmo'):
return False
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
return False
for selected_item in selection:
if selected_item.category.ID in (
8, # Charge
):
return True
def getText(self, itmContext, selection):
return "Add {0} to Cargo (x1000)".format(itmContext)
def activate(self, fullContext, selection, i):
sFit = Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
typeID = int(selection[0].ID)
sFit.addCargo(fitID, typeID, 1000)
self.mainFrame.additionsPane.select("Cargo")
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
CargoAmmo.register()

View File

@@ -8,13 +8,18 @@ from eos.saveddata.character import Skill
import gui.globalEvents as GE import gui.globalEvents as GE
from service.fit import Fit from service.fit import Fit
from service.character import Character from service.character import Character
from service.settings import ContextMenuSettings
class ChangeAffectingSkills(ContextMenu): class ChangeAffectingSkills(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('changeAffectingSkills'):
return False
if self.mainFrame.getActiveFit() is None or srcContext not in ("fittingModule", "fittingCharge", "fittingShip"): if self.mainFrame.getActiveFit() is None or srcContext not in ("fittingModule", "fittingCharge", "fittingShip"):
return False return False

View File

@@ -6,6 +6,7 @@ import wx
from gui.bitmapLoader import BitmapLoader from gui.bitmapLoader import BitmapLoader
from service.fit import Fit from service.fit import Fit
from service.damagePattern import DamagePattern as import_DamagePattern from service.damagePattern import DamagePattern as import_DamagePattern
from service.settings import ContextMenuSettings
try: try:
from collections import OrderedDict from collections import OrderedDict
@@ -16,8 +17,12 @@ except ImportError:
class DamagePattern(ContextMenu): class DamagePattern(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('damagePattern'):
return False
return srcContext == "resistancesViewFull" and self.mainFrame.getActiveFit() is not None return srcContext == "resistancesViewFull" and self.mainFrame.getActiveFit() is not None
def getText(self, itmContext, selection): def getText(self, itmContext, selection):

View File

@@ -4,13 +4,18 @@ import gui.mainFrame
import wx import wx
import gui.globalEvents as GE import gui.globalEvents as GE
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
class ItemRemove(ContextMenu): class ItemRemove(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('droneRemoveStack'):
return False
return srcContext == "droneItem" return srcContext == "droneItem"
def getText(self, itmContext, selection): def getText(self, itmContext, selection):

View File

@@ -4,13 +4,18 @@ import gui.globalEvents as GE
from service.fit import Fit from service.fit import Fit
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
import wx import wx
from service.settings import ContextMenuSettings
class DroneSplit(ContextMenu): class DroneSplit(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('droneSplit'):
return False
return srcContext in ("droneItem", "projectedDrone") and selection[0].amount > 1 return srcContext in ("droneItem", "projectedDrone") and selection[0].amount > 1
def getText(self, itmContext, selection): def getText(self, itmContext, selection):

View File

@@ -0,0 +1,43 @@
from gui.contextMenu import ContextMenu
import gui.mainFrame
import service
import gui.globalEvents as GE
import wx
from service.settings import ContextMenuSettings
from service.fit import Fit
class DroneStack(ContextMenu):
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection):
if not self.settings.get('droneStack'):
return False
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
return False
for selected_item in selection:
if selected_item.category.ID in (
18, # Drones
):
return True
return False
def getText(self, itmContext, selection):
return "Add {0} to Drone Bay (x5)".format(itmContext)
def activate(self, fullContext, selection, i):
sFit = Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
typeID = int(selection[0].ID)
sFit.addDrone(fitID, typeID, 5)
self.mainFrame.additionsPane.select("Drones")
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
DroneStack.register()

View File

@@ -5,13 +5,18 @@ import gui.globalEvents as GE
import wx import wx
from gui.bitmapLoader import BitmapLoader from gui.bitmapLoader import BitmapLoader
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
class FactorReload(ContextMenu): class FactorReload(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('factorReload'):
return False
return srcContext == "firepowerViewFull" and self.mainFrame.getActiveFit() is not None return srcContext == "firepowerViewFull" and self.mainFrame.getActiveFit() is not None
def getText(self, itmContext, selection): def getText(self, itmContext, selection):

View File

@@ -4,13 +4,18 @@ from gui.contextMenu import ContextMenu
import gui.mainFrame import gui.mainFrame
import gui.globalEvents as GE import gui.globalEvents as GE
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
class FighterAbility(ContextMenu): class FighterAbility(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('fighterAbilities'):
return False
if self.mainFrame.getActiveFit() is None or srcContext not in ("fighterItem", "projectedFighter"): if self.mainFrame.getActiveFit() is None or srcContext not in ("fighterItem", "projectedFighter"):
return False return False

View File

@@ -6,13 +6,18 @@ import wx
from service.implantSet import ImplantSets as s_ImplantSets from service.implantSet import ImplantSets as s_ImplantSets
from service.character import Character from service.character import Character
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
class ImplantSets(ContextMenu): class ImplantSets(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('implantSets'):
return False
return srcContext in ("implantView", "implantEditor") return srcContext in ("implantView", "implantEditor")
def getText(self, itmContext, selection): def getText(self, itmContext, selection):

View File

@@ -4,13 +4,18 @@ import gui.mainFrame
import wx import wx
import gui.globalEvents as GE import gui.globalEvents as GE
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
class ItemRemove(ContextMenu): class ItemRemove(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('itemRemove'):
return False
return srcContext in ("fittingModule", "fittingCharge", return srcContext in ("fittingModule", "fittingCharge",
"droneItem", "implantItem", "droneItem", "implantItem",
"boosterItem", "projectedModule", "boosterItem", "projectedModule",

View File

@@ -4,13 +4,17 @@ import gui.mainFrame
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
import wx import wx
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
class ItemStats(ContextMenu): class ItemStats(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('itemStats'):
return False
return srcContext in ("marketItemGroup", "marketItemMisc", return srcContext in ("marketItemGroup", "marketItemMisc",
"fittingModule", "fittingCharge", "fittingModule", "fittingCharge",

View File

@@ -1,13 +1,18 @@
from gui.contextMenu import ContextMenu from gui.contextMenu import ContextMenu
import gui.mainFrame import gui.mainFrame
from service.market import Market from service.market import Market
from service.settings import ContextMenuSettings
class MarketJump(ContextMenu): class MarketJump(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('marketJump'):
return False
validContexts = ("marketItemMisc", "fittingModule", validContexts = ("marketItemMisc", "fittingModule",
"fittingCharge", "droneItem", "fittingCharge", "droneItem",
"implantItem", "boosterItem", "implantItem", "boosterItem",

View File

@@ -8,15 +8,30 @@ from service.market import Market
import gui.mainFrame import gui.mainFrame
import gui.globalEvents as GE import gui.globalEvents as GE
from gui.contextMenu import ContextMenu from gui.contextMenu import ContextMenu
from service.settings import ContextMenuSettings
from eos.saveddata.booster import Booster
from eos.saveddata.module import Module
from eos.saveddata.drone import Drone
from eos.saveddata.fighter import Fighter
from eos.saveddata.implant import Implant
class MetaSwap(ContextMenu): class MetaSwap(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('metaSwap'):
return False
if self.mainFrame.getActiveFit() is None or srcContext not in ("fittingModule",): if self.mainFrame.getActiveFit() is None or srcContext not in (
"fittingModule",
"droneItem",
"fighterItem",
"boosterItem",
"implantItem",
):
return False return False
# Check if list of variations is same for all of selection # Check if list of variations is same for all of selection
@@ -52,6 +67,17 @@ class MetaSwap(ContextMenu):
def get_metagroup(x): def get_metagroup(x):
return x.metaGroup.ID if x.metaGroup is not None else 0 return x.metaGroup.ID if x.metaGroup is not None else 0
def get_boosterrank(x):
# If we're returning a lot of items, sort my name
if len(self.variations) > 7:
return x.name
# Sort by booster chance to get some sort of pseudorank.
elif 'boosterEffectChance1' in x.attributes:
return x.attributes['boosterEffectChance1'].value
# the "first" rank (Synth) doesn't have boosterEffectChance1. If we're not pulling back all boosters, return 0 for proper sorting
else:
return 0
m = wx.Menu() m = wx.Menu()
# If on Windows we need to bind out events into the root menu, on other # If on Windows we need to bind out events into the root menu, on other
@@ -63,6 +89,15 @@ class MetaSwap(ContextMenu):
# Sort items by metalevel, and group within that metalevel # Sort items by metalevel, and group within that metalevel
items = list(self.variations) items = list(self.variations)
print context
if "implantItem" in context:
# sort implants based on name
items.sort(key=lambda x: x.name)
elif "boosterItem" in context:
# boosters don't have meta or anything concrete that we can rank by. Go by chance to inflict side effect
items.sort(key=get_boosterrank)
else:
# sort by group and meta level
items.sort(key=get_metalevel) items.sort(key=get_metalevel)
items.sort(key=get_metagroup) items.sort(key=get_metagroup)
@@ -74,7 +109,7 @@ class MetaSwap(ContextMenu):
else: else:
thisgroup = item.metaGroup.name thisgroup = item.metaGroup.name
if thisgroup != group: if thisgroup != group and context not in ("implantItem", "boosterItem"):
group = thisgroup group = thisgroup
id = ContextMenu.nextID() id = ContextMenu.nextID()
m.Append(id, u'%s' % group) m.Append(id, u'%s' % group)
@@ -97,10 +132,57 @@ class MetaSwap(ContextMenu):
fitID = self.mainFrame.getActiveFit() fitID = self.mainFrame.getActiveFit()
fit = sFit.getFit(fitID) fit = sFit.getFit(fitID)
for mod in self.selection: for selected_item in self.selection:
pos = fit.modules.index(mod) if isinstance(selected_item, Module):
pos = fit.modules.index(selected_item)
sFit.changeModule(fitID, pos, item.ID) sFit.changeModule(fitID, pos, item.ID)
elif isinstance(selected_item, Drone):
drone_count = None
for idx, drone_stack in enumerate(fit.drones):
if drone_stack is selected_item:
drone_count = drone_stack.amount
sFit.removeDrone(fitID, idx, drone_count)
break
if drone_count:
sFit.addDrone(fitID, item.ID, drone_count)
elif isinstance(selected_item, Fighter):
fighter_count = None
for idx, fighter_stack in enumerate(fit.fighters):
# Right now fighters always will have max stack size.
# Including this for future improvement, so if adjustable
# fighter stacks get added we're ready for it.
if fighter_stack is selected_item:
if fighter_stack.amount > 0:
fighter_count = fighter_stack.amount
elif fighter_stack.amount == -1:
fighter_count = fighter_stack.amountActive
else:
fighter_count.amount = 0
sFit.removeFighter(fitID, idx)
break
sFit.addFighter(fitID, item.ID)
elif isinstance(selected_item, Booster):
for idx, booster_stack in enumerate(fit.boosters):
if booster_stack is selected_item:
sFit.removeBooster(fitID, idx)
sFit.addBooster(fitID, item.ID)
break
elif isinstance(selected_item, Implant):
for idx, implant_stack in enumerate(fit.implants):
if implant_stack is selected_item:
sFit.removeImplant(fitID, idx)
sFit.addImplant(fitID, item.ID, False)
break
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))

View File

@@ -10,6 +10,7 @@ import gui.mainFrame
import gui.globalEvents as GE import gui.globalEvents as GE
from gui.contextMenu import ContextMenu from gui.contextMenu import ContextMenu
from gui.bitmapLoader import BitmapLoader from gui.bitmapLoader import BitmapLoader
from service.settings import ContextMenuSettings
class ModuleAmmoPicker(ContextMenu): class ModuleAmmoPicker(ContextMenu):
@@ -18,8 +19,12 @@ class ModuleAmmoPicker(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('moduleAmmoPicker'):
return False
if self.mainFrame.getActiveFit() is None or srcContext not in ("fittingModule", "projectedModule"): if self.mainFrame.getActiveFit() is None or srcContext not in ("fittingModule", "projectedModule"):
return False return False

View File

@@ -6,12 +6,14 @@ import gui.globalEvents as GE
from gui.builtinContextMenus.moduleAmmoPicker import ModuleAmmoPicker from gui.builtinContextMenus.moduleAmmoPicker import ModuleAmmoPicker
from eos.db.saveddata.queries import getFit as db_getFit from eos.db.saveddata.queries import getFit as db_getFit
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
class ModuleGlobalAmmoPicker(ModuleAmmoPicker): class ModuleGlobalAmmoPicker(ModuleAmmoPicker):
def __init__(self): def __init__(self):
super(ModuleGlobalAmmoPicker, self).__init__() super(ModuleGlobalAmmoPicker, self).__init__()
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def getText(self, itmContext, selection): def getText(self, itmContext, selection):
return "Charge (All)" return "Charge (All)"
@@ -42,6 +44,9 @@ class ModuleGlobalAmmoPicker(ModuleAmmoPicker):
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('moduleGlobalAmmoPicker'):
return False
try: try:
selectionLen = len(selection) selectionLen = len(selection)
except: except:

View File

@@ -3,13 +3,18 @@ import gui.mainFrame
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
import wx import wx
from gui.shipBrowser import FitSelected from gui.shipBrowser import FitSelected
from service.settings import ContextMenuSettings
class OpenFit(ContextMenu): class OpenFit(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('openFit'):
return False
return srcContext == "projectedFit" return srcContext == "projectedFit"
def getText(self, itmContext, selection): def getText(self, itmContext, selection):

View File

@@ -4,13 +4,18 @@ import gui.mainFrame
import wx import wx
import gui.globalEvents as GE import gui.globalEvents as GE
from service.market import Market from service.market import Market
from service.settings import ContextMenuSettings
class PriceClear(ContextMenu): class PriceClear(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('priceClear'):
return False
return srcContext == "priceViewFull" return srcContext == "priceViewFull"
def getText(self, itmContext, selection): def getText(self, itmContext, selection):

View File

@@ -4,13 +4,18 @@ import gui.globalEvents as GE
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
import wx import wx
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
class Project(ContextMenu): class Project(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('project'):
return False
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None: if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
return False return False

View File

@@ -4,13 +4,18 @@ from gui.contextMenu import ContextMenu
import gui.mainFrame import gui.mainFrame
from gui.shipBrowser import Stage3Selected from gui.shipBrowser import Stage3Selected
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
class ShipJump(ContextMenu): class ShipJump(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('shipJump'):
return False
return srcContext == "fittingShip" return srcContext == "fittingShip"
def getText(self, itmContext, selection): def getText(self, itmContext, selection):

View File

@@ -5,13 +5,18 @@ import gui.mainFrame
import gui.globalEvents as GE import gui.globalEvents as GE
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
class TacticalMode(ContextMenu): class TacticalMode(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('tacticalMode'):
return False
if self.mainFrame.getActiveFit() is None or srcContext != "fittingShip": if self.mainFrame.getActiveFit() is None or srcContext != "fittingShip":
return False return False

View File

@@ -6,6 +6,7 @@ import wx
from gui.bitmapLoader import BitmapLoader from gui.bitmapLoader import BitmapLoader
from service.targetResists import TargetResists as svc_TargetResists from service.targetResists import TargetResists as svc_TargetResists
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
try: try:
from collections import OrderedDict from collections import OrderedDict
@@ -16,8 +17,12 @@ except ImportError:
class TargetResists(ContextMenu): class TargetResists(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('targetResists'):
return False
if self.mainFrame.getActiveFit() is None or srcContext != "firepowerViewFull": if self.mainFrame.getActiveFit() is None or srcContext != "firepowerViewFull":
return False return False

View File

@@ -5,13 +5,18 @@ import gui.globalEvents as GE
import wx import wx
from service.market import Market from service.market import Market
from service.fit import Fit from service.fit import Fit
from service.settings import ContextMenuSettings
class WhProjector(ContextMenu): class WhProjector(ContextMenu):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection): def display(self, srcContext, selection):
if not self.settings.get('whProjector'):
return False
return srcContext == "projected" return srcContext == "projected"
def getText(self, itmContext, selection): def getText(self, itmContext, selection):

View File

@@ -1,8 +1,16 @@
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
import wx import wx
__all__ = ["pyfaGeneralPreferences", "pyfaHTMLExportPreferences", "pyfaUpdatePreferences", __all__ = [
"pyfaNetworkPreferences"] # noqa "pyfaGeneralPreferences",
"pyfaHTMLExportPreferences",
"pyfaUpdatePreferences",
"pyfaNetworkPreferences",
"pyfaDatabasePreferences",
"pyfaLoggingPreferences",
"pyfaEnginePreferences",
"pyfaStatViewPreferences",
]
if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)): if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)):
__all__.append("pyfaCrestPreferences") __all__.append("pyfaCrestPreferences")

View File

@@ -0,0 +1,114 @@
import wx
from gui.preferenceView import PreferenceView
from gui.bitmapLoader import BitmapLoader
import gui.mainFrame
from service.settings import ContextMenuSettings
class PFContextMenuPref(PreferenceView):
title = "Context Menu Panel"
def populatePanel(self, panel):
self.settings = ContextMenuSettings.getInstance()
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.dirtySettings = False
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
self.stTitle.Wrap(-1)
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
self.stSubTitle = wx.StaticText(panel, wx.ID_ANY,
u"Disabling context menus can improve responsiveness.",
wx.DefaultPosition, wx.DefaultSize, 0)
self.stSubTitle.Wrap(-1)
mainSizer.Add(self.stSubTitle, 0, wx.ALL, 5)
# Row 1
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.ALL, 5)
rbSizerRow1 = wx.BoxSizer(wx.HORIZONTAL)
self.rbBox1 = wx.RadioBox(panel, -1, "Set as Damage Pattern", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
self.rbBox1.SetSelection(self.settings.get('ammoPattern'))
rbSizerRow1.Add(self.rbBox1, 1, wx.TOP | wx.RIGHT, 5)
self.rbBox1.Bind(wx.EVT_RADIOBOX, self.OnSetting1Change)
self.rbBox2 = wx.RadioBox(panel, -1, "Change Skills", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
self.rbBox2.SetSelection(self.settings.get('changeAffectingSkills'))
rbSizerRow1.Add(self.rbBox2, 1, wx.ALL, 5)
self.rbBox2.Bind(wx.EVT_RADIOBOX, self.OnSetting2Change)
self.rbBox3 = wx.RadioBox(panel, -1, "Factor in Reload Time", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
self.rbBox3.SetSelection(self.settings.get('factorReload'))
rbSizerRow1.Add(self.rbBox3, 1, wx.ALL, 5)
self.rbBox3.Bind(wx.EVT_RADIOBOX, self.OnSetting3Change)
mainSizer.Add(rbSizerRow1, 1, wx.ALL | wx.EXPAND, 0)
# Row 2
rbSizerRow2 = wx.BoxSizer(wx.HORIZONTAL)
self.rbBox4 = wx.RadioBox(panel, -1, "Variations", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
self.rbBox4.SetSelection(self.settings.get('metaSwap'))
rbSizerRow2.Add(self.rbBox4, 1, wx.TOP | wx.RIGHT, 5)
self.rbBox4.Bind(wx.EVT_RADIOBOX, self.OnSetting4Change)
'''
self.rbBox5 = wx.RadioBox(panel, -1, "Charge", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
self.rbBox5.SetSelection(self.settings.get('moduleAmmoPicker'))
rbSizerRow2.Add(self.rbBox5, 1, wx.ALL, 5)
self.rbBox5.Bind(wx.EVT_RADIOBOX, self.OnSetting5Change)
'''
self.rbBox6 = wx.RadioBox(panel, -1, "Charge (All)", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
self.rbBox6.SetSelection(self.settings.get('moduleGlobalAmmoPicker'))
rbSizerRow2.Add(self.rbBox6, 1, wx.ALL, 5)
self.rbBox6.Bind(wx.EVT_RADIOBOX, self.OnSetting6Change)
mainSizer.Add(rbSizerRow2, 1, wx.ALL | wx.EXPAND, 0)
# Row 3
rbSizerRow3 = wx.BoxSizer(wx.HORIZONTAL)
self.rbBox7 = wx.RadioBox(panel, -1, "Project onto Fit", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
self.rbBox7.SetSelection(self.settings.get('project'))
rbSizerRow3.Add(self.rbBox7, 1, wx.TOP | wx.RIGHT, 5)
self.rbBox7.Bind(wx.EVT_RADIOBOX, self.OnSetting7Change)
mainSizer.Add(rbSizerRow3, 1, wx.ALL | wx.EXPAND, 0)
panel.SetSizer(mainSizer)
panel.Layout()
def OnSetting1Change(self, event):
self.settings.set('ammoPattern', event.GetInt())
def OnSetting2Change(self, event):
self.settings.set('changeAffectingSkills', event.GetInt())
def OnSetting3Change(self, event):
self.settings.set('factorReload', event.GetInt())
def OnSetting4Change(self, event):
self.settings.set('metaSwap', event.GetInt())
def OnSetting5Change(self, event):
self.settings.set('moduleAmmoPicker', event.GetInt())
def OnSetting6Change(self, event):
self.settings.set('moduleGlobalAmmoPicker', event.GetInt())
def OnSetting7Change(self, event):
self.settings.set('project', event.GetInt())
def getImage(self):
return BitmapLoader.getBitmap("settings_menu", "gui")
PFContextMenuPref.register()

View File

@@ -0,0 +1,112 @@
import wx
from gui.preferenceView import PreferenceView
from gui.bitmapLoader import BitmapLoader
import config
import logging
logger = logging.getLogger(__name__)
class PFGeneralPref(PreferenceView):
title = "Database"
def populatePanel(self, panel):
self.dirtySettings = False
# self.openFitsSettings = service.SettingsProvider.getInstance().getSettings("pyfaPrevOpenFits", {"enabled": False, "pyfaOpenFits": []})
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
self.stTitle.Wrap(-1)
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
self.stSubTitle = wx.StaticText(panel, wx.ID_ANY, u"(Cannot be changed while pyfa is running. Set via command line switches.)",
wx.DefaultPosition, wx.DefaultSize, 0)
self.stSubTitle.Wrap(-1)
mainSizer.Add(self.stSubTitle, 0, wx.ALL, 3)
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
# Save in Root
self.cbsaveInRoot = wx.CheckBox(panel, wx.ID_ANY, u"Using Executable Path for Saved Fit Database and Settings", wx.DefaultPosition, wx.DefaultSize, 0)
mainSizer.Add(self.cbsaveInRoot, 0, wx.ALL | wx.EXPAND, 5)
# Database path
self.stSetUserPath = wx.StaticText(panel, wx.ID_ANY, u"pyfa User Path:", wx.DefaultPosition, wx.DefaultSize, 0)
self.stSetUserPath.Wrap(-1)
mainSizer.Add(self.stSetUserPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
self.inputUserPath = wx.TextCtrl(panel, wx.ID_ANY, config.savePath, wx.DefaultPosition, wx.DefaultSize, 0)
self.inputUserPath.SetEditable(False)
self.inputUserPath.SetBackgroundColour((200, 200, 200))
mainSizer.Add(self.inputUserPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
# Save DB
self.stFitDB = wx.StaticText(panel, wx.ID_ANY, u"Fitting Database:", wx.DefaultPosition, wx.DefaultSize, 0)
self.stFitDB.Wrap(-1)
mainSizer.Add(self.stFitDB, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
self.inputFitDB = wx.TextCtrl(panel, wx.ID_ANY, config.saveDB, wx.DefaultPosition, wx.DefaultSize, 0)
self.inputFitDB.SetEditable(False)
self.inputFitDB.SetBackgroundColour((200, 200, 200))
mainSizer.Add(self.inputFitDB, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
# Game Data DB
self.stGameDB = wx.StaticText(panel, wx.ID_ANY, u"Game Database:", wx.DefaultPosition, wx.DefaultSize, 0)
self.stGameDB.Wrap(-1)
mainSizer.Add(self.stGameDB, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
self.inputGameDB = wx.TextCtrl(panel, wx.ID_ANY, config.gameDB, wx.DefaultPosition, wx.DefaultSize, 0)
self.inputGameDB.SetEditable(False)
self.inputGameDB.SetBackgroundColour((200, 200, 200))
mainSizer.Add(self.inputGameDB, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
self.cbsaveInRoot.SetValue(config.saveInRoot)
self.cbsaveInRoot.Bind(wx.EVT_CHECKBOX, self.onCBsaveInRoot)
self.inputUserPath.Bind(wx.EVT_LEAVE_WINDOW, self.OnWindowLeave)
self.inputFitDB.Bind(wx.EVT_LEAVE_WINDOW, self.OnWindowLeave)
self.inputGameDB.Bind(wx.EVT_LEAVE_WINDOW, self.OnWindowLeave)
panel.SetSizer(mainSizer)
panel.Layout()
def onCBsaveInRoot(self, event):
# We don't want users to be able to actually change this,
# so if they try and change it, set it back to the current setting
self.cbsaveInRoot.SetValue(config.saveInRoot)
# If we ever enable it might need this.
'''
config.saveInRoot = self.cbsaveInRoot.GetValue()
'''
def getImage(self):
return BitmapLoader.getBitmap("settings_database", "gui")
def OnWindowLeave(self, event):
# We don't want to do anything when they leave,
# but in the future we'd want to make sure settings
# changed get saved.
pass
'''
#Set database path
config.defPaths(self.inputFitDBPath.GetValue())
logger.debug("Running database import")
if self.cbimportDefaults is True:
# Import default database values
# Import values that must exist otherwise Pyfa breaks
DefaultDatabaseValues.importRequiredDefaults()
# Import default values for damage profiles
DefaultDatabaseValues.importDamageProfileDefaults()
# Import default values for target resist profiles
DefaultDatabaseValues.importResistProfileDefaults()
'''
PFGeneralPref.register()

View File

@@ -0,0 +1,84 @@
import logging
import wx
from service.fit import Fit
from gui.bitmapLoader import BitmapLoader
from gui.preferenceView import PreferenceView
logger = logging.getLogger(__name__)
class PFFittingEnginePref(PreferenceView):
title = "Fitting Engine"
def __init__(self):
self.dirtySettings = False
def refreshPanel(self, fit):
pass
# noinspection PyAttributeOutsideInit
def populatePanel(self, panel):
# self.openFitsSettings = service.SettingsProvider.getInstance().getSettings("pyfaPrevOpenFits", {"enabled": False, "pyfaOpenFits": []})
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
self.stTitle.Wrap(-1)
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
self.cbGlobalForceReload = wx.CheckBox(panel, wx.ID_ANY, u"Factor in reload time when calculating capacitor usage, damage, and tank.",
wx.DefaultPosition, wx.DefaultSize, 0)
mainSizer.Add(self.cbGlobalForceReload, 0, wx.ALL | wx.EXPAND, 5)
# Future code once new cap sim is implemented
'''
self.cbGlobalForceReactivationTimer = wx.CheckBox( panel, wx.ID_ANY, u"Factor in reactivation timer", wx.DefaultPosition, wx.DefaultSize, 0 )
mainSizer.Add( self.cbGlobalForceReactivationTimer, 0, wx.ALL|wx.EXPAND, 5 )
text = u" Ignores reactivation timer when calculating capacitor usage,\n damage, and tank."
self.cbGlobalForceReactivationTimerText = wx.StaticText( panel, wx.ID_ANY, text, wx.DefaultPosition, wx.DefaultSize, 0 )
self.cbGlobalForceReactivationTimerText.Wrap( -1 )
self.cbGlobalForceReactivationTimerText.SetFont( wx.Font( 10, 70, 90, 90, False, wx.EmptyString ) )
mainSizer.Add( self.cbGlobalForceReactivationTimerText, 0, wx.ALL, 5 )
'''
# Future code for mining laser crystal
'''
self.cbGlobalMiningSpecialtyCrystal = wx.CheckBox( panel, wx.ID_ANY, u"Factor in reactivation timer", wx.DefaultPosition, wx.DefaultSize, 0 )
mainSizer.Add( self.cbGlobalMiningSpecialtyCrystal, 0, wx.ALL|wx.EXPAND, 5 )
text = u" If enabled, displays the Specialty Crystal mining amount.\n This is the amount mined when using crystals and mining the matching asteroid."
self.cbGlobalMiningSpecialtyCrystalText = wx.StaticText( panel, wx.ID_ANY, text, wx.DefaultPosition, wx.DefaultSize, 0 )
self.cbGlobalMiningSpecialtyCrystalText.Wrap( -1 )
self.cbGlobalMiningSpecialtyCrystalText.SetFont( wx.Font( 10, 70, 90, 90, False, wx.EmptyString ) )
mainSizer.Add( self.cbGlobalMiningSpecialtyCrystalText, 0, wx.ALL, 5 )
'''
self.sFit = Fit.getInstance()
self.cbGlobalForceReload.SetValue(self.sFit.serviceFittingOptions["useGlobalForceReload"])
self.cbGlobalForceReload.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalForceReloadStateChange)
panel.SetSizer(mainSizer)
panel.Layout()
def OnCBGlobalForceReloadStateChange(self, event):
self.sFit.serviceFittingOptions["useGlobalForceReload"] = self.cbGlobalForceReload.GetValue()
def getImage(self):
return BitmapLoader.getBitmap("settings_fitting", "gui")
def OnWindowLeave(self, event):
# We don't want to do anything when they leave,
# but in the future we might.
pass
PFFittingEnginePref.register()

View File

@@ -40,10 +40,6 @@ class PFGeneralPref(PreferenceView):
wx.DefaultSize, 0) wx.DefaultSize, 0)
mainSizer.Add(self.cbGlobalDmgPattern, 0, wx.ALL | wx.EXPAND, 5) mainSizer.Add(self.cbGlobalDmgPattern, 0, wx.ALL | wx.EXPAND, 5)
self.cbGlobalForceReload = wx.CheckBox(panel, wx.ID_ANY, u"Factor in reload time", wx.DefaultPosition,
wx.DefaultSize, 0)
mainSizer.Add(self.cbGlobalForceReload, 0, wx.ALL | wx.EXPAND, 5)
self.cbCompactSkills = wx.CheckBox(panel, wx.ID_ANY, u"Compact skills needed tooltip", wx.DefaultPosition, self.cbCompactSkills = wx.CheckBox(panel, wx.ID_ANY, u"Compact skills needed tooltip", wx.DefaultPosition,
wx.DefaultSize, 0) wx.DefaultSize, 0)
mainSizer.Add(self.cbCompactSkills, 0, wx.ALL | wx.EXPAND, 5) mainSizer.Add(self.cbCompactSkills, 0, wx.ALL | wx.EXPAND, 5)
@@ -97,7 +93,6 @@ class PFGeneralPref(PreferenceView):
self.cbGlobalChar.SetValue(self.sFit.serviceFittingOptions["useGlobalCharacter"]) self.cbGlobalChar.SetValue(self.sFit.serviceFittingOptions["useGlobalCharacter"])
self.cbGlobalDmgPattern.SetValue(self.sFit.serviceFittingOptions["useGlobalDamagePattern"]) self.cbGlobalDmgPattern.SetValue(self.sFit.serviceFittingOptions["useGlobalDamagePattern"])
self.cbGlobalForceReload.SetValue(self.sFit.serviceFittingOptions["useGlobalForceReload"])
self.cbFitColorSlots.SetValue(self.sFit.serviceFittingOptions["colorFitBySlot"] or False) self.cbFitColorSlots.SetValue(self.sFit.serviceFittingOptions["colorFitBySlot"] or False)
self.cbRackSlots.SetValue(self.sFit.serviceFittingOptions["rackSlots"] or False) self.cbRackSlots.SetValue(self.sFit.serviceFittingOptions["rackSlots"] or False)
self.cbRackLabels.SetValue(self.sFit.serviceFittingOptions["rackLabels"] or False) self.cbRackLabels.SetValue(self.sFit.serviceFittingOptions["rackLabels"] or False)
@@ -112,7 +107,6 @@ class PFGeneralPref(PreferenceView):
self.cbGlobalChar.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalCharStateChange) self.cbGlobalChar.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalCharStateChange)
self.cbGlobalDmgPattern.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalDmgPatternStateChange) self.cbGlobalDmgPattern.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalDmgPatternStateChange)
self.cbGlobalForceReload.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalForceReloadStateChange)
self.cbFitColorSlots.Bind(wx.EVT_CHECKBOX, self.onCBGlobalColorBySlot) self.cbFitColorSlots.Bind(wx.EVT_CHECKBOX, self.onCBGlobalColorBySlot)
self.cbRackSlots.Bind(wx.EVT_CHECKBOX, self.onCBGlobalRackSlots) self.cbRackSlots.Bind(wx.EVT_CHECKBOX, self.onCBGlobalRackSlots)
self.cbRackLabels.Bind(wx.EVT_CHECKBOX, self.onCBGlobalRackLabels) self.cbRackLabels.Bind(wx.EVT_CHECKBOX, self.onCBGlobalRackLabels)
@@ -152,13 +146,6 @@ class PFGeneralPref(PreferenceView):
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
event.Skip() event.Skip()
def OnCBGlobalForceReloadStateChange(self, event):
self.sFit.serviceFittingOptions["useGlobalForceReload"] = self.cbGlobalForceReload.GetValue()
fitID = self.mainFrame.getActiveFit()
self.sFit.refreshFit(fitID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
event.Skip()
def OnCBGlobalCharStateChange(self, event): def OnCBGlobalCharStateChange(self, event):
self.sFit.serviceFittingOptions["useGlobalCharacter"] = self.cbGlobalChar.GetValue() self.sFit.serviceFittingOptions["useGlobalCharacter"] = self.cbGlobalChar.GetValue()
event.Skip() event.Skip()

View File

@@ -0,0 +1,59 @@
import wx
from gui.preferenceView import PreferenceView
from gui.bitmapLoader import BitmapLoader
import config
class PFGeneralPref(PreferenceView):
title = "Logging"
def populatePanel(self, panel):
self.dirtySettings = False
# self.openFitsSettings = service.SettingsProvider.getInstance().getSettings("pyfaPrevOpenFits", {"enabled": False, "pyfaOpenFits": []})
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
self.stTitle.Wrap(-1)
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
self.stSubTitle = wx.StaticText(panel, wx.ID_ANY, u"(Cannot be changed while pyfa is running. Set via command line switches.)",
wx.DefaultPosition, wx.DefaultSize, 0)
self.stSubTitle.Wrap(-1)
mainSizer.Add(self.stSubTitle, 0, wx.ALL, 3)
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
# Debug Logging
self.cbdebugLogging = wx.CheckBox(panel, wx.ID_ANY, u"Debug Logging Enabled", wx.DefaultPosition, wx.DefaultSize, 0)
mainSizer.Add(self.cbdebugLogging, 0, wx.ALL | wx.EXPAND, 5)
self.cbdebugLogging.SetValue(config.debug)
self.cbdebugLogging.Bind(wx.EVT_CHECKBOX, self.onCBdebugLogging)
panel.SetSizer(mainSizer)
panel.Layout()
def onCBdebugLogging(self, event):
# We don't want users to be able to actually change this,
# so if they try and change it, set it back to the current setting
self.cbdebugLogging.SetValue(config.debug)
# In case we do, down there road, here's a bit of a start.
'''
if self.cbdebugLogging.GetValue() is True:
self.cbdebugLogging.SetValue(False)
config.Debug = self.cbdebugLogging.GetValue()
else:
self.cbdebugLogging.SetValue(True)
config.Debug = self.cbdebugLogging.GetValue()
'''
def getImage(self):
return BitmapLoader.getBitmap("settings_log", "gui")
PFGeneralPref.register()

View File

@@ -0,0 +1,158 @@
# noinspection PyPackageRequirements
import wx
from gui.preferenceView import PreferenceView
from gui.bitmapLoader import BitmapLoader
from service.settings import StatViewSettings
class PFStatViewPref(PreferenceView):
title = "Statistics Panel"
def __init__(self):
self.dirtySettings = False
self.settings = StatViewSettings.getInstance()
def refreshPanel(self, fit):
pass
# noinspection PyAttributeOutsideInit
def populatePanel(self, panel):
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
self.stTitle.Wrap(-1)
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
self.stSubTitle = wx.StaticText(panel, wx.ID_ANY,
u"Changes require restart of pyfa to take effect.",
wx.DefaultPosition, wx.DefaultSize, 0)
self.stSubTitle.Wrap(-1)
mainSizer.Add(self.stSubTitle, 0, wx.ALL, 3)
# Row 1
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.ALL, 5)
rbSizerRow1 = wx.BoxSizer(wx.HORIZONTAL)
self.rbResources = wx.RadioBox(panel, -1, "Resources", wx.DefaultPosition, wx.DefaultSize, ['None', 'Minimal', 'Full'], 1, wx.RA_SPECIFY_COLS)
# Disable minimal as we don't have a view for this yet
self.rbResources.EnableItem(1, False)
self.rbResources.SetSelection(self.settings.get('resources'))
rbSizerRow1.Add(self.rbResources, 1, wx.TOP | wx.RIGHT, 5)
self.rbResources.Bind(wx.EVT_RADIOBOX, self.OnResourcesChange)
self.rbResistances = wx.RadioBox(panel, -1, "Resistances", wx.DefaultPosition, wx.DefaultSize, ['None', 'Minimal', 'Full'], 1, wx.RA_SPECIFY_COLS)
# Disable minimal as we don't have a view for this yet
self.rbResistances.EnableItem(1, False)
self.rbResistances.SetSelection(self.settings.get('resistances'))
rbSizerRow1.Add(self.rbResistances, 1, wx.ALL, 5)
self.rbResistances.Bind(wx.EVT_RADIOBOX, self.OnResistancesChange)
self.rbRecharge = wx.RadioBox(panel, -1, "Shield/Armor Tank", wx.DefaultPosition, wx.DefaultSize, ['None', 'Minimal', 'Full'], 1, wx.RA_SPECIFY_COLS)
# Disable minimal as we don't have a view for this yet
self.rbRecharge.EnableItem(1, False)
self.rbRecharge.SetSelection(self.settings.get('recharge'))
rbSizerRow1.Add(self.rbRecharge, 1, wx.ALL, 5)
self.rbRecharge.Bind(wx.EVT_RADIOBOX, self.OnRechargeChange)
mainSizer.Add(rbSizerRow1, 1, wx.ALL | wx.EXPAND, 0)
# Row 2
rbSizerRow2 = wx.BoxSizer(wx.HORIZONTAL)
self.rbFirepower = wx.RadioBox(panel, -1, "Firepower", wx.DefaultPosition, wx.DefaultSize, ['None', 'Minimal', 'Full'], 1, wx.RA_SPECIFY_COLS)
# Disable minimal as we don't have a view for this yet
self.rbFirepower.EnableItem(1, False)
self.rbFirepower.SetSelection(self.settings.get('firepower'))
rbSizerRow2.Add(self.rbFirepower, 1, wx.TOP | wx.RIGHT, 5)
self.rbFirepower.Bind(wx.EVT_RADIOBOX, self.OnFirepowerChange)
self.rbCapacitor = wx.RadioBox(panel, -1, "Capacitor", wx.DefaultPosition, wx.DefaultSize, ['None', 'Minimal', 'Full'], 1, wx.RA_SPECIFY_COLS)
# Disable minimal as we don't have a view for this yet
self.rbCapacitor.EnableItem(1, False)
self.rbCapacitor.SetSelection(self.settings.get('capacitor'))
rbSizerRow2.Add(self.rbCapacitor, 1, wx.ALL, 5)
self.rbCapacitor.Bind(wx.EVT_RADIOBOX, self.OnCapacitorChange)
self.rbMisc = wx.RadioBox(panel, -1, "Misc", wx.DefaultPosition, wx.DefaultSize, ['None', 'Minimal', 'Full'], 1, wx.RA_SPECIFY_COLS)
# Disable full as we don't have a view for this yet
self.rbMisc.EnableItem(2, False)
self.rbMisc.SetSelection(self.settings.get('targetingMisc'))
rbSizerRow2.Add(self.rbMisc, 1, wx.ALL, 5)
self.rbMisc.Bind(wx.EVT_RADIOBOX, self.OnTargetingMiscChange)
mainSizer.Add(rbSizerRow2, 1, wx.ALL | wx.EXPAND, 0)
# Row 3
rbSizerRow3 = wx.BoxSizer(wx.HORIZONTAL)
self.rbPrice = wx.RadioBox(panel, -1, "Price", wx.DefaultPosition, wx.DefaultSize, ['None', 'Minimal', 'Full'], 1, wx.RA_SPECIFY_COLS)
# Disable minimal as we don't have a view for this yet
self.rbPrice.EnableItem(1, False)
self.rbPrice.SetSelection(self.settings.get('price'))
rbSizerRow3.Add(self.rbPrice, 1, wx.TOP | wx.RIGHT, 5)
self.rbPrice.Bind(wx.EVT_RADIOBOX, self.OnPriceChange)
self.rbOutgoing = wx.RadioBox(panel, -1, "Remote Reps", wx.DefaultPosition, wx.DefaultSize, ['None', 'Minimal', 'Full'], 1, wx.RA_SPECIFY_COLS)
self.rbOutgoing.SetSelection(self.settings.get('outgoing'))
rbSizerRow3.Add(self.rbOutgoing, 1, wx.TOP | wx.RIGHT, 5)
self.rbOutgoing.Bind(wx.EVT_RADIOBOX, self.OnOutgoingChange)
# We don't have views for these.....yet
'''
self.rbMining = wx.RadioBox(panel, -1, "Mining", wx.DefaultPosition, wx.DefaultSize,
['None', 'Minimal', 'Full'], 1, wx.RA_SPECIFY_COLS)
self.rbMining.SetSelection(self.settings.get('miningyield'))
rbSizerRow3.Add(self.rbMining, 1, wx.ALL, 5)
self.rbMining.Bind(wx.EVT_RADIOBOX, self.OnMiningYieldChange)
self.rbDrones = wx.RadioBox(panel, -1, "Drones", wx.DefaultPosition, wx.DefaultSize,
['None', 'Minimal', 'Full'], 1, wx.RA_SPECIFY_COLS)
self.rbDrones.SetSelection(self.settings.get('drones'))
rbSizerRow3.Add(self.rbDrones, 1, wx.ALL, 5)
self.rbDrones.Bind(wx.EVT_RADIOBOX, self.OnDroneChange)
'''
mainSizer.Add(rbSizerRow3, 1, wx.ALL | wx.EXPAND, 0)
panel.SetSizer(mainSizer)
panel.Layout()
def OnResourcesChange(self, event):
self.settings.set('resources', event.GetInt())
def OnResistancesChange(self, event):
self.settings.set('resistances', event.GetInt())
def OnRechargeChange(self, event):
self.settings.set('recharge', event.GetInt())
def OnFirepowerChange(self, event):
self.settings.set('firepower', event.GetInt())
def OnCapacitorChange(self, event):
self.settings.set('capacitor', event.GetInt())
def OnTargetingMiscChange(self, event):
self.settings.set('targetingMisc', event.GetInt())
def OnPriceChange(self, event):
self.settings.set('price', event.GetInt())
def OnOutgoingChange(self, event):
self.settings.set('outgoing', event.GetInt())
def OnMiningYieldChange(self, event):
self.settings.set('miningyield', event.GetInt())
def OnDroneChange(self, event):
self.settings.set('drones', event.GetInt())
def getImage(self):
return BitmapLoader.getBitmap("settings_stats", "gui")
PFStatViewPref.register()

View File

@@ -1,3 +1,11 @@
__all__ = ["resourcesViewFull", "resistancesViewFull", __all__ = [
"rechargeViewFull", "firepowerViewFull", "capacitorViewFull", "resourcesViewFull",
"targetingMiscViewFull", "priceViewFull", "miningyieldViewFull"] "resistancesViewFull",
"rechargeViewFull",
"firepowerViewFull",
"capacitorViewFull",
"outgoingViewFull",
"outgoingViewMinimal",
"targetingMiscViewMinimal",
"priceViewFull",
]

View File

@@ -0,0 +1,106 @@
# ===============================================================================
# Copyright (C) 2014 Alexandros Kosiaris
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
# noinspection PyPackageRequirements
import wx
from gui.statsView import StatsView
from gui.bitmapLoader import BitmapLoader
from gui.utils.numberFormatter import formatAmount
class OutgoingViewFull(StatsView):
name = "outgoingViewFull"
def __init__(self, parent):
StatsView.__init__(self)
self.parent = parent
self._cachedValues = []
def getHeaderText(self, fit):
return "Remote Reps"
def getTextExtentW(self, text):
width, height = self.parent.GetTextExtent(text)
return width
def populatePanel(self, contentPanel, headerPanel):
contentSizer = contentPanel.GetSizer()
parent = self.panel = contentPanel
self.headerPanel = headerPanel
sizerOutgoing = wx.GridSizer(1, 4)
contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0)
counter = 0
rr_list = [
("RemoteCapacitor", "Capacitor:", "capacitorInfo", "Capacitor GJ/s per second transferred remotely."),
("RemoteShield", "Shield:", "shieldActive", "Shield hitpoints per second repaired remotely."),
("RemoteArmor", "Armor:", "armorActive", "Armor hitpoints per second repaired remotely."),
("RemoteHull", "Hull:", "hullActive", "Hull hitpoints per second repaired remotely."),
]
for outgoingType, label, image, tooltip in rr_list:
baseBox = wx.BoxSizer(wx.VERTICAL)
baseBox.Add(BitmapLoader.getStaticBitmap("%s_big" % image, parent, "gui"), 0, wx.ALIGN_CENTER)
if "Capacitor" in outgoingType:
lbl = wx.StaticText(parent, wx.ID_ANY, u"0 GJ/s")
else:
lbl = wx.StaticText(parent, wx.ID_ANY, u"0 HP/s")
lbl.SetToolTip(wx.ToolTip(tooltip))
setattr(self, "label%s" % outgoingType, lbl)
baseBox.Add(lbl, 0, wx.ALIGN_CENTER)
self._cachedValues.append(0)
counter += 1
sizerOutgoing.Add(baseBox, 1, wx.ALIGN_LEFT)
def refreshPanel(self, fit):
# If we did anything intresting, we'd update our labels to reflect the new fit's stats here
stats = [
("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, u"%s HP/s", None),
("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, u"%s HP/s", None),
("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, u"%s HP/s", None),
("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, u"%s GJ/s", None),
]
counter = 0
for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats:
label = getattr(self, labelName)
value = value() if fit is not None else 0
value = value if value is not None else 0
if self._cachedValues[counter] != value:
valueStr = formatAmount(value, prec, lowest, highest)
label.SetLabel(valueFormat % valueStr)
tipStr = valueFormat % valueStr if altFormat is None else altFormat % value
label.SetToolTip(wx.ToolTip(tipStr))
self._cachedValues[counter] = value
counter += 1
self.panel.Layout()
self.headerPanel.Layout()
OutgoingViewFull.register()

View File

@@ -0,0 +1,105 @@
# ===============================================================================
# Copyright (C) 2014 Alexandros Kosiaris
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
# noinspection PyPackageRequirements
import wx
from gui.statsView import StatsView
from gui.utils.numberFormatter import formatAmount
class OutgoingViewMinimal(StatsView):
name = "outgoingViewMinimal"
def __init__(self, parent):
StatsView.__init__(self)
self.parent = parent
self._cachedValues = []
def getHeaderText(self, fit):
return "Remote Reps"
def getTextExtentW(self, text):
width, height = self.parent.GetTextExtent(text)
return width
def populatePanel(self, contentPanel, headerPanel):
contentSizer = contentPanel.GetSizer()
parent = self.panel = contentPanel
self.headerPanel = headerPanel
sizerOutgoing = wx.GridSizer(1, 4)
contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0)
counter = 0
rr_list = [
("RemoteCapacitor", "Capacitor:", "capacitorInfo", "Capacitor GJ/s per second transferred remotely."),
("RemoteShield", "Shield:", "shieldActive", "Shield hitpoints per second repaired remotely."),
("RemoteArmor", "Armor:", "armorActive", "Armor hitpoints per second repaired remotely."),
("RemoteHull", "Hull:", "hullActive", "Hull hitpoints per second repaired remotely."),
]
for outgoingType, label, image, tooltip in rr_list:
baseBox = wx.BoxSizer(wx.VERTICAL)
baseBox.Add(wx.StaticText(contentPanel, wx.ID_ANY, label), 0, wx.ALIGN_CENTER)
if "Capacitor" in outgoingType:
lbl = wx.StaticText(parent, wx.ID_ANY, u"0 GJ/s")
else:
lbl = wx.StaticText(parent, wx.ID_ANY, u"0 HP/s")
lbl.SetToolTip(wx.ToolTip(tooltip))
setattr(self, "label%s" % outgoingType, lbl)
baseBox.Add(lbl, 0, wx.ALIGN_CENTER)
self._cachedValues.append(0)
counter += 1
sizerOutgoing.Add(baseBox, 1, wx.ALIGN_LEFT)
def refreshPanel(self, fit):
# If we did anything intresting, we'd update our labels to reflect the new fit's stats here
stats = [
("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, u"%s HP/s", None),
("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, u"%s HP/s", None),
("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, u"%s HP/s", None),
("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, u"%s GJ/s", None),
]
counter = 0
for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats:
label = getattr(self, labelName)
value = value() if fit is not None else 0
value = value if value is not None else 0
if self._cachedValues[counter] != value:
valueStr = formatAmount(value, prec, lowest, highest)
label.SetLabel(valueFormat % valueStr)
tipStr = valueFormat % valueStr if altFormat is None else altFormat % value
label.SetToolTip(wx.ToolTip(tipStr))
self._cachedValues[counter] = value
counter += 1
self.panel.Layout()
self.headerPanel.Layout()
OutgoingViewMinimal.register()

View File

@@ -29,7 +29,7 @@ except ImportError:
class TargetingMiscViewFull(StatsView): class TargetingMiscViewFull(StatsView):
name = "targetingmiscViewFull" name = "targetingMiscViewFull"
def __init__(self, parent): def __init__(self, parent):
StatsView.__init__(self) StatsView.__init__(self)

View File

@@ -0,0 +1,256 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================
# noinspection PyPackageRequirements
import wx
from gui.statsView import StatsView
from gui.utils.numberFormatter import formatAmount
try:
from collections import OrderedDict
except ImportError:
from utils.compat import OrderedDict
class TargetingMiscViewMinimal(StatsView):
name = "targetingMiscViewMinimal"
def __init__(self, parent):
StatsView.__init__(self)
self.parent = parent
self._cachedValues = []
def getHeaderText(self, fit):
return "Targeting && Misc"
def getTextExtentW(self, text):
width, height = self.parent.GetTextExtent(text)
return width
def populatePanel(self, contentPanel, headerPanel):
contentSizer = contentPanel.GetSizer()
self.panel = contentPanel
self.headerPanel = headerPanel
gridTargetingMisc = wx.FlexGridSizer(1, 3)
contentSizer.Add(gridTargetingMisc, 0, wx.EXPAND | wx.ALL, 0)
gridTargetingMisc.AddGrowableCol(0)
gridTargetingMisc.AddGrowableCol(2)
# Targeting
gridTargeting = wx.FlexGridSizer(5, 2)
gridTargeting.AddGrowableCol(1)
gridTargetingMisc.Add(gridTargeting, 0, wx.ALIGN_LEFT | wx.ALL, 5)
labels = (("Targets", "Targets", ""),
("Range", "Range", "km"),
("Scan res.", "ScanRes", "mm"),
("Sensor str.", "SensorStr", ""),
("Drone range", "CtrlRange", "km"))
for header, labelShort, unit in labels:
gridTargeting.Add(wx.StaticText(contentPanel, wx.ID_ANY, "%s: " % header), 0, wx.ALIGN_LEFT)
box = wx.BoxSizer(wx.HORIZONTAL)
gridTargeting.Add(box, 0, wx.ALIGN_LEFT)
lbl = wx.StaticText(contentPanel, wx.ID_ANY, "0 %s" % unit)
setattr(self, "label%s" % labelShort, lbl)
box.Add(lbl, 0, wx.ALIGN_LEFT)
self._cachedValues.append({"main": 0})
# Misc
gridTargetingMisc.Add(wx.StaticLine(contentPanel, wx.ID_ANY, style=wx.VERTICAL), 0, wx.EXPAND, 3)
gridMisc = wx.FlexGridSizer(5, 2)
gridMisc.AddGrowableCol(1)
gridTargetingMisc.Add(gridMisc, 0, wx.ALIGN_LEFT | wx.ALL, 5)
labels = (("Speed", "Speed", "m/s"),
("Align time", "AlignTime", "s"),
("Signature", "SigRadius", "m"),
("Warp Speed", "WarpSpeed", "AU/s"),
("Cargo", "Cargo", u"m\u00B3"))
for header, labelShort, unit in labels:
gridMisc.Add(wx.StaticText(contentPanel, wx.ID_ANY, "%s: " % header), 0, wx.ALIGN_LEFT)
box = wx.BoxSizer(wx.HORIZONTAL)
gridMisc.Add(box, 0, wx.ALIGN_LEFT)
lbl = wx.StaticText(contentPanel, wx.ID_ANY, "0 %s" % unit)
setattr(self, "labelFull%s" % labelShort, lbl)
box.Add(lbl, 0, wx.ALIGN_LEFT)
self._cachedValues.append({"main": 0})
def refreshPanel(self, fit):
# If we did anything interesting, we'd update our labels to reflect the new fit's stats here
cargoNamesOrder = OrderedDict((
("fleetHangarCapacity", "Fleet hangar"),
("shipMaintenanceBayCapacity", "Maintenance bay"),
("specialAmmoHoldCapacity", "Ammo hold"),
("specialFuelBayCapacity", "Fuel bay"),
("specialShipHoldCapacity", "Ship hold"),
("specialSmallShipHoldCapacity", "Small ship hold"),
("specialMediumShipHoldCapacity", "Medium ship hold"),
("specialLargeShipHoldCapacity", "Large ship hold"),
("specialIndustrialShipHoldCapacity", "Industrial ship hold"),
("specialOreHoldCapacity", "Ore hold"),
("specialMineralHoldCapacity", "Mineral hold"),
("specialMaterialBayCapacity", "Material bay"),
("specialGasHoldCapacity", "Gas hold"),
("specialSalvageHoldCapacity", "Salvage hold"),
("specialCommandCenterHoldCapacity", "Command center hold"),
("specialPlanetaryCommoditiesHoldCapacity", "Planetary goods hold"),
("specialQuafeHoldCapacity", "Quafe hold")
))
cargoValues = {
"main": lambda: fit.ship.getModifiedItemAttr("capacity"),
"fleetHangarCapacity": lambda: fit.ship.getModifiedItemAttr("fleetHangarCapacity"),
"shipMaintenanceBayCapacity": lambda: fit.ship.getModifiedItemAttr("shipMaintenanceBayCapacity"),
"specialAmmoHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialAmmoHoldCapacity"),
"specialFuelBayCapacity": lambda: fit.ship.getModifiedItemAttr("specialFuelBayCapacity"),
"specialShipHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialShipHoldCapacity"),
"specialSmallShipHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialSmallShipHoldCapacity"),
"specialMediumShipHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialMediumShipHoldCapacity"),
"specialLargeShipHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialLargeShipHoldCapacity"),
"specialIndustrialShipHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialIndustrialShipHoldCapacity"),
"specialOreHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialOreHoldCapacity"),
"specialMineralHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialMineralHoldCapacity"),
"specialMaterialBayCapacity": lambda: fit.ship.getModifiedItemAttr("specialMaterialBayCapacity"),
"specialGasHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialGasHoldCapacity"),
"specialSalvageHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialSalvageHoldCapacity"),
"specialCommandCenterHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialCommandCenterHoldCapacity"),
"specialPlanetaryCommoditiesHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialPlanetaryCommoditiesHoldCapacity"),
"specialQuafeHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialQuafeHoldCapacity")
}
stats = (("labelTargets", {"main": lambda: fit.maxTargets}, 3, 0, 0, ""),
("labelRange", {"main": lambda: fit.maxTargetRange / 1000}, 3, 0, 0, "km"),
("labelScanRes", {"main": lambda: fit.ship.getModifiedItemAttr("scanResolution")}, 3, 0, 0, "mm"),
("labelSensorStr", {"main": lambda: fit.scanStrength}, 3, 0, 0, ""),
("labelCtrlRange", {"main": lambda: fit.extraAttributes["droneControlRange"] / 1000}, 3, 0, 0, "km"),
("labelFullSpeed", {"main": lambda: fit.maxSpeed}, 3, 0, 0, "m/s"),
("labelFullAlignTime", {"main": lambda: fit.alignTime}, 3, 0, 0, "s"),
("labelFullSigRadius", {"main": lambda: fit.ship.getModifiedItemAttr("signatureRadius")}, 3, 0, 9, ""),
("labelFullWarpSpeed", {"main": lambda: fit.warpSpeed}, 3, 0, 0, "AU/s"),
("labelFullCargo", cargoValues, 4, 0, 9, u"m\u00B3"))
counter = 0
RADII = [("Pod", 25), ("Interceptor", 33), ("Frigate", 38),
("Destroyer", 83), ("Cruiser", 130),
("Battlecruiser", 265), ("Battleship", 420),
("Carrier", 3000)]
for labelName, valueDict, prec, lowest, highest, unit in stats:
label = getattr(self, labelName)
newValues = {}
for valueAlias, value in valueDict.items():
value = value() if fit is not None else 0
value = value if value is not None else 0
newValues[valueAlias] = value
if self._cachedValues[counter] != newValues:
mainValue = newValues["main"]
otherValues = dict((k, newValues[k]) for k in filter(lambda k: k != "main", newValues))
if labelName == "labelFullCargo":
# Get sum of all cargoholds except for maintenance bay
additionalCargo = sum(otherValues.values())
if additionalCargo > 0:
label.SetLabel("%s+%s %s" % (formatAmount(mainValue, prec, lowest, highest),
formatAmount(additionalCargo, prec, lowest, highest),
unit))
else:
label.SetLabel("%s %s" % (formatAmount(mainValue, prec, lowest, highest), unit))
else:
label.SetLabel("%s %s" % (formatAmount(mainValue, prec, lowest, highest), unit))
# Tooltip stuff
if fit:
if labelName == "labelScanRes":
lockTime = "%s\n" % "Lock Times".center(30)
for size, radius in RADII:
left = "%.1fs" % fit.calculateLockTime(radius)
right = "%s [%d]" % (size, radius)
lockTime += "%5s\t%s\n" % (left, right)
label.SetToolTip(wx.ToolTip(lockTime))
elif labelName == "labelFullWarpSpeed":
label.SetToolTip(wx.ToolTip("Max Warp Distance: %.1f AU" % fit.maxWarpDistance))
elif labelName == "labelSensorStr":
if fit.jamChance > 0:
label.SetToolTip(wx.ToolTip("Type: %s\n%.1f%% Chance of Jam" % (fit.scanType, fit.jamChance)))
else:
label.SetToolTip(wx.ToolTip("Type: %s" % fit.scanType))
elif labelName == "labelFullAlignTime":
alignTime = "Align:\t%.3fs" % mainValue
mass = 'Mass:\t{:,.0f}kg'.format(fit.ship.getModifiedItemAttr("mass"))
agility = "Agility:\t%.3fx" % (fit.ship.getModifiedItemAttr("agility") or 0)
label.SetToolTip(wx.ToolTip("%s\n%s\n%s" % (alignTime, mass, agility)))
elif labelName == "labelFullCargo":
tipLines = [u"Cargohold: {:,.2f}m\u00B3 / {:,.2f}m\u00B3".format(fit.cargoBayUsed, newValues["main"])]
for attrName, tipAlias in cargoNamesOrder.items():
if newValues[attrName] > 0:
tipLines.append(u"{}: {:,.2f}m\u00B3".format(tipAlias, newValues[attrName]))
label.SetToolTip(wx.ToolTip(u"\n".join(tipLines)))
else:
label.SetToolTip(wx.ToolTip("%.1f" % mainValue))
else:
label.SetToolTip(wx.ToolTip(""))
self._cachedValues[counter] = newValues
elif labelName == "labelFullWarpSpeed":
if fit:
label.SetToolTip(wx.ToolTip("Max Warp Distance: %.1f AU" % fit.maxWarpDistance))
else:
label.SetToolTip(wx.ToolTip(""))
elif labelName == "labelSensorStr":
if fit:
if fit.jamChance > 0:
label.SetToolTip(wx.ToolTip("Type: %s\n%.1f%% Chance of Jam" % (fit.scanType, fit.jamChance)))
else:
label.SetToolTip(wx.ToolTip("Type: %s" % fit.scanType))
else:
label.SetToolTip(wx.ToolTip(""))
elif labelName == "labelFullCargo":
if fit:
cachedCargo = self._cachedValues[counter]
# if you add stuff to cargo, the capacity doesn't change and thus it is still cached
# This assures us that we force refresh of cargo tooltip
tipLines = [u"Cargohold: {:,.2f}m\u00B3 / {:,.2f}m\u00B3".format(fit.cargoBayUsed, cachedCargo["main"])]
for attrName, tipAlias in cargoNamesOrder.items():
if cachedCargo[attrName] > 0:
tipLines.append(u"{}: {:,.2f}m\u00B3".format(tipAlias, cachedCargo[attrName]))
label.SetToolTip(wx.ToolTip(u"\n".join(tipLines)))
else:
label.SetToolTip(wx.ToolTip(""))
# forces update of probe size, since this stat is used by both sig radius and sensor str
if labelName == "labelFullSigRadius":
print "labelName"
if fit:
label.SetToolTip(wx.ToolTip("Probe Size: %.3f" % (fit.probeSize or 0)))
else:
label.SetToolTip(wx.ToolTip(""))
counter += 1
self.panel.Layout()
self.headerPanel.Layout()
TargetingMiscViewMinimal.register()

View File

@@ -33,7 +33,7 @@ from gui.builtinViewColumns.state import State
from gui.bitmapLoader import BitmapLoader from gui.bitmapLoader import BitmapLoader
import gui.builtinViews.emptyView import gui.builtinViews.emptyView
from gui.utils.exportHtml import exportHtml from gui.utils.exportHtml import exportHtml
from logging import getLogger from logbook import Logger
from gui.chromeTabs import EVT_NOTEBOOK_PAGE_CHANGED from gui.chromeTabs import EVT_NOTEBOOK_PAGE_CHANGED
from service.fit import Fit from service.fit import Fit
@@ -41,7 +41,7 @@ from service.market import Market
import gui.globalEvents as GE import gui.globalEvents as GE
logger = getLogger(__name__) pyfalog = Logger(__name__)
# Tab spawning handler # Tab spawning handler
@@ -55,14 +55,16 @@ class FitSpawner(gui.multiSwitch.TabSpawner):
def fitSelected(self, event): def fitSelected(self, event):
count = -1 count = -1
for index, page in enumerate(self.multiSwitch.pages): for index, page in enumerate(self.multiSwitch.pages):
if not isinstance(page, gui.builtinViews.emptyView.BlankPage): # Don't try and process it if it's a blank page.
try: try:
if page.activeFitID == event.fitID: if page.activeFitID == event.fitID:
count += 1 count += 1
self.multiSwitch.SetSelection(index) self.multiSwitch.SetSelection(index)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=event.fitID)) wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=event.fitID))
break break
except: except Exception as e:
pass pyfalog.critical("Caught exception in fitSelected")
pyfalog.critical(e)
if count < 0: if count < 0:
startup = getattr(event, "startup", False) # see OpenFitsThread in gui.mainFrame startup = getattr(event, "startup", False) # see OpenFitsThread in gui.mainFrame
sFit = Fit.getInstance() sFit = Fit.getInstance()
@@ -148,7 +150,6 @@ class FittingView(d.Display):
self.activeFitID = None self.activeFitID = None
self.FVsnapshot = None self.FVsnapshot = None
self.itemCount = 0 self.itemCount = 0
self.itemRect = 0
self.hoveredRow = None self.hoveredRow = None
self.hoveredColumn = None self.hoveredColumn = None
@@ -278,6 +279,7 @@ class FittingView(d.Display):
sFit.refreshFit(self.getActiveFit()) sFit.refreshFit(self.getActiveFit())
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.activeFitID)) wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.activeFitID))
except wx._core.PyDeadObjectError: except wx._core.PyDeadObjectError:
pyfalog.warning("Caught dead object")
pass pass
event.Skip() event.Skip()
@@ -406,7 +408,7 @@ class FittingView(d.Display):
if mod1.slot != mod2.slot: if mod1.slot != mod2.slot:
return return
if getattr(mod2, "modPosition"): if getattr(mod2, "modPosition") is not None:
if clone and mod2.isEmpty: if clone and mod2.isEmpty:
sFit.cloneModule(self.mainFrame.getActiveFit(), srcIdx, mod2.modPosition) sFit.cloneModule(self.mainFrame.getActiveFit(), srcIdx, mod2.modPosition)
else: else:
@@ -414,7 +416,7 @@ class FittingView(d.Display):
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.mainFrame.getActiveFit())) wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.mainFrame.getActiveFit()))
else: else:
logger.error("Missing module position for: %s", str(getattr(mod2, "ID", "Unknown"))) pyfalog.error("Missing module position for: {0}", str(getattr(mod2, "ID", "Unknown")))
def generateMods(self): def generateMods(self):
""" """
@@ -483,7 +485,7 @@ class FittingView(d.Display):
self.Show(self.activeFitID is not None and self.activeFitID == event.fitID) self.Show(self.activeFitID is not None and self.activeFitID == event.fitID)
except wx._core.PyDeadObjectError: except wx._core.PyDeadObjectError:
pass pyfalog.warning("Caught dead object")
finally: finally:
event.Skip() event.Skip()
@@ -631,20 +633,21 @@ class FittingView(d.Display):
self.Thaw() self.Thaw()
self.itemCount = self.GetItemCount() self.itemCount = self.GetItemCount()
self.itemRect = self.GetItemRect(0)
if 'wxMac' in wx.PlatformInfo: if 'wxMac' in wx.PlatformInfo:
try: try:
self.MakeSnapshot() self.MakeSnapshot()
except: except Exception as e:
pass pyfalog.critical("Failed to make snapshot")
pyfalog.critical(e)
def OnShow(self, event): def OnShow(self, event):
if event.GetShow(): if event.GetShow():
try: try:
self.MakeSnapshot() self.MakeSnapshot()
except: except Exception as e:
pass pyfalog.critical("Failed to make snapshot")
pyfalog.critical(e)
event.Skip() event.Skip()
def Snapshot(self): def Snapshot(self):
@@ -669,8 +672,9 @@ class FittingView(d.Display):
sFit = Fit.getInstance() sFit = Fit.getInstance()
try: try:
fit = sFit.getFit(self.activeFitID) fit = sFit.getFit(self.activeFitID)
except: except Exception as e:
return pyfalog.critical("Failed to get fit")
pyfalog.critical(e)
if fit is None: if fit is None:
return return

View File

@@ -33,6 +33,8 @@ from service.fit import Fit
from service.character import Character from service.character import Character
from service.network import AuthenticationError, TimeoutError from service.network import AuthenticationError, TimeoutError
from service.market import Market from service.market import Market
from logbook import Logger
pyfalog = Logger(__name__)
class CharacterTextValidor(BaseValidator): class CharacterTextValidor(BaseValidator):
@@ -55,6 +57,7 @@ class CharacterTextValidor(BaseValidator):
return True return True
except ValueError, e: except ValueError, e:
pyfalog.error(e)
wx.MessageBox(u"{}".format(e), "Error") wx.MessageBox(u"{}".format(e), "Error")
textCtrl.SetFocus() textCtrl.SetFocus()
return False return False
@@ -628,11 +631,16 @@ class APIView(wx.Panel):
try: try:
activeChar = self.charEditor.entityEditor.getActiveEntity() activeChar = self.charEditor.entityEditor.getActiveEntity()
list = sChar.apiCharList(activeChar.ID, self.inputID.GetLineText(0), self.inputKey.GetLineText(0)) list = sChar.apiCharList(activeChar.ID, self.inputID.GetLineText(0), self.inputKey.GetLineText(0))
except AuthenticationError: except AuthenticationError, e:
self.stStatus.SetLabel("Authentication failure. Please check keyID and vCode combination.") msg = "Authentication failure. Please check keyID and vCode combination."
except TimeoutError: pyfalog.info(msg)
self.stStatus.SetLabel("Request timed out. Please check network connectivity and/or proxy settings.") self.stStatus.SetLabel(msg)
except TimeoutError, e:
msg = "Request timed out. Please check network connectivity and/or proxy settings."
pyfalog.info(msg)
self.stStatus.SetLabel(msg)
except Exception, e: except Exception, e:
pyfalog.error(e)
self.stStatus.SetLabel("Error:\n%s" % e.message) self.stStatus.SetLabel("Error:\n%s" % e.message)
else: else:
self.charChoice.Clear() self.charChoice.Clear()
@@ -655,6 +663,7 @@ class APIView(wx.Panel):
sChar.apiFetch(activeChar.ID, charName) sChar.apiFetch(activeChar.ID, charName)
self.stStatus.SetLabel("Successfully fetched %s\'s skills from EVE API." % charName) self.stStatus.SetLabel("Successfully fetched %s\'s skills from EVE API." % charName)
except Exception, e: except Exception, e:
pyfalog.error("Unable to retrieve {0}\'s skills. Error message:\n{1}", charName, e)
self.stStatus.SetLabel("Unable to retrieve %s\'s skills. Error message:\n%s" % (charName, e)) self.stStatus.SetLabel("Unable to retrieve %s\'s skills. Error message:\n%s" % (charName, e))

View File

@@ -24,6 +24,8 @@ import gui.globalEvents as GE
import gui.mainFrame import gui.mainFrame
from service.character import Character from service.character import Character
from service.fit import Fit from service.fit import Fit
from logbook import Logger
pyfalog = Logger(__name__)
class CharacterSelection(wx.Panel): class CharacterSelection(wx.Panel):
@@ -112,9 +114,10 @@ class CharacterSelection(wx.Panel):
if charName: if charName:
try: try:
sChar.apiFetch(self.getActiveCharacter(), charName) sChar.apiFetch(self.getActiveCharacter(), charName)
except: except Exception as e:
# can we do a popup, notifying user of API error? # can we do a popup, notifying user of API error?
pass pyfalog.error("API fetch error")
pyfalog.error(e)
self.refreshCharacterList() self.refreshCharacterList()
def charChanged(self, event): def charChanged(self, event):

View File

@@ -25,9 +25,11 @@ import gui.utils.colorUtils as colorUtils
import gui.utils.drawUtils as drawUtils import gui.utils.drawUtils as drawUtils
import gui.utils.fonts as fonts import gui.utils.fonts as fonts
from gui.bitmapLoader import BitmapLoader from gui.bitmapLoader import BitmapLoader
from logbook import Logger
from service.fit import Fit from service.fit import Fit
pyfalog = Logger(__name__)
_PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = wx.lib.newevent.NewEvent() _PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = wx.lib.newevent.NewEvent()
_PageChanged, EVT_NOTEBOOK_PAGE_CHANGED = wx.lib.newevent.NewEvent() _PageChanged, EVT_NOTEBOOK_PAGE_CHANGED = wx.lib.newevent.NewEvent()
_PageAdding, EVT_NOTEBOOK_PAGE_ADDING = wx.lib.newevent.NewEvent() _PageAdding, EVT_NOTEBOOK_PAGE_ADDING = wx.lib.newevent.NewEvent()
@@ -1093,8 +1095,9 @@ class PFTabsContainer(wx.Panel):
self.previewTab = tab self.previewTab = tab
self.previewTimer.Start(500, True) self.previewTimer.Start(500, True)
break break
except: except Exception as e:
pass pyfalog.critical("Exception caught in CheckTabPreview.")
pyfalog.critical(e)
def CheckAddHighlighted(self, x, y): def CheckAddHighlighted(self, x, y):
""" """

View File

@@ -19,9 +19,9 @@
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
import wx import wx
import logging from logbook import Logger
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class ContextMenu(object): class ContextMenu(object):
@@ -121,7 +121,7 @@ class ContextMenu(object):
debug_end = len(cls._ids) debug_end = len(cls._ids)
if debug_end - debug_start: if debug_end - debug_start:
logger.debug("%d new IDs created for this menu" % (debug_end - debug_start)) pyfalog.debug("{0} new IDs created for this menu", (debug_end - debug_start))
return rootMenu if empty is False else None return rootMenu if empty is False else None
@@ -181,7 +181,7 @@ class ContextMenu(object):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from gui.builtinContextMenus import ( # noqa: E402,F401 from gui.builtinContextMenus import ( # noqa: E402,F401
openFit, openFit,
# moduleGlobalAmmoPicker, moduleGlobalAmmoPicker,
moduleAmmoPicker, moduleAmmoPicker,
itemStats, itemStats,
damagePattern, damagePattern,
@@ -200,6 +200,8 @@ from gui.builtinContextMenus import ( # noqa: E402,F401
targetResists, targetResists,
priceClear, priceClear,
amount, amount,
cargoAmmo,
droneStack,
metaSwap, metaSwap,
implantSets, implantSets,
fighterAbilities, fighterAbilities,

View File

@@ -14,6 +14,9 @@ from eos.db import getItem
from gui.display import Display from gui.display import Display
import gui.globalEvents as GE import gui.globalEvents as GE
from logbook import Logger
pyfalog = Logger(__name__)
if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)): if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)):
from service.crest import Crest, CrestModes from service.crest import Crest, CrestModes
@@ -147,7 +150,9 @@ class CrestFittings(wx.Frame):
self.fitTree.populateSkillTree(fittings) self.fitTree.populateSkillTree(fittings)
del waitDialog del waitDialog
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
self.statusbar.SetStatusText("Connection error, please check your internet connection") msg = "Connection error, please check your internet connection"
pyfalog.error(msg)
self.statusbar.SetStatusText(msg)
def importFitting(self, event): def importFitting(self, event):
selection = self.fitView.fitSelection selection = self.fitView.fitSelection
@@ -173,7 +178,9 @@ class CrestFittings(wx.Frame):
try: try:
sCrest.delFitting(self.getActiveCharacter(), data['fittingID']) sCrest.delFitting(self.getActiveCharacter(), data['fittingID'])
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
self.statusbar.SetStatusText("Connection error, please check your internet connection") msg = "Connection error, please check your internet connection"
pyfalog.error(msg)
self.statusbar.SetStatusText(msg)
class ExportToEve(wx.Frame): class ExportToEve(wx.Frame):
@@ -281,9 +288,12 @@ class ExportToEve(wx.Frame):
text = json.loads(res.text) text = json.loads(res.text)
self.statusbar.SetStatusText(text['message'], 1) self.statusbar.SetStatusText(text['message'], 1)
except ValueError: except ValueError:
pyfalog.warning("Value error on loading JSON.")
self.statusbar.SetStatusText("", 1) self.statusbar.SetStatusText("", 1)
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
self.statusbar.SetStatusText("Connection error, please check your internet connection", 1) msg = "Connection error, please check your internet connection"
pyfalog.error(msg)
self.statusbar.SetStatusText(msg)
class CrestMgmt(wx.Dialog): class CrestMgmt(wx.Dialog):
@@ -405,8 +415,9 @@ class FittingsTreeView(wx.Panel):
cargo = Cargo(getItem(item['type']['id'])) cargo = Cargo(getItem(item['type']['id']))
cargo.amount = item['quantity'] cargo.amount = item['quantity']
list.append(cargo) list.append(cargo)
except: except Exception as e:
pass pyfalog.critical("Exception caught in displayFit")
pyfalog.critical(e)
self.parent.fitView.fitSelection = selection self.parent.fitView.fitSelection = selection
self.parent.fitView.update(list) self.parent.fitView.update(list)

94
gui/errorDialog.py Normal file
View File

@@ -0,0 +1,94 @@
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
import wx
import sys
import gui.utils.fonts as fonts
import config
class ErrorFrame(wx.Frame):
def __init__(self, exception, tb):
wx.Frame.__init__(self, None, id=wx.ID_ANY, title="pyfa error", pos=wx.DefaultPosition, size=wx.Size(500, 400),
style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER | wx.STAY_ON_TOP)
desc = "pyfa has experienced an unexpected error. Below is the " \
"Traceback that contains crucial information about how this " \
"error was triggered. Please contact the developers with " \
"the information provided through the EVE Online forums " \
"or file a GitHub issue."
self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize)
if 'wxMSW' in wx.PlatformInfo:
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
mainSizer = wx.BoxSizer(wx.VERTICAL)
headSizer = wx.BoxSizer(wx.HORIZONTAL)
self.headingText = wx.StaticText(self, wx.ID_ANY, "Error!", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE)
self.headingText.SetFont(wx.Font(14, 74, 90, 92, False))
headSizer.Add(self.headingText, 1, wx.ALL, 5)
mainSizer.Add(headSizer, 0, wx.EXPAND, 5)
mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.EXPAND | wx.ALL, 5)
descSizer = wx.BoxSizer(wx.HORIZONTAL)
self.descText = wx.TextCtrl(self, wx.ID_ANY, desc, wx.DefaultPosition, wx.DefaultSize,
wx.TE_AUTO_URL | wx.TE_MULTILINE | wx.TE_READONLY | wx.BORDER_NONE | wx.TRANSPARENT_WINDOW)
self.descText.SetFont(wx.Font(fonts.BIG, wx.SWISS, wx.NORMAL, wx.NORMAL))
descSizer.Add(self.descText, 1, wx.ALL, 5)
mainSizer.Add(descSizer, 1, wx.EXPAND, 5)
self.eveForums = wx.HyperlinkCtrl(self, wx.ID_ANY, "EVE Forums Thread", "https://forums.eveonline.com/default.aspx?g=posts&t=466425",
wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE)
mainSizer.Add(self.eveForums, 0, wx.ALL, 2)
self.eveForums = wx.HyperlinkCtrl(self, wx.ID_ANY, "Github Issues", "https://github.com/pyfa-org/Pyfa/issues",
wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE)
mainSizer.Add(self.eveForums, 0, wx.ALL, 2)
# mainSizer.AddSpacer((0, 5), 0, wx.EXPAND, 5)
self.errorTextCtrl = wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2 | wx.TE_DONTWRAP)
self.errorTextCtrl.SetFont(wx.Font(8, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL))
mainSizer.Add(self.errorTextCtrl, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
self.errorTextCtrl.AppendText("pyfa root: ")
self.errorTextCtrl.AppendText(config.pyfaPath or "Unknown")
self.errorTextCtrl.AppendText('\n')
self.errorTextCtrl.AppendText("save path: ")
self.errorTextCtrl.AppendText(config.savePath or "Unknown")
self.errorTextCtrl.AppendText('\n')
self.errorTextCtrl.AppendText("fs encoding: ")
self.errorTextCtrl.AppendText(sys.getfilesystemencoding())
self.errorTextCtrl.AppendText('\n\n')
self.errorTextCtrl.AppendText(tb)
self.SetSizer(mainSizer)
mainSizer.Layout()
self.Layout()
self.Centre(wx.BOTH)
self.Show()

View File

@@ -18,8 +18,7 @@
# ============================================================================= # =============================================================================
import os import os
import logging from logbook import Logger
import imp
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
import wx import wx
@@ -30,50 +29,53 @@ import gui.mainFrame
import gui.globalEvents as GE import gui.globalEvents as GE
from gui.graph import Graph from gui.graph import Graph
from gui.bitmapLoader import BitmapLoader from gui.bitmapLoader import BitmapLoader
from config import parsePath import traceback
pyfalog = Logger(__name__)
# Don't actually import the thing, since it takes for fucking ever
try: try:
imp.find_module('matplotlib')
graphFrame_enabled = True
mplImported = True
except ImportError:
graphFrame_enabled = False
mplImported = False
logger = logging.getLogger(__name__)
class GraphFrame(wx.Frame):
def __init__(self, parent, style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE | wx.FRAME_FLOAT_ON_PARENT):
global graphFrame_enabled
global mplImported
self.Patch = None
self.mpl_version = -1
try:
import matplotlib as mpl import matplotlib as mpl
self.mpl_version = int(mpl.__version__[0])
if self.mpl_version >= 2: mpl_version = int(mpl.__version__[0]) or -1
if mpl_version >= 2:
mpl.use('wxagg') mpl.use('wxagg')
mplImported = True mplImported = True
else: else:
mplImported = False mplImported = False
from matplotlib.patches import Patch from matplotlib.patches import Patch
self.Patch = Patch
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas
from matplotlib.figure import Figure from matplotlib.figure import Figure
graphFrame_enabled = True graphFrame_enabled = True
except ImportError: mplImported = True
except ImportError as e:
pyfalog.warning("Matplotlib failed to import. Likely missing or incompatible version.")
mpl_version = -1
Patch = mpl = Canvas = Figure = None Patch = mpl = Canvas = Figure = None
graphFrame_enabled = False graphFrame_enabled = False
mplImported = False
except Exception:
# We can get exceptions deep within matplotlib. Catch those. See GH #1046
tb = traceback.format_exc()
pyfalog.critical("Exception when importing Matplotlib. Continuing without importing.")
pyfalog.critical(tb)
mpl_version = -1
Patch = mpl = Canvas = Figure = None
graphFrame_enabled = False
mplImported = False
class GraphFrame(wx.Frame):
def __init__(self, parent, style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE | wx.FRAME_FLOAT_ON_PARENT):
global graphFrame_enabled
global mplImported
global mpl_version
self.legendFix = False self.legendFix = False
if not graphFrame_enabled: if not graphFrame_enabled:
logger.info("Problems importing matplotlib; continuing without graphs") pyfalog.warning("Matplotlib is not enabled. Skipping initialization.")
return return
try: try:
@@ -81,7 +83,7 @@ class GraphFrame(wx.Frame):
except: except:
cache_dir = os.path.expanduser(os.path.join("~", ".matplotlib")) cache_dir = os.path.expanduser(os.path.join("~", ".matplotlib"))
cache_file = parsePath(cache_dir, 'fontList.cache') cache_file = os.path.join(cache_dir, 'fontList.cache')
if os.access(cache_dir, os.W_OK | os.X_OK) and os.path.isfile(cache_file): if os.access(cache_dir, os.W_OK | os.X_OK) and os.path.isfile(cache_file):
# remove matplotlib font cache, see #234 # remove matplotlib font cache, see #234
@@ -225,6 +227,8 @@ class GraphFrame(wx.Frame):
self.draw() self.draw()
def draw(self, event=None): def draw(self, event=None):
global mpl_version
values = self.getValues() values = self.getValues()
view = self.getView() view = self.getView()
self.subplot.clear() self.subplot.clear()
@@ -244,11 +248,12 @@ class GraphFrame(wx.Frame):
self.subplot.plot(x, y) self.subplot.plot(x, y)
legend.append(fit.name) legend.append(fit.name)
except: except:
pyfalog.warning("Invalid values in '{0}'", fit.name)
self.SetStatusText("Invalid values in '%s'" % fit.name) self.SetStatusText("Invalid values in '%s'" % fit.name)
self.canvas.draw() self.canvas.draw()
return return
if self.mpl_version < 2: if mpl_version < 2:
if self.legendFix and len(legend) > 0: if self.legendFix and len(legend) > 0:
leg = self.subplot.legend(tuple(legend), "upper right", shadow=False) leg = self.subplot.legend(tuple(legend), "upper right", shadow=False)
for t in leg.get_texts(): for t in leg.get_texts():
@@ -264,7 +269,7 @@ class GraphFrame(wx.Frame):
for l in leg.get_lines(): for l in leg.get_lines():
l.set_linewidth(1) l.set_linewidth(1)
elif self.mpl_version >= 2: elif mpl_version >= 2:
legend2 = [] legend2 = []
legend_colors = { legend_colors = {
0: "blue", 0: "blue",
@@ -282,7 +287,7 @@ class GraphFrame(wx.Frame):
selected_color = legend_colors[i] selected_color = legend_colors[i]
except: except:
selected_color = None selected_color = None
legend2.append(self.Patch(color=selected_color, label=i_name), ) legend2.append(Patch(color=selected_color, label=i_name), )
if len(legend2) > 0: if len(legend2) > 0:
leg = self.subplot.legend(handles=legend2) leg = self.subplot.legend(handles=legend2)

View File

@@ -197,6 +197,10 @@ class ItemStatsContainer(wx.Panel):
self.affectedby = ItemAffectedBy(self.nbContainer, stuff, item) self.affectedby = ItemAffectedBy(self.nbContainer, stuff, item)
self.nbContainer.AddPage(self.affectedby, "Affected by") self.nbContainer.AddPage(self.affectedby, "Affected by")
if config.debug:
self.properties = ItemProperties(self.nbContainer, stuff, item, context)
self.nbContainer.AddPage(self.properties, "Properties")
self.nbContainer.Bind(wx.EVT_LEFT_DOWN, self.mouseHit) self.nbContainer.Bind(wx.EVT_LEFT_DOWN, self.mouseHit)
self.SetSizer(mainSizer) self.SetSizer(mainSizer)
self.Layout() self.Layout()
@@ -496,17 +500,19 @@ class ItemParams(wx.Panel):
attribute = Attribute.getInstance().getAttributeInfo(value) attribute = Attribute.getInstance().getAttributeInfo(value)
return "%s (%d)" % (attribute.name.capitalize(), value) return "%s (%d)" % (attribute.name.capitalize(), value)
trans = {"Inverse Absolute Percent": (lambda: (1 - value) * 100, unitName), trans = {
"Inverse Absolute Percent" : (lambda: (1 - value) * 100, unitName),
"Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName), "Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName),
"Modifier Percent": ( "Modifier Percent" : (
lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName), lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName),
"Volume": (lambda: value, u"m\u00B3"), "Volume" : (lambda: value, u"m\u00B3"),
"Sizeclass": (lambda: value, ""), "Sizeclass" : (lambda: value, ""),
"Absolute Percent": (lambda: (value * 100), unitName), "Absolute Percent" : (lambda: (value * 100), unitName),
"Milliseconds": (lambda: value / 1000.0, unitName), "Milliseconds" : (lambda: value / 1000.0, unitName),
"typeID": (itemIDCallback, ""), "typeID" : (itemIDCallback, ""),
"groupID": (groupIDCallback, ""), "groupID" : (groupIDCallback, ""),
"attributeID": (attributeIDCallback, "")} "attributeID" : (attributeIDCallback, "")
}
override = trans.get(unitDisplayName) override = trans.get(unitDisplayName)
if override is not None: if override is not None:
@@ -689,17 +695,18 @@ class ItemCompare(wx.Panel):
attribute = Attribute.getInstance().getAttributeInfo(value) attribute = Attribute.getInstance().getAttributeInfo(value)
return "%s (%d)" % (attribute.name.capitalize(), value) return "%s (%d)" % (attribute.name.capitalize(), value)
trans = {"Inverse Absolute Percent": (lambda: (1 - value) * 100, unitName), trans = {
"Inverse Absolute Percent" : (lambda: (1 - value) * 100, unitName),
"Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName), "Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName),
"Modifier Percent": ( "Modifier Percent" : (lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName),
lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName), "Volume" : (lambda: value, u"m\u00B3"),
"Volume": (lambda: value, u"m\u00B3"), "Sizeclass" : (lambda: value, ""),
"Sizeclass": (lambda: value, ""), "Absolute Percent" : (lambda: (value * 100), unitName),
"Absolute Percent": (lambda: (value * 100), unitName), "Milliseconds" : (lambda: value / 1000.0, unitName),
"Milliseconds": (lambda: value / 1000.0, unitName), "typeID" : (itemIDCallback, ""),
"typeID": (itemIDCallback, ""), "groupID" : (groupIDCallback, ""),
"groupID": (groupIDCallback, ""), "attributeID" : (attributeIDCallback, "")
"attributeID": (attributeIDCallback, "")} }
override = trans.get(unitDisplayName) override = trans.get(unitDisplayName)
if override is not None: if override is not None:
@@ -849,7 +856,7 @@ class ItemEffects(wx.Panel):
If effect file does not exist, create it If effect file does not exist, create it
""" """
file_ = config.getPyfaPath(os.path.join("eos", "effects", "%s.py" % event.GetText().lower())) file_ = os.path.join(config.pyfaPath, "eos", "effects", "%s.py" % event.GetText().lower())
if not os.path.isfile(file_): if not os.path.isfile(file_):
open(file_, 'a').close() open(file_, 'a').close()
@@ -1295,3 +1302,97 @@ class ItemAffectedBy(wx.Panel):
treeitem = self.affectedBy.AppendItem(child, display, attrIcon) treeitem = self.affectedBy.AppendItem(child, display, attrIcon)
self.affectedBy.SetPyData(treeitem, saved) self.affectedBy.SetPyData(treeitem, saved)
self.treeItems.append(treeitem) self.treeItems.append(treeitem)
class ItemProperties(wx.Panel):
def __init__(self, parent, stuff, item, context=None):
wx.Panel.__init__(self, parent)
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.paramList = AutoListCtrl(self, wx.ID_ANY,
style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES | wx.NO_BORDER)
mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0)
self.SetSizer(mainSizer)
self.toggleView = 1
self.stuff = stuff
self.item = item
self.attrInfo = {}
self.attrValues = {}
self._fetchValues()
self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
mainSizer.Add(self.m_staticline, 0, wx.EXPAND)
bSizer = wx.BoxSizer(wx.HORIZONTAL)
self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, 0)
bSizer.Add(self.totalAttrsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT)
mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT)
self.PopulateList()
def _fetchValues(self):
if self.stuff is None:
self.attrInfo.clear()
self.attrValues.clear()
self.attrInfo.update(self.item.attributes)
self.attrValues.update(self.item.attributes)
elif self.stuff.item == self.item:
self.attrInfo.clear()
self.attrValues.clear()
self.attrInfo.update(self.stuff.item.attributes)
self.attrValues.update(self.stuff.itemModifiedAttributes)
elif self.stuff.charge == self.item:
self.attrInfo.clear()
self.attrValues.clear()
self.attrInfo.update(self.stuff.charge.attributes)
self.attrValues.update(self.stuff.chargeModifiedAttributes)
# When item for stats window no longer exists, don't change anything
else:
return
def PopulateList(self):
self.paramList.InsertColumn(0, "Attribute")
self.paramList.InsertColumn(1, "Current Value")
self.paramList.SetColumnWidth(0, 110)
self.paramList.SetColumnWidth(1, 1500)
self.paramList.setResizeColumn(0)
if self.stuff:
names = dir(self.stuff)
else:
names = dir(self.item)
names = [a for a in names if not (a.startswith('__') and a.endswith('__'))]
idNameMap = {}
idCount = 0
for name in names:
try:
if self.stuff:
attrName = name.title()
value = getattr(self.stuff, name)
else:
attrName = name.title()
value = getattr(self.item, name)
except Exception as e:
# TODO: Add logging to this.
# We couldn't get a property for some reason. Skip it for now.
print(e)
continue
index = self.paramList.InsertStringItem(sys.maxint, attrName)
# index = self.paramList.InsertImageStringItem(sys.maxint, attrName)
idNameMap[idCount] = attrName
self.paramList.SetItemData(index, idCount)
idCount += 1
valueUnit = str(value)
self.paramList.SetStringItem(index, 1, valueUnit)
self.paramList.SortItems(lambda id1, id2: cmp(idNameMap[id1], idNameMap[id2]))
self.paramList.RefreshRows()
self.totalAttrsLabel.SetLabel("%d attributes. " % idCount)
self.Layout()

View File

@@ -19,7 +19,7 @@
import sys import sys
import os.path import os.path
import logging from logbook import Logger
import sqlalchemy import sqlalchemy
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
@@ -94,7 +94,7 @@ except ImportError as e:
print("Error loading Attribute Editor: %s.\nAccess to Attribute Editor is disabled." % e.message) print("Error loading Attribute Editor: %s.\nAccess to Attribute Editor is disabled." % e.message)
disableOverrideEditor = True disableOverrideEditor = True
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
# dummy panel(no paint no erasebk) # dummy panel(no paint no erasebk)
@@ -145,6 +145,7 @@ class MainFrame(wx.Frame):
return cls.__instance if cls.__instance is not None else MainFrame() return cls.__instance if cls.__instance is not None else MainFrame()
def __init__(self, title="pyfa"): def __init__(self, title="pyfa"):
pyfalog.debug("Initialize MainFrame")
self.title = title self.title = title
wx.Frame.__init__(self, None, wx.ID_ANY, self.title) wx.Frame.__init__(self, None, wx.ID_ANY, self.title)
@@ -399,7 +400,7 @@ class MainFrame(wx.Frame):
try: try:
dlg.Destroy() dlg.Destroy()
except PyDeadObjectError: except PyDeadObjectError:
logger.error("Tried to destroy an object that doesn't exist in <showDamagePatternEditor>.") pyfalog.error("Tried to destroy an object that doesn't exist in <showDamagePatternEditor>.")
def showImplantSetEditor(self, event): def showImplantSetEditor(self, event):
ImplantSetEditorDlg(self) ImplantSetEditorDlg(self)
@@ -427,7 +428,7 @@ class MainFrame(wx.Frame):
try: try:
dlg.Destroy() dlg.Destroy()
except PyDeadObjectError: except PyDeadObjectError:
logger.error("Tried to destroy an object that doesn't exist in <showExportDialog>.") pyfalog.error("Tried to destroy an object that doesn't exist in <showExportDialog>.")
return return
with open(path, "w", encoding="utf-8") as openfile: with open(path, "w", encoding="utf-8") as openfile:
@@ -437,7 +438,7 @@ class MainFrame(wx.Frame):
try: try:
dlg.Destroy() dlg.Destroy()
except PyDeadObjectError: except PyDeadObjectError:
logger.error("Tried to destroy an object that doesn't exist in <showExportDialog>.") pyfalog.error("Tried to destroy an object that doesn't exist in <showExportDialog>.")
def showPreferenceDialog(self, event): def showPreferenceDialog(self, event):
dlg = PreferenceDialog(self) dlg = PreferenceDialog(self)
@@ -734,7 +735,7 @@ class MainFrame(wx.Frame):
try: try:
fits = Port().importFitFromBuffer(clipboard, self.getActiveFit()) fits = Port().importFitFromBuffer(clipboard, self.getActiveFit())
except: except:
logger.error("Attempt to import failed:\n%s", clipboard) pyfalog.error("Attempt to import failed:\n{0}", clipboard)
else: else:
self._openAfterImport(fits) self._openAfterImport(fits)
@@ -754,7 +755,7 @@ class MainFrame(wx.Frame):
try: try:
dlg.Destroy() dlg.Destroy()
except PyDeadObjectError: except PyDeadObjectError:
logger.error("Tried to destroy an object that doesn't exist in <exportToClipboard>.") pyfalog.error("Tried to destroy an object that doesn't exist in <exportToClipboard>.")
def exportSkillsNeeded(self, event): def exportSkillsNeeded(self, event):
""" Exports skills needed for active fit and active character """ """ Exports skills needed for active fit and active character """
@@ -811,7 +812,7 @@ class MainFrame(wx.Frame):
try: try:
dlg.Destroy() dlg.Destroy()
except PyDeadObjectError: except PyDeadObjectError:
logger.error("Tried to destroy an object that doesn't exist in <fileImportDialog>.") pyfalog.error("Tried to destroy an object that doesn't exist in <fileImportDialog>.")
def backupToXml(self, event): def backupToXml(self, event):
""" Back up all fits to EVE XML file """ """ Back up all fits to EVE XML file """

View File

@@ -26,6 +26,9 @@ import gui.graphFrame
import gui.globalEvents as GE import gui.globalEvents as GE
from gui.bitmapLoader import BitmapLoader from gui.bitmapLoader import BitmapLoader
from logbook import Logger
pyfalog = Logger(__name__)
if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)): if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)):
from service.crest import Crest from service.crest import Crest
from service.crest import CrestModes from service.crest import CrestModes
@@ -33,6 +36,7 @@ if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION
class MainMenuBar(wx.MenuBar): class MainMenuBar(wx.MenuBar):
def __init__(self, mainFrame): def __init__(self, mainFrame):
pyfalog.debug("Initialize MainMenuBar")
self.characterEditorId = wx.NewId() self.characterEditorId = wx.NewId()
self.damagePatternEditorId = wx.NewId() self.damagePatternEditorId = wx.NewId()
self.targetResistsEditorId = wx.NewId() self.targetResistsEditorId = wx.NewId()
@@ -166,6 +170,7 @@ class MainMenuBar(wx.MenuBar):
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
def fitChanged(self, event): def fitChanged(self, event):
pyfalog.debug("fitChanged triggered")
enable = event.fitID is not None enable = event.fitID is not None
self.Enable(wx.ID_SAVEAS, enable) self.Enable(wx.ID_SAVEAS, enable)
self.Enable(wx.ID_COPY, enable) self.Enable(wx.ID_COPY, enable)

View File

@@ -26,6 +26,9 @@ import gui.PFSearchBox as SBox
from gui.cachingImageList import CachingImageList from gui.cachingImageList import CachingImageList
from gui.contextMenu import ContextMenu from gui.contextMenu import ContextMenu
from gui.bitmapLoader import BitmapLoader from gui.bitmapLoader import BitmapLoader
from logbook import Logger
pyfalog = Logger(__name__)
ItemSelected, ITEM_SELECTED = wx.lib.newevent.NewEvent() ItemSelected, ITEM_SELECTED = wx.lib.newevent.NewEvent()
@@ -56,6 +59,7 @@ class MetaButton(wx.ToggleButton):
class MarketBrowser(wx.Panel): class MarketBrowser(wx.Panel):
def __init__(self, parent): def __init__(self, parent):
wx.Panel.__init__(self, parent) wx.Panel.__init__(self, parent)
pyfalog.debug("Initialize marketBrowser")
vbox = wx.BoxSizer(wx.VERTICAL) vbox = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(vbox) self.SetSizer(vbox)
@@ -134,6 +138,7 @@ class SearchBox(SBox.PFSearchBox):
class MarketTree(wx.TreeCtrl): class MarketTree(wx.TreeCtrl):
def __init__(self, parent, marketBrowser): def __init__(self, parent, marketBrowser):
wx.TreeCtrl.__init__(self, parent, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT) wx.TreeCtrl.__init__(self, parent, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT)
pyfalog.debug("Initialize marketTree")
self.root = self.AddRoot("root") self.root = self.AddRoot("root")
self.imageList = CachingImageList(16, 16) self.imageList = CachingImageList(16, 16)
@@ -182,7 +187,9 @@ class MarketTree(wx.TreeCtrl):
iconId = self.addImage(sMkt.getIconByMarketGroup(childMktGrp)) iconId = self.addImage(sMkt.getIconByMarketGroup(childMktGrp))
try: try:
childId = self.AppendItem(root, childMktGrp.name, iconId, data=wx.TreeItemData(childMktGrp.ID)) childId = self.AppendItem(root, childMktGrp.name, iconId, data=wx.TreeItemData(childMktGrp.ID))
except: except Exception as e:
pyfalog.debug("Error appending item.")
pyfalog.debug(e)
continue continue
if sMkt.marketGroupHasTypesCheck(childMktGrp) is False: if sMkt.marketGroupHasTypesCheck(childMktGrp) is False:
self.AppendItem(childId, "dummy") self.AppendItem(childId, "dummy")
@@ -226,6 +233,7 @@ class ItemView(Display):
def __init__(self, parent, marketBrowser): def __init__(self, parent, marketBrowser):
Display.__init__(self, parent) Display.__init__(self, parent)
pyfalog.debug("Initialize ItemView")
marketBrowser.Bind(wx.EVT_TREE_SEL_CHANGED, self.selectionMade) marketBrowser.Bind(wx.EVT_TREE_SEL_CHANGED, self.selectionMade)
self.unfilteredStore = set() self.unfilteredStore = set()
@@ -252,6 +260,7 @@ class ItemView(Display):
self.metaMap = self.makeReverseMetaMap() self.metaMap = self.makeReverseMetaMap()
# Fill up recently used modules set # Fill up recently used modules set
pyfalog.debug("Fill up recently used modules set")
for itemID in self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]: for itemID in self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]:
self.recentlyUsedModules.add(self.sMkt.getItem(itemID)) self.recentlyUsedModules.add(self.sMkt.getItem(itemID))

View File

@@ -25,6 +25,9 @@ from wx.lib.intctrl import IntCtrl
from gui.utils.clipboard import toClipboard, fromClipboard from gui.utils.clipboard import toClipboard, fromClipboard
from gui.builtinViews.entityEditor import EntityEditor, BaseValidator from gui.builtinViews.entityEditor import EntityEditor, BaseValidator
from service.damagePattern import DamagePattern, ImportError from service.damagePattern import DamagePattern, ImportError
from logbook import Logger
pyfalog = Logger(__name__)
class DmgPatternTextValidor(BaseValidator): class DmgPatternTextValidor(BaseValidator):
@@ -47,6 +50,7 @@ class DmgPatternTextValidor(BaseValidator):
return True return True
except ValueError as e: except ValueError as e:
pyfalog.error(e)
wx.MessageBox(u"{}".format(e), "Error") wx.MessageBox(u"{}".format(e), "Error")
textCtrl.SetFocus() textCtrl.SetFocus()
return False return False
@@ -256,9 +260,13 @@ class DmgPatternEditorDlg(wx.Dialog):
sDP.importPatterns(text) sDP.importPatterns(text)
self.stNotice.SetLabel("Patterns successfully imported from clipboard") self.stNotice.SetLabel("Patterns successfully imported from clipboard")
except ImportError as e: except ImportError as e:
pyfalog.error(e)
self.stNotice.SetLabel(str(e)) self.stNotice.SetLabel(str(e))
except Exception: except Exception as e:
self.stNotice.SetLabel("Could not import from clipboard: unknown errors") msg = "Could not import from clipboard: unknown errors"
pyfalog.warning(msg)
pyfalog.error(e)
self.stNotice.SetLabel(msg)
finally: finally:
self.entityEditor.refreshEntityList() self.entityEditor.refreshEntityList()
else: else:

View File

@@ -66,12 +66,12 @@ class PreferenceDialog(wx.Dialog):
# Set the height based on a condition. Can all the panels fit in the current height? # Set the height based on a condition. Can all the panels fit in the current height?
# If not, use the .GetBestVirtualSize() to ensure that all content is available. # If not, use the .GetBestVirtualSize() to ensure that all content is available.
minHeight = 360 minHeight = 550
bestFit = self.GetBestVirtualSize() bestFit = self.GetBestVirtualSize()
if minHeight > bestFit[1]: if minHeight > bestFit[1]:
self.SetSizeWH(450, minHeight) self.SetSizeWH(650, minHeight)
else: else:
self.SetSizeWH(450, bestFit[1]) self.SetSizeWH(650, bestFit[1])
self.Layout() self.Layout()

View File

@@ -44,5 +44,10 @@ from gui.builtinPreferenceViews import ( # noqa: E402, F401
pyfaNetworkPreferences, pyfaNetworkPreferences,
pyfaHTMLExportPreferences, pyfaHTMLExportPreferences,
pyfaCrestPreferences, pyfaCrestPreferences,
pyfaUpdatePreferences pyfaContextMenuPreferences,
pyfaStatViewPreferences,
pyfaUpdatePreferences,
pyfaEnginePreferences,
pyfaDatabasePreferences,
pyfaLoggingPreferences
) )

View File

@@ -1,5 +1,5 @@
import csv import csv
import logging from logbook import Logger
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
import wx import wx
@@ -21,7 +21,7 @@ import gui.PFSearchBox as SBox
from gui.marketBrowser import SearchBox from gui.marketBrowser import SearchBox
from gui.bitmapLoader import BitmapLoader from gui.bitmapLoader import BitmapLoader
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class AttributeEditor(wx.Frame): class AttributeEditor(wx.Frame):
@@ -270,7 +270,7 @@ class AttributeGrid(wxpg.PropertyGrid):
self.itemView.updateItems() self.itemView.updateItems()
logger.debug('%s changed to "%s"' % (p.GetName(), p.GetValueAsString())) pyfalog.debug('{0} changed to "{1}"', p.GetName(), p.GetValueAsString())
def OnPropGridSelect(self, event): def OnPropGridSelect(self, event):
pass pass

View File

@@ -23,6 +23,9 @@ from service.targetResists import TargetResists
from gui.bitmapLoader import BitmapLoader from gui.bitmapLoader import BitmapLoader
from gui.utils.clipboard import toClipboard, fromClipboard from gui.utils.clipboard import toClipboard, fromClipboard
from gui.builtinViews.entityEditor import EntityEditor, BaseValidator from gui.builtinViews.entityEditor import EntityEditor, BaseValidator
from logbook import Logger
pyfalog = Logger(__name__)
class TargetResistsTextValidor(BaseValidator): class TargetResistsTextValidor(BaseValidator):
@@ -45,6 +48,7 @@ class TargetResistsTextValidor(BaseValidator):
return True return True
except ValueError as e: except ValueError as e:
pyfalog.error(e)
wx.MessageBox(u"{}".format(e), "Error") wx.MessageBox(u"{}".format(e), "Error")
textCtrl.SetFocus() textCtrl.SetFocus()
return False return False
@@ -230,10 +234,14 @@ class ResistsEditorDlg(wx.Dialog):
except ValueError: except ValueError:
editObj.SetForegroundColour(wx.RED) editObj.SetForegroundColour(wx.RED)
self.stNotice.SetLabel("Incorrect Formatting (decimals only)") msg = "Incorrect Formatting (decimals only)"
pyfalog.warning(msg)
self.stNotice.SetLabel(msg)
except AssertionError: except AssertionError:
editObj.SetForegroundColour(wx.RED) editObj.SetForegroundColour(wx.RED)
self.stNotice.SetLabel("Incorrect Range (must be 0-100)") msg = "Incorrect Range (must be 0-100)"
pyfalog.warning(msg)
self.stNotice.SetLabel(msg)
finally: # Refresh for color changes to take effect immediately finally: # Refresh for color changes to take effect immediately
self.Refresh() self.Refresh()
@@ -271,9 +279,13 @@ class ResistsEditorDlg(wx.Dialog):
sTR.importPatterns(text) sTR.importPatterns(text)
self.stNotice.SetLabel("Patterns successfully imported from clipboard") self.stNotice.SetLabel("Patterns successfully imported from clipboard")
except ImportError as e: except ImportError as e:
pyfalog.error(e)
self.stNotice.SetLabel(str(e)) self.stNotice.SetLabel(str(e))
except Exception: except Exception as e:
self.stNotice.SetLabel("Could not import from clipboard: unknown errors") msg = "Could not import from clipboard:"
pyfalog.warning(msg)
pyfalog.error(e)
self.stNotice.SetLabel(msg)
finally: finally:
self.entityEditor.refreshEntityList() self.entityEditor.refreshEntityList()
else: else:

View File

@@ -17,7 +17,7 @@
# along with pyfa. If not, see <http://www.gnu.org/licenses/>. # along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# ============================================================================= # =============================================================================
import logging from logbook import Logger
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
import wx import wx
@@ -26,7 +26,7 @@ from gui.builtinViews.implantEditor import BaseImplantEditorView
from gui.utils.clipboard import toClipboard, fromClipboard from gui.utils.clipboard import toClipboard, fromClipboard
from gui.builtinViews.entityEditor import EntityEditor, BaseValidator from gui.builtinViews.entityEditor import EntityEditor, BaseValidator
logger = logging.getLogger(__name__) pyfalog = Logger(__name__)
class ImplantTextValidor(BaseValidator): class ImplantTextValidor(BaseValidator):
@@ -49,6 +49,7 @@ class ImplantTextValidor(BaseValidator):
return True return True
except ValueError as e: except ValueError as e:
pyfalog.error(e)
wx.MessageBox(u"{}".format(e), "Error") wx.MessageBox(u"{}".format(e), "Error")
textCtrl.SetFocus() textCtrl.SetFocus()
return False return False
@@ -198,9 +199,10 @@ class ImplantSetEditorDlg(wx.Dialog):
self.stNotice.SetLabel("Patterns successfully imported from clipboard") self.stNotice.SetLabel("Patterns successfully imported from clipboard")
self.showInput(False) self.showInput(False)
except ImportError as e: except ImportError as e:
pyfalog.error(e)
self.stNotice.SetLabel(str(e)) self.stNotice.SetLabel(str(e))
except Exception as e: except Exception as e:
logging.exception("Unhandled Exception") pyfalog.error(e)
self.stNotice.SetLabel("Could not import from clipboard: unknown errors") self.stNotice.SetLabel("Could not import from clipboard: unknown errors")
finally: finally:
self.updateChoices() self.updateChoices()

View File

@@ -21,6 +21,8 @@ import gui.utils.animEffects as animEffects
from gui.PFListPane import PFListPane from gui.PFListPane import PFListPane
from gui.contextMenu import ContextMenu from gui.contextMenu import ContextMenu
from gui.bitmapLoader import BitmapLoader from gui.bitmapLoader import BitmapLoader
from logbook import Logger
pyfalog = Logger(__name__)
FitRenamed, EVT_FIT_RENAMED = wx.lib.newevent.NewEvent() FitRenamed, EVT_FIT_RENAMED = wx.lib.newevent.NewEvent()
FitSelected, EVT_FIT_SELECTED = wx.lib.newevent.NewEvent() FitSelected, EVT_FIT_SELECTED = wx.lib.newevent.NewEvent()
@@ -684,6 +686,7 @@ class ShipBrowser(wx.Panel):
self.lpane.Freeze() self.lpane.Freeze()
self.lpane.RemoveAllChildren() self.lpane.RemoveAllChildren()
pyfalog.debug("Populate ship category list.")
if len(self.categoryList) == 0: if len(self.categoryList) == 0:
# set cache of category list # set cache of category list
self.categoryList = list(sMkt.getShipRoot()) self.categoryList = list(sMkt.getShipRoot())
@@ -692,7 +695,8 @@ class ShipBrowser(wx.Panel):
# set map & cache of fittings per category # set map & cache of fittings per category
for cat in self.categoryList: for cat in self.categoryList:
itemIDs = [x.ID for x in cat.items] itemIDs = [x.ID for x in cat.items]
self.categoryFitCache[cat.ID] = sFit.countFitsWithShip(itemIDs) > 1 num = sFit.countFitsWithShip(itemIDs)
self.categoryFitCache[cat.ID] = num > 0
for ship in self.categoryList: for ship in self.categoryList:
if self.filterShipsWithNoFits and not self.categoryFitCache[ship.ID]: if self.filterShipsWithNoFits and not self.categoryFitCache[ship.ID]:
@@ -938,7 +942,7 @@ class ShipBrowser(wx.Panel):
if fits: if fits:
for fit in fits: for fit in fits:
shipTrait = fit.ship.traits.traitText if (fit.ship.traits is not None) else "" shipTrait = fit.ship.item.traits.traitText if (fit.ship.item.traits is not None) else ""
# empty string if no traits # empty string if no traits
self.lpane.AddWidget(FitItem( self.lpane.AddWidget(FitItem(
@@ -1551,6 +1555,10 @@ class FitItem(SFItem.SFBrowserItem):
self.selTimer.Start(100) self.selTimer.Start(100)
self.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu) self.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu)
self.Bind(wx.EVT_MIDDLE_UP, self.OpenNewTab)
def OpenNewTab(self, evt):
self.selectFit(newTab=True)
def OnToggleBooster(self, event): def OnToggleBooster(self, event):
sFit = Fit.getInstance() sFit = Fit.getInstance()
@@ -1615,6 +1623,9 @@ class FitItem(SFItem.SFBrowserItem):
# menu.AppendSubMenu(boosterMenu, 'Set Booster') # menu.AppendSubMenu(boosterMenu, 'Set Booster')
if fit: if fit:
newTabItem = menu.Append(wx.ID_ANY, "Open in new tab")
self.Bind(wx.EVT_MENU, self.OpenNewTab, newTabItem)
projectedItem = menu.Append(wx.ID_ANY, "Project onto Active Fit") projectedItem = menu.Append(wx.ID_ANY, "Project onto Active Fit")
self.Bind(wx.EVT_MENU, self.OnProjectToFit, projectedItem) self.Bind(wx.EVT_MENU, self.OnProjectToFit, projectedItem)
@@ -1808,7 +1819,10 @@ class FitItem(SFItem.SFBrowserItem):
self.dragWindow.SetPosition(pos) self.dragWindow.SetPosition(pos)
return return
def selectFit(self, event=None): def selectFit(self, event=None, newTab=False):
if newTab:
wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fitID, startup=2))
else:
wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fitID)) wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fitID))
def RestoreEditButton(self): def RestoreEditButton(self):

View File

@@ -21,6 +21,7 @@
import wx import wx
from service.fit import Fit from service.fit import Fit
from service.settings import StatViewSettings
import gui.mainFrame import gui.mainFrame
import gui.builtinStatsViews import gui.builtinStatsViews
import gui.globalEvents as GE import gui.globalEvents as GE
@@ -28,12 +29,44 @@ import gui.globalEvents as GE
from gui.statsView import StatsView from gui.statsView import StatsView
from gui.contextMenu import ContextMenu from gui.contextMenu import ContextMenu
from gui.pyfatogglepanel import TogglePanel from gui.pyfatogglepanel import TogglePanel
from logbook import Logger
pyfalog = Logger(__name__)
class StatsPane(wx.Panel): class StatsPane(wx.Panel):
DEFAULT_VIEWS = ["resourcesViewFull", "resistancesViewFull", "rechargeViewFull", "firepowerViewFull", AVAILIBLE_VIEWS = [
"capacitorViewFull", "targetingmiscViewFull", "resources",
"priceViewFull"] "resistances",
"recharge",
"firepower",
"outgoing",
"capacitor",
"targetingMisc",
"price",
]
# Don't have these....yet....
'''
"miningyield", "drones"
]
'''
DEFAULT_VIEWS = []
settings = StatViewSettings.getInstance()
for aView in AVAILIBLE_VIEWS:
if settings.get(aView) == 2:
DEFAULT_VIEWS.extend(["%sViewFull" % aView])
pyfalog.debug("Setting full view for: {0}", aView)
elif settings.get(aView) == 1:
DEFAULT_VIEWS.extend(["%sViewMinimal" % aView])
pyfalog.debug("Setting minimal view for: {0}", aView)
elif settings.get(aView) == 0:
pyfalog.debug("Setting disabled view for: {0}", aView)
else:
pyfalog.error("Unknown setting for view: {0}", aView)
def fitChanged(self, event): def fitChanged(self, event):
sFit = Fit.getInstance() sFit = Fit.getInstance()
@@ -66,7 +99,12 @@ class StatsPane(wx.Panel):
contentPanel = tp.GetContentPane() contentPanel = tp.GetContentPane()
contentPanel.viewName = viewName contentPanel.viewName = viewName
try:
view = StatsView.getView(viewName)(self) view = StatsView.getView(viewName)(self)
pyfalog.debug("Load view: {0}", viewName)
except KeyError:
pyfalog.error("Attempted to load an invalid view: {0}", viewName)
self.nameViewMap[viewName] = view self.nameViewMap[viewName] = view
self.views.append(view) self.views.append(view)

View File

@@ -50,6 +50,8 @@ from gui.builtinStatsViews import ( # noqa: E402, F401
miningyieldViewFull, miningyieldViewFull,
capacitorViewFull, capacitorViewFull,
rechargeViewFull, rechargeViewFull,
targetingMiscViewFull, targetingMiscViewMinimal,
priceViewFull, priceViewFull,
outgoingViewFull,
outgoingViewMinimal,
) )

View File

@@ -4,7 +4,12 @@ import time
import wx import wx
from service.settings import HTMLExportSettings from service.settings import HTMLExportSettings
from service.fit import Fit from service.fit import Fit
from service.port import Port
from service.market import Market from service.market import Market
from logbook import Logger
from eos.db import getFit
pyfalog = Logger(__name__)
class exportHtml(object): class exportHtml(object):
@@ -184,6 +189,7 @@ class exportHtmlThread(threading.Thread):
groupFits = 0 groupFits = 0
for ship in ships: for ship in ships:
fits = sFit.getFitsWithShip(ship.ID) fits = sFit.getFitsWithShip(ship.ID)
if len(fits) > 0: if len(fits) > 0:
groupFits += len(fits) groupFits += len(fits)
@@ -192,10 +198,11 @@ class exportHtmlThread(threading.Thread):
return return
fit = fits[0] fit = fits[0]
try: try:
dnaFit = sFit.exportDna(fit[0]) dnaFit = Port.exportDna(getFit(fit[0]))
HTMLgroup += ' <li><a data-dna="' + dnaFit + '" target="_blank">' + ship.name + ": " + \ HTMLgroup += ' <li><a data-dna="' + dnaFit + '" target="_blank">' + ship.name + ": " + \
fit[1] + '</a></li>\n' fit[1] + '</a></li>\n'
except: except:
pyfalog.warning("Failed to export line")
pass pass
finally: finally:
if self.callback: if self.callback:
@@ -214,10 +221,12 @@ class exportHtmlThread(threading.Thread):
if self.stopRunning: if self.stopRunning:
return return
try: try:
dnaFit = sFit.exportDna(fit[0]) dnaFit = Port.exportDna(getFit(fit[0]))
print dnaFit
HTMLship += ' <li><a data-dna="' + dnaFit + '" target="_blank">' + fit[ HTMLship += ' <li><a data-dna="' + dnaFit + '" target="_blank">' + fit[
1] + '</a></li>\n' 1] + '</a></li>\n'
except: except:
pyfalog.warning("Failed to export line")
continue continue
finally: finally:
if self.callback: if self.callback:
@@ -266,10 +275,11 @@ class exportHtmlThread(threading.Thread):
if self.stopRunning: if self.stopRunning:
return return
try: try:
dnaFit = sFit.exportDna(fit[0]) dnaFit = Port.exportDna(getFit(fit[0]))
HTML += '<a class="outOfGameBrowserLink" target="_blank" href="' + dnaUrl + dnaFit + '">' + ship.name + ': ' + \ HTML += '<a class="outOfGameBrowserLink" target="_blank" href="' + dnaUrl + dnaFit + '">' + ship.name + ': ' + \
fit[1] + '</a><br> \n' fit[1] + '</a><br> \n'
except: except:
pyfalog.error("Failed to export line")
continue continue
finally: finally:
if self.callback: if self.callback:

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
imgs/gui/settings_log.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
imgs/gui/settings_menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
imgs/gui/settings_stats.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

167
pyfa.py
View File

@@ -19,11 +19,18 @@
# ============================================================================== # ==============================================================================
import sys import sys
import os
import os.path
import re import re
import config import config
from optparse import OptionParser, BadOptionError, AmbiguousOptionError from optparse import OptionParser, BadOptionError, AmbiguousOptionError
import logbook
from logbook import TimedRotatingFileHandler, Logger, StreamHandler, NestedSetup, FingersCrossedHandler, NullHandler, \
CRITICAL, ERROR, WARNING, DEBUG, INFO
pyfalog = Logger(__name__)
class PassThroughOptionParser(OptionParser): class PassThroughOptionParser(OptionParser):
""" """
@@ -36,9 +43,30 @@ class PassThroughOptionParser(OptionParser):
try: try:
OptionParser._process_args(self, largs, rargs, values) OptionParser._process_args(self, largs, rargs, values)
except (BadOptionError, AmbiguousOptionError) as e: except (BadOptionError, AmbiguousOptionError) as e:
pyfalog.error("Bad startup option passed.")
largs.append(e.opt_str) largs.append(e.opt_str)
class LoggerWriter:
def __init__(self, level):
# self.level is really like using log.debug(message)
# at least in my case
self.level = level
def write(self, message):
# if statement reduces the amount of newlines that are
# printed to the logger
if message not in {'\n', ' '}:
self.level(message.replace("\n", ""))
def flush(self):
# create a flush method so things can be flushed when
# the system wants to. Not sure if simply 'printing'
# sys.stderr is the correct way to do it, but it seemed
# to work properly for me.
self.level(sys.stderr)
# Parse command line options # Parse command line options
usage = "usage: %prog [--root]" usage = "usage: %prog [--root]"
parser = PassThroughOptionParser(usage=usage) parser = PassThroughOptionParser(usage=usage)
@@ -47,9 +75,23 @@ parser.add_option("-w", "--wx28", action="store_true", dest="force28", help="For
parser.add_option("-d", "--debug", action="store_true", dest="debug", help="Set logger to debug level.", default=False) parser.add_option("-d", "--debug", action="store_true", dest="debug", help="Set logger to debug level.", default=False)
parser.add_option("-t", "--title", action="store", dest="title", help="Set Window Title", default=None) parser.add_option("-t", "--title", action="store", dest="title", help="Set Window Title", default=None)
parser.add_option("-s", "--savepath", action="store", dest="savepath", help="Set the folder for savedata", default=None) parser.add_option("-s", "--savepath", action="store", dest="savepath", help="Set the folder for savedata", default=None)
parser.add_option("-l", "--logginglevel", action="store", dest="logginglevel", help="Set desired logging level [Critical|Error|Warning|Info|Debug]", default="Error")
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
if options.logginglevel == "Critical":
options.logginglevel = CRITICAL
elif options.logginglevel == "Error":
options.logginglevel = ERROR
elif options.logginglevel == "Warning":
options.logginglevel = WARNING
elif options.logginglevel == "Info":
options.logginglevel = INFO
elif options.logginglevel == "Debug":
options.logginglevel = DEBUG
else:
options.logginglevel = ERROR
if not hasattr(sys, 'frozen'): if not hasattr(sys, 'frozen'):
if sys.version_info < (2, 6) or sys.version_info > (3, 0): if sys.version_info < (2, 6) or sys.version_info > (3, 0):
@@ -103,6 +145,10 @@ if not hasattr(sys, 'frozen'):
print("Cannot find python-dateutil.\nYou can download python-dateutil from https://pypi.python.org/pypi/python-dateutil") print("Cannot find python-dateutil.\nYou can download python-dateutil from https://pypi.python.org/pypi/python-dateutil")
sys.exit(1) sys.exit(1)
logVersion = logbook.__version__.split('.')
if int(logVersion[0]) < 1:
print ("Logbook version >= 1.0.0 is recommended. You may have some performance issues by continuing to use an earlier version.")
if __name__ == "__main__": if __name__ == "__main__":
# Configure paths # Configure paths
@@ -114,14 +160,6 @@ if __name__ == "__main__":
options.title = "pyfa %s%s - Python Fitting Assistant" % (config.version, "" if config.tag.lower() != 'git' else " (git)") options.title = "pyfa %s%s - Python Fitting Assistant" % (config.version, "" if config.tag.lower() != 'git' else " (git)")
config.debug = options.debug config.debug = options.debug
# convert to unicode if it is set
if options.savepath is not None:
options.savepath = unicode(options.savepath)
config.defPaths(options.savepath)
# Basic logging initialization
import logging
logging.basicConfig()
# Import everything # Import everything
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
@@ -129,10 +167,86 @@ if __name__ == "__main__":
import os import os
import os.path import os.path
try:
# convert to unicode if it is set
if options.savepath is not None:
options.savepath = unicode(options.savepath)
config.defPaths(options.savepath)
# Basic logging initialization
# Logging levels:
'''
logbook.CRITICAL
logbook.ERROR
logbook.WARNING
logbook.INFO
logbook.DEBUG
logbook.NOTSET
'''
if options.debug:
savePath_filename = "Pyfa_debug.log"
else:
savePath_filename = "Pyfa.log"
savePath_Destination = os.path.join(config.savePath, savePath_filename)
try:
if options.debug:
logging_mode = "Debug"
logging_setup = NestedSetup([
# make sure we never bubble up to the stderr handler
# if we run out of setup handling
NullHandler(),
StreamHandler(
sys.stdout,
bubble=False,
level=options.logginglevel
),
TimedRotatingFileHandler(
savePath_Destination,
level=0,
backup_count=3,
bubble=True,
date_format='%Y-%m-%d',
),
])
else:
logging_mode = "User"
logging_setup = NestedSetup([
# make sure we never bubble up to the stderr handler
# if we run out of setup handling
NullHandler(),
FingersCrossedHandler(
TimedRotatingFileHandler(
savePath_Destination,
level=0,
backup_count=3,
bubble=False,
date_format='%Y-%m-%d',
),
action_level=ERROR,
buffer_size=1000,
# pull_information=True,
# reset=False,
)
])
except:
logging_mode = "Console Only"
logging_setup = NestedSetup([
# make sure we never bubble up to the stderr handler
# if we run out of setup handling
NullHandler(),
StreamHandler(
sys.stdout,
bubble=False
)
])
import eos.db import eos.db
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import service.prefetch # noqa: F401 import service.prefetch # noqa: F401
from gui.mainFrame import MainFrame
# Make sure the saveddata db exists # Make sure the saveddata db exists
if not os.path.exists(config.savePath): if not os.path.exists(config.savePath):
@@ -140,6 +254,41 @@ if __name__ == "__main__":
eos.db.saveddata_meta.create_all() eos.db.saveddata_meta.create_all()
except Exception, e:
import traceback
from gui.errorDialog import ErrorFrame
tb = traceback.format_exc()
pyfa = wx.App(False)
ErrorFrame(e, tb)
pyfa.MainLoop()
sys.exit()
with logging_setup.threadbound():
# Don't redirect if frozen
if not hasattr(sys, 'frozen'):
# Output all stdout (print) messages as warnings
try:
sys.stdout = LoggerWriter(pyfalog.warning)
except ValueError, Exception:
pyfalog.critical("Cannot access log file. Continuing without writing stdout to log.")
if not options.debug:
# Output all stderr (stacktrace) messages as critical
try:
sys.stderr = LoggerWriter(pyfalog.critical)
except ValueError, Exception:
pyfalog.critical("Cannot access log file. Continuing without writing stderr to log.")
pyfalog.info("Starting Pyfa")
pyfalog.info("Running in logging mode: {0}", logging_mode)
if hasattr(sys, 'frozen') and options.debug:
pyfalog.critical("Running in frozen mode with debug turned on. Forcing all output to be written to log.")
from gui.mainFrame import MainFrame
pyfa = wx.App(False) pyfa = wx.App(False)
MainFrame(options.title) MainFrame(options.title)
pyfa.MainLoop() pyfa.MainLoop()

83
pyfa.spec Normal file
View File

@@ -0,0 +1,83 @@
# -*- mode: python -*-
# Note: This script is provided AS-IS for those that may be interested.
# pyfa does not currently support pyInstaller (or any other build process) 100% at the moment
# Command line to build:
# (Run from directory where pyfa.py and pyfa.spec lives.)
# c:\Python27\scripts\pyinstaller.exe --clean --noconfirm --windowed --upx-dir=.\scripts\upx.exe pyfa.spec
# Don't forget to change the path to where your pyfa.py and pyfa.spec lives
# pathex=['C:\\Users\\Ebag333\\Documents\\GitHub\\Ebag333\\Pyfa'],
import os
block_cipher = None
added_files = [
( 'imgs/gui/*.png', 'imgs/gui' ),
( 'imgs/gui/*.gif', 'imgs/gui' ),
( 'imgs/icons/*.png', 'imgs/icons' ),
( 'imgs/renders/*.png', 'imgs/renders' ),
( 'dist_assets/win/pyfa.ico', '.' ),
( 'dist_assets/cacert.pem', '.' ),
( 'eve.db', '.' ),
( 'README.md', '.' ),
( 'LICENSE', '.' ),
]
import_these = []
# Walk eos.effects and add all effects so we can import them properly
for root, folders, files in os.walk("eos/effects"):
for file_ in files:
if file_.endswith(".py") and not file_.startswith("_"):
mod_name = "{}.{}".format(
root.replace("/", "."),
file_.split(".py")[0],
)
import_these.append(mod_name)
a = Analysis(
['pyfa.py'],
pathex=['C:\\Users\\Ebag333\\Documents\\GitHub\\Ebag333\\Pyfa'],
binaries=[],
datas=added_files,
hiddenimports=import_these,
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
)
pyz = PYZ(
a.pure,
a.zipped_data,
cipher=block_cipher,
)
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
debug=False,
console=False,
strip=False,
upx=True,
name='pyfa',
icon='dist_assets/win/pyfa.ico',
onefile=False,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
onefile=False,
name='pyfa',
icon='dist_assets/win/pyfa.ico',
)

View File

@@ -1,3 +1,4 @@
logbook>=1.0.0
matplotlib matplotlib
PyYAML PyYAML
python-dateutil python-dateutil

View File

@@ -0,0 +1,9 @@
PyInstaller >= 3.2.1
cycler >= 0.10.0
functools32 >= 3.2.3
future >= 0.16.0
numpy >= 1.12.
pyparsing >= 2.1.10
pypiwin32 >= 219
pytz >= 2016.10
six >= 1.10.0

View File

@@ -0,0 +1,8 @@
PyInstaller >= 3.2.1
cycler >= 0.10.0
functools32 >= 3.2.3
future >= 0.16.0
numpy >= 1.12.
pyparsing >= 2.1.10
pytz >= 2016.10
six

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