mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-18 18:07:37 +03:00
Merge branch 'develop' into enhancement/2188/translation-override
This commit is contained in:
commit
4e802b46df
1
.flake8
1
.flake8
@ -7,7 +7,6 @@ exclude =
|
|||||||
FDevIDs/
|
FDevIDs/
|
||||||
venv/
|
venv/
|
||||||
.venv/
|
.venv/
|
||||||
hotkey/darwin.py # FIXME: Check under macOS VM at some point
|
|
||||||
|
|
||||||
# Show exactly where in a line the error happened
|
# Show exactly where in a line the error happened
|
||||||
#show-source = True
|
#show-source = True
|
||||||
|
2
.github/workflows/windows-build.yml
vendored
2
.github/workflows/windows-build.yml
vendored
@ -155,7 +155,7 @@ jobs:
|
|||||||
run: sha256sum EDMarketConnector_Installer_*.exe EDMarketConnector-release-*.{zip,tar.gz} > ./hashes.sum
|
run: sha256sum EDMarketConnector_Installer_*.exe EDMarketConnector-release-*.{zip,tar.gz} > ./hashes.sum
|
||||||
|
|
||||||
- name: Create Draft Release
|
- name: Create Draft Release
|
||||||
uses: "softprops/action-gh-release@v1"
|
uses: "softprops/action-gh-release@v2"
|
||||||
with:
|
with:
|
||||||
token: "${{secrets.GITHUB_TOKEN}}"
|
token: "${{secrets.GITHUB_TOKEN}}"
|
||||||
draft: true
|
draft: true
|
||||||
|
@ -6,5 +6,4 @@ scripts_are_modules = True
|
|||||||
; `<var> = <value>`
|
; `<var> = <value>`
|
||||||
; i.e. no typing info.
|
; i.e. no typing info.
|
||||||
check_untyped_defs = True
|
check_untyped_defs = True
|
||||||
; platform = darwin
|
|
||||||
explicit_package_bases = True
|
explicit_package_bases = True
|
||||||
|
24
ChangeLog.md
24
ChangeLog.md
@ -6,6 +6,30 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are
|
|||||||
in the source (not distributed with the Windows installer) for the
|
in the source (not distributed with the Windows installer) for the
|
||||||
currently used version.
|
currently used version.
|
||||||
---
|
---
|
||||||
|
Release 5.10.4
|
||||||
|
===
|
||||||
|
This release contains updated dependencies, modules files, translations, and adds two new EDDN schemas. It also
|
||||||
|
adds Turkish translations to EDMC!
|
||||||
|
|
||||||
|
We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines.
|
||||||
|
For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC
|
||||||
|
|
||||||
|
**Changes and Enhancements**
|
||||||
|
* Adds Turkish Translations to EDMC
|
||||||
|
* Adds DockingDenied and DockingGranted EDDN Schemas
|
||||||
|
* Updated FDevIDs Dependency
|
||||||
|
* Updated Translations
|
||||||
|
* Updated modules files to process several missing module types used for bug squishing or going fast
|
||||||
|
* Updated Python Dependencies
|
||||||
|
|
||||||
|
**Bug Fixes**
|
||||||
|
* Fixed a bug on older Python versions which couldn't import updated type annotations
|
||||||
|
|
||||||
|
**Plugin Developers**
|
||||||
|
* modules.p and ships.p are deprecated, and slated for removal in 5.11+!
|
||||||
|
* The `openurl()` function in ttkHyperlinkLabel has been deprecated,
|
||||||
|
and slated for removal in 5.11+! Please migrate to `webbrowser.open()`.
|
||||||
|
|
||||||
Release 5.10.3
|
Release 5.10.3
|
||||||
===
|
===
|
||||||
This release contains a bugfix for the shipyard outfitting parsing system and an update to the French translations.
|
This release contains a bugfix for the shipyard outfitting parsing system and an update to the French translations.
|
||||||
|
@ -679,7 +679,7 @@ the following does not work:
|
|||||||
|
|
||||||
```py
|
```py
|
||||||
from sys import platform
|
from sys import platform
|
||||||
if platform == 'darwin':
|
if platform == 'win32':
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -496,21 +496,20 @@ class AppWindow:
|
|||||||
|
|
||||||
plug.load_plugins(master)
|
plug.load_plugins(master)
|
||||||
|
|
||||||
if sys.platform != 'darwin':
|
if sys.platform == 'win32':
|
||||||
if sys.platform == 'win32':
|
self.w.wm_iconbitmap(default='EDMarketConnector.ico')
|
||||||
self.w.wm_iconbitmap(default='EDMarketConnector.ico')
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.w.tk.call('wm', 'iconphoto', self.w, '-default',
|
self.w.tk.call('wm', 'iconphoto', self.w, '-default',
|
||||||
tk.PhotoImage(file=path.join(config.respath_path, 'io.edcd.EDMarketConnector.png')))
|
tk.PhotoImage(file=path.join(config.respath_path, 'io.edcd.EDMarketConnector.png')))
|
||||||
|
|
||||||
# TODO: Export to files and merge from them in future ?
|
# TODO: Export to files and merge from them in future ?
|
||||||
self.theme_icon = tk.PhotoImage(
|
self.theme_icon = tk.PhotoImage(
|
||||||
data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==') # noqa: E501
|
data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==') # noqa: E501
|
||||||
self.theme_minimize = tk.BitmapImage(
|
self.theme_minimize = tk.BitmapImage(
|
||||||
data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501
|
data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501
|
||||||
self.theme_close = tk.BitmapImage(
|
self.theme_close = tk.BitmapImage(
|
||||||
data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n 0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n 0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501
|
data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n 0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n 0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501
|
||||||
|
|
||||||
frame = tk.Frame(self.w, name=appname.lower())
|
frame = tk.Frame(self.w, name=appname.lower())
|
||||||
frame.grid(sticky=tk.NSEW)
|
frame.grid(sticky=tk.NSEW)
|
||||||
@ -599,7 +598,7 @@ class AppWindow:
|
|||||||
self.theme_button = tk.Label(
|
self.theme_button = tk.Label(
|
||||||
frame,
|
frame,
|
||||||
name='themed_update_button',
|
name='themed_update_button',
|
||||||
width=32 if sys.platform == 'darwin' else 28,
|
width=28,
|
||||||
state=tk.DISABLED
|
state=tk.DISABLED
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -633,148 +632,104 @@ class AppWindow:
|
|||||||
self.updater = update.Updater(tkroot=self.w)
|
self.updater = update.Updater(tkroot=self.w)
|
||||||
self.updater.check_for_updates() # Sparkle / WinSparkle does this automatically for packaged apps
|
self.updater.check_for_updates() # Sparkle / WinSparkle does this automatically for packaged apps
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
|
||||||
# Can't handle (de)iconify if topmost is set, so suppress iconify button
|
self.file_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status))
|
||||||
# http://wiki.tcl.tk/13428 and p15 of
|
self.file_menu.add_command(command=self.save_raw)
|
||||||
# https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/HandlingWindowsControls/windowscontrols.pdf
|
self.file_menu.add_command(command=lambda: prefs.PreferencesDialog(self.w, self.postprefs))
|
||||||
root.call('tk::unsupported::MacWindowStyle', 'style', root, 'document', 'closeBox resizable')
|
self.file_menu.add_separator()
|
||||||
|
self.file_menu.add_command(command=self.onexit)
|
||||||
|
self.menubar.add_cascade(menu=self.file_menu)
|
||||||
|
self.edit_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
|
||||||
|
self.edit_menu.add_command(accelerator='Ctrl+C', state=tk.DISABLED, command=self.copy)
|
||||||
|
self.menubar.add_cascade(menu=self.edit_menu)
|
||||||
|
self.help_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) # type: ignore
|
||||||
|
self.help_menu.add_command(command=self.help_general) # Documentation
|
||||||
|
self.help_menu.add_command(command=self.help_troubleshooting) # Troubleshooting
|
||||||
|
self.help_menu.add_command(command=self.help_report_a_bug) # Report A Bug
|
||||||
|
self.help_menu.add_command(command=self.help_privacy) # Privacy Policy
|
||||||
|
self.help_menu.add_command(command=self.help_releases) # Release Notes
|
||||||
|
self.help_menu.add_command(command=lambda: self.updater.check_for_updates()) # Check for Updates...
|
||||||
|
# About E:D Market Connector
|
||||||
|
self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w))
|
||||||
|
self.help_menu.add_command(command=prefs.help_open_log_folder) # Open Log Folder
|
||||||
|
|
||||||
# https://www.tcl.tk/man/tcl/TkCmd/menu.htm
|
self.menubar.add_cascade(menu=self.help_menu)
|
||||||
self.system_menu = tk.Menu(self.menubar, name='apple')
|
if sys.platform == 'win32':
|
||||||
self.system_menu.add_command(command=lambda: self.w.call('tk::mac::standardAboutPanel'))
|
# Must be added after at least one "real" menu entry
|
||||||
self.system_menu.add_command(command=lambda: self.updater.check_for_updates())
|
self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop')))
|
||||||
|
self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE)
|
||||||
|
self.system_menu.add_separator()
|
||||||
|
# LANG: Appearance - Label for checkbox to select if application always on top
|
||||||
|
self.system_menu.add_checkbutton(label=_('Always on top'),
|
||||||
|
variable=self.always_ontop,
|
||||||
|
command=self.ontop_changed) # Appearance setting
|
||||||
self.menubar.add_cascade(menu=self.system_menu)
|
self.menubar.add_cascade(menu=self.system_menu)
|
||||||
self.file_menu = tk.Menu(self.menubar, name='file')
|
self.w.bind('<Control-c>', self.copy)
|
||||||
self.file_menu.add_command(command=self.save_raw)
|
|
||||||
self.menubar.add_cascade(menu=self.file_menu)
|
|
||||||
self.edit_menu = tk.Menu(self.menubar, name='edit')
|
|
||||||
self.edit_menu.add_command(accelerator='Command-c', state=tk.DISABLED, command=self.copy)
|
|
||||||
self.menubar.add_cascade(menu=self.edit_menu)
|
|
||||||
self.w.bind('<Command-c>', self.copy)
|
|
||||||
self.view_menu = tk.Menu(self.menubar, name='view')
|
|
||||||
self.view_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status))
|
|
||||||
self.menubar.add_cascade(menu=self.view_menu)
|
|
||||||
window_menu = tk.Menu(self.menubar, name='window')
|
|
||||||
self.menubar.add_cascade(menu=window_menu)
|
|
||||||
self.help_menu = tk.Menu(self.menubar, name='help')
|
|
||||||
self.w.createcommand("::tk::mac::ShowHelp", self.help_general)
|
|
||||||
self.help_menu.add_command(command=self.help_troubleshooting)
|
|
||||||
self.help_menu.add_command(command=self.help_report_a_bug)
|
|
||||||
self.help_menu.add_command(command=self.help_privacy)
|
|
||||||
self.help_menu.add_command(command=self.help_releases)
|
|
||||||
self.menubar.add_cascade(menu=self.help_menu)
|
|
||||||
self.w['menu'] = self.menubar
|
|
||||||
# https://www.tcl.tk/man/tcl/TkCmd/tk_mac.htm
|
|
||||||
self.w.call('set', 'tk::mac::useCompatibilityMetrics', '0')
|
|
||||||
self.w.createcommand('tkAboutDialog', lambda: self.w.call('tk::mac::standardAboutPanel'))
|
|
||||||
self.w.createcommand("::tk::mac::Quit", self.onexit)
|
|
||||||
self.w.createcommand("::tk::mac::ShowPreferences", lambda: prefs.PreferencesDialog(self.w, self.postprefs))
|
|
||||||
self.w.createcommand("::tk::mac::ReopenApplication", self.w.deiconify) # click on app in dock = restore
|
|
||||||
self.w.protocol("WM_DELETE_WINDOW", self.w.withdraw) # close button shouldn't quit app
|
|
||||||
self.w.resizable(tk.FALSE, tk.FALSE) # Can't be only resizable on one axis
|
|
||||||
else:
|
|
||||||
self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
|
|
||||||
self.file_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status))
|
|
||||||
self.file_menu.add_command(command=self.save_raw)
|
|
||||||
self.file_menu.add_command(command=lambda: prefs.PreferencesDialog(self.w, self.postprefs))
|
|
||||||
self.file_menu.add_separator()
|
|
||||||
self.file_menu.add_command(command=self.onexit)
|
|
||||||
self.menubar.add_cascade(menu=self.file_menu)
|
|
||||||
self.edit_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
|
|
||||||
self.edit_menu.add_command(accelerator='Ctrl+C', state=tk.DISABLED, command=self.copy)
|
|
||||||
self.menubar.add_cascade(menu=self.edit_menu)
|
|
||||||
self.help_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) # type: ignore
|
|
||||||
self.help_menu.add_command(command=self.help_general) # Documentation
|
|
||||||
self.help_menu.add_command(command=self.help_troubleshooting) # Troubleshooting
|
|
||||||
self.help_menu.add_command(command=self.help_report_a_bug) # Report A Bug
|
|
||||||
self.help_menu.add_command(command=self.help_privacy) # Privacy Policy
|
|
||||||
self.help_menu.add_command(command=self.help_releases) # Release Notes
|
|
||||||
self.help_menu.add_command(command=lambda: self.updater.check_for_updates()) # Check for Updates...
|
|
||||||
# About E:D Market Connector
|
|
||||||
self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w))
|
|
||||||
self.help_menu.add_command(command=prefs.help_open_log_folder) # Open Log Folder
|
|
||||||
|
|
||||||
self.menubar.add_cascade(menu=self.help_menu)
|
# Bind to the Default theme minimise button
|
||||||
if sys.platform == 'win32':
|
self.w.bind("<Unmap>", self.default_iconify)
|
||||||
# Must be added after at least one "real" menu entry
|
|
||||||
self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop')))
|
|
||||||
self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE)
|
|
||||||
self.system_menu.add_separator()
|
|
||||||
# LANG: Appearance - Label for checkbox to select if application always on top
|
|
||||||
self.system_menu.add_checkbutton(label=_('Always on top'),
|
|
||||||
variable=self.always_ontop,
|
|
||||||
command=self.ontop_changed) # Appearance setting
|
|
||||||
self.menubar.add_cascade(menu=self.system_menu)
|
|
||||||
self.w.bind('<Control-c>', self.copy)
|
|
||||||
|
|
||||||
# Bind to the Default theme minimise button
|
self.w.protocol("WM_DELETE_WINDOW", self.onexit)
|
||||||
self.w.bind("<Unmap>", self.default_iconify)
|
theme.register(self.menubar) # menus and children aren't automatically registered
|
||||||
|
theme.register(self.file_menu)
|
||||||
|
theme.register(self.edit_menu)
|
||||||
|
theme.register(self.help_menu)
|
||||||
|
|
||||||
self.w.protocol("WM_DELETE_WINDOW", self.onexit)
|
# Alternate title bar and menu for dark theme
|
||||||
theme.register(self.menubar) # menus and children aren't automatically registered
|
self.theme_menubar = tk.Frame(frame, name="alternate_menubar")
|
||||||
theme.register(self.file_menu)
|
self.theme_menubar.columnconfigure(2, weight=1)
|
||||||
theme.register(self.edit_menu)
|
theme_titlebar = tk.Label(
|
||||||
theme.register(self.help_menu)
|
self.theme_menubar,
|
||||||
|
name="alternate_titlebar",
|
||||||
# Alternate title bar and menu for dark theme
|
text=applongname,
|
||||||
self.theme_menubar = tk.Frame(frame, name="alternate_menubar")
|
image=self.theme_icon, cursor='fleur',
|
||||||
self.theme_menubar.columnconfigure(2, weight=1)
|
anchor=tk.W, compound=tk.LEFT
|
||||||
theme_titlebar = tk.Label(
|
)
|
||||||
self.theme_menubar,
|
theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW)
|
||||||
name="alternate_titlebar",
|
self.drag_offset: tuple[int | None, int | None] = (None, None)
|
||||||
text=applongname,
|
theme_titlebar.bind('<Button-1>', self.drag_start)
|
||||||
image=self.theme_icon, cursor='fleur',
|
theme_titlebar.bind('<B1-Motion>', self.drag_continue)
|
||||||
anchor=tk.W, compound=tk.LEFT
|
theme_titlebar.bind('<ButtonRelease-1>', self.drag_end)
|
||||||
)
|
theme_minimize = tk.Label(self.theme_menubar, image=self.theme_minimize)
|
||||||
theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW)
|
theme_minimize.grid(row=0, column=3, padx=2)
|
||||||
self.drag_offset: tuple[int | None, int | None] = (None, None)
|
theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize)
|
||||||
theme_titlebar.bind('<Button-1>', self.drag_start)
|
theme_close = tk.Label(self.theme_menubar, image=self.theme_close)
|
||||||
theme_titlebar.bind('<B1-Motion>', self.drag_continue)
|
theme_close.grid(row=0, column=4, padx=2)
|
||||||
theme_titlebar.bind('<ButtonRelease-1>', self.drag_end)
|
theme.button_bind(theme_close, self.onexit, image=self.theme_close)
|
||||||
theme_minimize = tk.Label(self.theme_menubar, image=self.theme_minimize)
|
self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W)
|
||||||
theme_minimize.grid(row=0, column=3, padx=2)
|
self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W)
|
||||||
theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize)
|
theme.button_bind(self.theme_file_menu,
|
||||||
theme_close = tk.Label(self.theme_menubar, image=self.theme_close)
|
lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(),
|
||||||
theme_close.grid(row=0, column=4, padx=2)
|
e.widget.winfo_rooty()
|
||||||
theme.button_bind(theme_close, self.onexit, image=self.theme_close)
|
+ e.widget.winfo_height()))
|
||||||
self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W)
|
self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W)
|
||||||
self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W)
|
self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W)
|
||||||
theme.button_bind(self.theme_file_menu,
|
theme.button_bind(self.theme_edit_menu,
|
||||||
lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(),
|
lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(),
|
||||||
e.widget.winfo_rooty()
|
e.widget.winfo_rooty()
|
||||||
+ e.widget.winfo_height()))
|
+ e.widget.winfo_height()))
|
||||||
self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W)
|
self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W)
|
||||||
self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W)
|
self.theme_help_menu.grid(row=1, column=2, sticky=tk.W)
|
||||||
theme.button_bind(self.theme_edit_menu,
|
theme.button_bind(self.theme_help_menu,
|
||||||
lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(),
|
lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(),
|
||||||
e.widget.winfo_rooty()
|
e.widget.winfo_rooty()
|
||||||
+ e.widget.winfo_height()))
|
+ e.widget.winfo_height()))
|
||||||
self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W)
|
tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=self.PADX, sticky=tk.EW)
|
||||||
self.theme_help_menu.grid(row=1, column=2, sticky=tk.W)
|
theme.register(self.theme_minimize) # images aren't automatically registered
|
||||||
theme.button_bind(self.theme_help_menu,
|
theme.register(self.theme_close)
|
||||||
lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(),
|
self.blank_menubar = tk.Frame(frame, name="blank_menubar")
|
||||||
e.widget.winfo_rooty()
|
tk.Label(self.blank_menubar).grid()
|
||||||
+ e.widget.winfo_height()))
|
tk.Label(self.blank_menubar).grid()
|
||||||
tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=self.PADX, sticky=tk.EW)
|
tk.Frame(self.blank_menubar, height=2).grid()
|
||||||
theme.register(self.theme_minimize) # images aren't automatically registered
|
theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar),
|
||||||
theme.register(self.theme_close)
|
{'row': 0, 'columnspan': 2, 'sticky': tk.NSEW})
|
||||||
self.blank_menubar = tk.Frame(frame, name="blank_menubar")
|
self.w.resizable(tk.TRUE, tk.FALSE)
|
||||||
tk.Label(self.blank_menubar).grid()
|
|
||||||
tk.Label(self.blank_menubar).grid()
|
|
||||||
tk.Frame(self.blank_menubar, height=2).grid()
|
|
||||||
theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar),
|
|
||||||
{'row': 0, 'columnspan': 2, 'sticky': tk.NSEW})
|
|
||||||
self.w.resizable(tk.TRUE, tk.FALSE)
|
|
||||||
|
|
||||||
# update geometry
|
# update geometry
|
||||||
if config.get_str('geometry'):
|
if config.get_str('geometry'):
|
||||||
match = re.match(r'\+([\-\d]+)\+([\-\d]+)', config.get_str('geometry'))
|
match = re.match(r'\+([\-\d]+)\+([\-\d]+)', config.get_str('geometry'))
|
||||||
if match:
|
if match:
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'win32':
|
||||||
# http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
|
||||||
if int(match.group(2)) >= 0:
|
|
||||||
self.w.geometry(config.get_str('geometry'))
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
# Check that the titlebar will be at least partly on screen
|
# Check that the titlebar will be at least partly on screen
|
||||||
import ctypes
|
import ctypes
|
||||||
from ctypes.wintypes import POINT
|
from ctypes.wintypes import POINT
|
||||||
@ -910,49 +865,28 @@ class AppWindow:
|
|||||||
self.system_label['text'] = _('System') + ':' # LANG: Label for 'System' line in main UI
|
self.system_label['text'] = _('System') + ':' # LANG: Label for 'System' line in main UI
|
||||||
self.station_label['text'] = _('Station') + ':' # LANG: Label for 'Station' line in main UI
|
self.station_label['text'] = _('Station') + ':' # LANG: Label for 'Station' line in main UI
|
||||||
self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window
|
self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window
|
||||||
if sys.platform == 'darwin':
|
self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title
|
||||||
self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title on OSX
|
self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title
|
||||||
self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title on OSX
|
self.menubar.entryconfigure(3, label=_('Help')) # LANG: 'Help' menu title
|
||||||
self.menubar.entryconfigure(3, label=_('View')) # LANG: 'View' menu title on OSX
|
self.theme_file_menu['text'] = _('File') # LANG: 'File' menu title
|
||||||
self.menubar.entryconfigure(4, label=_('Window')) # LANG: 'Window' menu title on OSX
|
self.theme_edit_menu['text'] = _('Edit') # LANG: 'Edit' menu title
|
||||||
self.menubar.entryconfigure(5, label=_('Help')) # LANG: Help' menu title on OSX
|
self.theme_help_menu['text'] = _('Help') # LANG: 'Help' menu title
|
||||||
self.system_menu.entryconfigure(
|
|
||||||
0,
|
|
||||||
label=_("About {APP}").format(APP=applongname) # LANG: App menu entry on OSX
|
|
||||||
)
|
|
||||||
self.system_menu.entryconfigure(1, label=_("Check for Updates...")) # LANG: Help > Check for Updates...
|
|
||||||
self.file_menu.entryconfigure(0, label=_('Save Raw Data...')) # LANG: File > Save Raw Data...
|
|
||||||
self.view_menu.entryconfigure(0, label=_('Status')) # LANG: File > Status
|
|
||||||
self.help_menu.entryconfigure(1, label=_('Documentation')) # LANG: Help > Documentation
|
|
||||||
self.help_menu.entryconfigure(2, label=_('Troubleshooting')) # LANG: Help > Troubleshooting
|
|
||||||
self.help_menu.entryconfigure(3, label=_('Report A Bug')) # LANG: Help > Report A Bug
|
|
||||||
self.help_menu.entryconfigure(4, label=_('Privacy Policy')) # LANG: Help > Privacy Policy
|
|
||||||
self.help_menu.entryconfigure(5, label=_('Release Notes')) # LANG: Help > Release Notes
|
|
||||||
self.help_menu.entryconfigure(6, label=_('Open Log Folder')) # LANG: Help > Open Log Folder
|
|
||||||
|
|
||||||
else:
|
# File menu
|
||||||
self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title
|
self.file_menu.entryconfigure(0, label=_('Status')) # LANG: File > Status
|
||||||
self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title
|
self.file_menu.entryconfigure(1, label=_('Save Raw Data...')) # LANG: File > Save Raw Data...
|
||||||
self.menubar.entryconfigure(3, label=_('Help')) # LANG: 'Help' menu title
|
self.file_menu.entryconfigure(2, label=_('Settings')) # LANG: File > Settings
|
||||||
self.theme_file_menu['text'] = _('File') # LANG: 'File' menu title
|
self.file_menu.entryconfigure(4, label=_('Exit')) # LANG: File > Exit
|
||||||
self.theme_edit_menu['text'] = _('Edit') # LANG: 'Edit' menu title
|
|
||||||
self.theme_help_menu['text'] = _('Help') # LANG: 'Help' menu title
|
|
||||||
|
|
||||||
# File menu
|
# Help menu
|
||||||
self.file_menu.entryconfigure(0, label=_('Status')) # LANG: File > Status
|
self.help_menu.entryconfigure(0, label=_('Documentation')) # LANG: Help > Documentation
|
||||||
self.file_menu.entryconfigure(1, label=_('Save Raw Data...')) # LANG: File > Save Raw Data...
|
self.help_menu.entryconfigure(1, label=_('Troubleshooting')) # LANG: Help > Troubleshooting
|
||||||
self.file_menu.entryconfigure(2, label=_('Settings')) # LANG: File > Settings
|
self.help_menu.entryconfigure(2, label=_('Report A Bug')) # LANG: Help > Report A Bug
|
||||||
self.file_menu.entryconfigure(4, label=_('Exit')) # LANG: File > Exit
|
self.help_menu.entryconfigure(3, label=_('Privacy Policy')) # LANG: Help > Privacy Policy
|
||||||
|
self.help_menu.entryconfigure(4, label=_('Release Notes')) # LANG: Help > Release Notes
|
||||||
# Help menu
|
self.help_menu.entryconfigure(5, label=_('Check for Updates...')) # LANG: Help > Check for Updates...
|
||||||
self.help_menu.entryconfigure(0, label=_('Documentation')) # LANG: Help > Documentation
|
self.help_menu.entryconfigure(6, label=_("About {APP}").format(APP=applongname)) # LANG: Help > About App
|
||||||
self.help_menu.entryconfigure(1, label=_('Troubleshooting')) # LANG: Help > Troubleshooting
|
self.help_menu.entryconfigure(7, label=_('Open Log Folder')) # LANG: Help > Open Log Folder
|
||||||
self.help_menu.entryconfigure(2, label=_('Report A Bug')) # LANG: Help > Report A Bug
|
|
||||||
self.help_menu.entryconfigure(3, label=_('Privacy Policy')) # LANG: Help > Privacy Policy
|
|
||||||
self.help_menu.entryconfigure(4, label=_('Release Notes')) # LANG: Help > Release Notes
|
|
||||||
self.help_menu.entryconfigure(5, label=_('Check for Updates...')) # LANG: Help > Check for Updates...
|
|
||||||
self.help_menu.entryconfigure(6, label=_("About {APP}").format(APP=applongname)) # LANG: Help > About App
|
|
||||||
self.help_menu.entryconfigure(7, label=_('Open Log Folder')) # LANG: Help > Open Log Folder
|
|
||||||
|
|
||||||
# Edit menu
|
# Edit menu
|
||||||
self.edit_menu.entryconfigure(0, label=_('Copy')) # LANG: Label for 'Copy' as in 'Copy and Paste'
|
self.edit_menu.entryconfigure(0, label=_('Copy')) # LANG: Label for 'Copy' as in 'Copy and Paste'
|
||||||
@ -975,13 +909,8 @@ class AppWindow:
|
|||||||
|
|
||||||
self.button['state'] = self.theme_button['state'] = tk.DISABLED
|
self.button['state'] = self.theme_button['state'] = tk.DISABLED
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
self.file_menu.entryconfigure(0, state=tk.DISABLED) # Status
|
||||||
self.view_menu.entryconfigure(0, state=tk.DISABLED) # Status
|
self.file_menu.entryconfigure(1, state=tk.DISABLED) # Save Raw Data
|
||||||
self.file_menu.entryconfigure(0, state=tk.DISABLED) # Save Raw Data
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.file_menu.entryconfigure(0, state=tk.DISABLED) # Status
|
|
||||||
self.file_menu.entryconfigure(1, state=tk.DISABLED) # Save Raw Data
|
|
||||||
|
|
||||||
self.w.update_idletasks()
|
self.w.update_idletasks()
|
||||||
try:
|
try:
|
||||||
@ -989,13 +918,8 @@ class AppWindow:
|
|||||||
# LANG: Successfully authenticated with the Frontier website
|
# LANG: Successfully authenticated with the Frontier website
|
||||||
self.status['text'] = _('Authentication successful')
|
self.status['text'] = _('Authentication successful')
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
||||||
self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data
|
||||||
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
|
||||||
self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data
|
|
||||||
|
|
||||||
except (companion.CredentialsError, companion.ServerError, companion.ServerLagging) as e:
|
except (companion.CredentialsError, companion.ServerError, companion.ServerLagging) as e:
|
||||||
self.status['text'] = str(e)
|
self.status['text'] = str(e)
|
||||||
@ -1666,13 +1590,8 @@ class AppWindow:
|
|||||||
companion.session.auth_callback()
|
companion.session.auth_callback()
|
||||||
# LANG: Successfully authenticated with the Frontier website
|
# LANG: Successfully authenticated with the Frontier website
|
||||||
self.status['text'] = _('Authentication successful')
|
self.status['text'] = _('Authentication successful')
|
||||||
if sys.platform == 'darwin':
|
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
||||||
self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data
|
||||||
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
|
||||||
self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data
|
|
||||||
|
|
||||||
except companion.ServerError as e:
|
except companion.ServerError as e:
|
||||||
self.status['text'] = str(e)
|
self.status['text'] = str(e)
|
||||||
@ -1831,8 +1750,7 @@ class AppWindow:
|
|||||||
|
|
||||||
# position over parent
|
# position over parent
|
||||||
# http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
# http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
||||||
if sys.platform != 'darwin' or parent.winfo_rooty() > 0:
|
self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}')
|
||||||
self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}')
|
|
||||||
|
|
||||||
# remove decoration
|
# remove decoration
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
@ -1916,9 +1834,6 @@ class AppWindow:
|
|||||||
"""
|
"""
|
||||||
default_extension: str = ''
|
default_extension: str = ''
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
|
||||||
default_extension = '.json'
|
|
||||||
|
|
||||||
timestamp: str = strftime('%Y-%m-%dT%H.%M.%S', localtime())
|
timestamp: str = strftime('%Y-%m-%dT%H.%M.%S', localtime())
|
||||||
f = tkinter.filedialog.asksaveasfilename(
|
f = tkinter.filedialog.asksaveasfilename(
|
||||||
parent=self.w,
|
parent=self.w,
|
||||||
@ -1954,9 +1869,8 @@ class AppWindow:
|
|||||||
config.set_shutdown() # Signal we're in shutdown now.
|
config.set_shutdown() # Signal we're in shutdown now.
|
||||||
|
|
||||||
# http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
# http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
||||||
if sys.platform != 'darwin' or self.w.winfo_rooty() > 0:
|
x, y = self.w.geometry().split('+')[1:3] # e.g. '212x170+2881+1267'
|
||||||
x, y = self.w.geometry().split('+')[1:3] # e.g. '212x170+2881+1267'
|
config.set('geometry', f'+{x}+{y}')
|
||||||
config.set('geometry', f'+{x}+{y}')
|
|
||||||
|
|
||||||
# Let the user know we're shutting down.
|
# Let the user know we're shutting down.
|
||||||
# LANG: The application is shutting down
|
# LANG: The application is shutting down
|
||||||
|
2
FDevIDs
2
FDevIDs
@ -1 +1 @@
|
|||||||
Subproject commit 7205c79331f42c1a28b757b27467f79ff106716b
|
Subproject commit 9b3f40612017b43a8b826017e1e2befebd9074f2
|
@ -178,13 +178,13 @@
|
|||||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
"Default" = "Výchozí";
|
"Default" = "Výchozí";
|
||||||
|
|
||||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
"Auto" = "Auto";
|
"Auto" = "Auto";
|
||||||
|
|
||||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
"Normal" = "Normal";
|
"Normal" = "Normal";
|
||||||
|
|
||||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
"Beta" = "Beta";
|
"Beta" = "Beta";
|
||||||
|
|
||||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
|
@ -231,6 +231,18 @@
|
|||||||
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
|
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
|
||||||
"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Eins oder mehr deiner aktivierten Plugins konnte nicht geladen werden. Du kannst dir die Liste im '{PLUGINS}'-Tab unter '{FILE}' > '{SETTINGS}' ansehen. Dies könnte an einer falschen Ordnerstruktur liegen. Die Datei load.py sollte sich unter plugins/PLUGIN_NAME/load.py befinden.\n\nDu kannst ein Plugin deaktivieren, indem du dessen Ordner ein '{DISABLED}' am Ende des Namens anhängst.";
|
"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Eins oder mehr deiner aktivierten Plugins konnte nicht geladen werden. Du kannst dir die Liste im '{PLUGINS}'-Tab unter '{FILE}' > '{SETTINGS}' ansehen. Dies könnte an einer falschen Ordnerstruktur liegen. Die Datei load.py sollte sich unter plugins/PLUGIN_NAME/load.py befinden.\n\nDu kannst ein Plugin deaktivieren, indem du dessen Ordner ein '{DISABLED}' am Ende des Namens anhängst.";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
|
||||||
|
"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Einer oder mehrere deiner URL-Anbieter waren ungültig und wurden zurückgesetzt:\n\n";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
|
||||||
|
"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} war zuvor auf {OLDPROV} festgelegt und wurde zu {NEWPROV} zurückgesetzt.\n";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
|
||||||
|
"EDMC: Default Providers Reset" = "EDMC: Standardanbieter wurden zurückgesetzt";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
|
||||||
|
"Awaiting Full CMDR Login" = "Warte auf vollständige CMDR Anmeldung";
|
||||||
|
|
||||||
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
||||||
"Journal directory already locked" = "Journal-Ordner bereits gesperrt";
|
"Journal directory already locked" = "Journal-Ordner bereits gesperrt";
|
||||||
|
|
||||||
@ -246,13 +258,13 @@
|
|||||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
"Default" = "Standard";
|
"Default" = "Standard";
|
||||||
|
|
||||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
"Auto" = "Auto";
|
"Auto" = "Auto";
|
||||||
|
|
||||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
"Normal" = "Normal";
|
"Normal" = "Normal";
|
||||||
|
|
||||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
"Beta" = "Beta";
|
"Beta" = "Beta";
|
||||||
|
|
||||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
@ -768,3 +780,5 @@
|
|||||||
/* stats.py: Status dialog title; In files: stats.py:418; */
|
/* stats.py: Status dialog title; In files: stats.py:418; */
|
||||||
"Ships" = "Schiffe";
|
"Ships" = "Schiffe";
|
||||||
|
|
||||||
|
/* update.py: Update Available Text; In files: update.py:229; */
|
||||||
|
"{NEWVER} is available" = "{NEWVER} ist verfügbar";
|
||||||
|
@ -78,12 +78,6 @@
|
|||||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||||
"Edit" = "Edit";
|
"Edit" = "Edit";
|
||||||
|
|
||||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
|
||||||
"View" = "View";
|
|
||||||
|
|
||||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
|
||||||
"Window" = "Window";
|
|
||||||
|
|
||||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||||
"Help" = "Help";
|
"Help" = "Help";
|
||||||
|
|
||||||
@ -351,9 +345,6 @@
|
|||||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||||
"Error: Inara {MSG}" = "Error: Inara {MSG}";
|
"Error: Inara {MSG}" = "Error: Inara {MSG}";
|
||||||
|
|
||||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
|
||||||
"Preferences" = "Preferences";
|
|
||||||
|
|
||||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||||
"Please choose what data to save" = "Please choose what data to save";
|
"Please choose what data to save" = "Please choose what data to save";
|
||||||
|
|
||||||
@ -372,9 +363,6 @@
|
|||||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||||
"File location" = "File location";
|
"File location" = "File location";
|
||||||
|
|
||||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
|
||||||
"Change..." = "Change...";
|
|
||||||
|
|
||||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||||
"Browse..." = "Browse...";
|
"Browse..." = "Browse...";
|
||||||
|
|
||||||
@ -390,21 +378,9 @@
|
|||||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||||
"Enable Fleetcarrier CAPI Queries" = "Enable Fleetcarrier CAPI Queries";
|
"Enable Fleetcarrier CAPI Queries" = "Enable Fleetcarrier CAPI Queries";
|
||||||
|
|
||||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
|
||||||
"Keyboard shortcut" = "Keyboard shortcut";
|
|
||||||
|
|
||||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||||
"Hotkey" = "Hotkey";
|
"Hotkey" = "Hotkey";
|
||||||
|
|
||||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
|
||||||
"Re-start {APP} to use shortcuts" = "Re-start {APP} to use shortcuts";
|
|
||||||
|
|
||||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
|
||||||
"{APP} needs permission to use shortcuts" = "{APP} needs permission to use shortcuts";
|
|
||||||
|
|
||||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
|
||||||
"Open System Preferences" = "Open System Preferences";
|
|
||||||
|
|
||||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||||
"Only when Elite: Dangerous is the active app" = "Only when Elite: Dangerous is the active app";
|
"Only when Elite: Dangerous is the active app" = "Only when Elite: Dangerous is the active app";
|
||||||
|
|
||||||
@ -802,4 +778,7 @@
|
|||||||
"Ships" = "Ships";
|
"Ships" = "Ships";
|
||||||
|
|
||||||
/* update.py: Update Available Text; In files: update.py:229; */
|
/* update.py: Update Available Text; In files: update.py:229; */
|
||||||
"{NEWVER} is available" = "{NEWVER} is available";
|
"{NEWVER} is available" = "{NEWVER} is available";
|
||||||
|
|
||||||
|
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||||
|
"Cannot paste non-text content." = "Cannot paste non-text content.";
|
||||||
|
@ -184,13 +184,13 @@
|
|||||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
"Default" = "Por defecto";
|
"Default" = "Por defecto";
|
||||||
|
|
||||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
"Auto" = "Auto";
|
"Auto" = "Auto";
|
||||||
|
|
||||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
"Normal" = "Normal";
|
"Normal" = "Normal";
|
||||||
|
|
||||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
"Beta" = "Beta";
|
"Beta" = "Beta";
|
||||||
|
|
||||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
|
@ -231,6 +231,18 @@
|
|||||||
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
|
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
|
||||||
"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Impossibile caricare uno o più plugin abilitati. Consulta l'elenco nella scheda '{PLUGINS}' di '{FILE}' > '{SETTINGS}'. Ciò potrebbe essere causato da un'errata struttura delle cartelle. Il file load.py dovrebbe trovarsi in plugins/PLUGIN_NAME/load.py.\n\nPuoi disabilitare un plugin rinominando la sua cartella in modo che abbia '{DISABLED}' alla fine del nome.";
|
"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Impossibile caricare uno o più plugin abilitati. Consulta l'elenco nella scheda '{PLUGINS}' di '{FILE}' > '{SETTINGS}'. Ciò potrebbe essere causato da un'errata struttura delle cartelle. Il file load.py dovrebbe trovarsi in plugins/PLUGIN_NAME/load.py.\n\nPuoi disabilitare un plugin rinominando la sua cartella in modo che abbia '{DISABLED}' alla fine del nome.";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
|
||||||
|
"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Uno o più Provider URL non erano validi e sono stati ripristinati:\n";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
|
||||||
|
"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} era impostato su {OLDPROV} ed è stato reimpostato su {NEWPROV}\n";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
|
||||||
|
"EDMC: Default Providers Reset" = "EDMC: Reset dei provider predefiniti";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
|
||||||
|
"Awaiting Full CMDR Login" = "In attesa del login completo al CMDR";
|
||||||
|
|
||||||
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
||||||
"Journal directory already locked" = "cartella Journal già bloccata";
|
"Journal directory already locked" = "cartella Journal già bloccata";
|
||||||
|
|
||||||
@ -246,13 +258,13 @@
|
|||||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
"Default" = "Predefinito";
|
"Default" = "Predefinito";
|
||||||
|
|
||||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
"Auto" = "Auto";
|
"Auto" = "Auto";
|
||||||
|
|
||||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
"Normal" = "Normale";
|
"Normal" = "Normale";
|
||||||
|
|
||||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
"Beta" = "Beta";
|
"Beta" = "Beta";
|
||||||
|
|
||||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
@ -789,3 +801,5 @@
|
|||||||
/* stats.py: Status dialog title; In files: stats.py:418; */
|
/* stats.py: Status dialog title; In files: stats.py:418; */
|
||||||
"Ships" = "Navi";
|
"Ships" = "Navi";
|
||||||
|
|
||||||
|
/* update.py: Update Available Text; In files: update.py:229; */
|
||||||
|
"{NEWVER} is available" = "{NEWVER} è disponibile";
|
||||||
|
@ -246,13 +246,13 @@
|
|||||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
"Default" = "デフォルト";
|
"Default" = "デフォルト";
|
||||||
|
|
||||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
"Auto" = "自動";
|
"Auto" = "自動";
|
||||||
|
|
||||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
"Normal" = "通常版";
|
"Normal" = "通常版";
|
||||||
|
|
||||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
"Beta" = "ベータ版";
|
"Beta" = "ベータ版";
|
||||||
|
|
||||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
|
@ -202,13 +202,13 @@
|
|||||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
"Default" = "기본값";
|
"Default" = "기본값";
|
||||||
|
|
||||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
"Auto" = "자동";
|
"Auto" = "자동";
|
||||||
|
|
||||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
"Normal" = "일반";
|
"Normal" = "일반";
|
||||||
|
|
||||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
"Beta" = "베타";
|
"Beta" = "베타";
|
||||||
|
|
||||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
|
@ -234,13 +234,13 @@
|
|||||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
"Default" = "Domyślne";
|
"Default" = "Domyślne";
|
||||||
|
|
||||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
"Auto" = "Auto";
|
"Auto" = "Auto";
|
||||||
|
|
||||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
"Normal" = "Normalny";
|
"Normal" = "Normalny";
|
||||||
|
|
||||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
"Beta" = "Beta";
|
"Beta" = "Beta";
|
||||||
|
|
||||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
|
@ -234,13 +234,13 @@
|
|||||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
"Default" = "Padrão";
|
"Default" = "Padrão";
|
||||||
|
|
||||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
"Auto" = "Automático";
|
"Auto" = "Automático";
|
||||||
|
|
||||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
"Normal" = "Normal";
|
"Normal" = "Normal";
|
||||||
|
|
||||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
"Beta" = "Beta";
|
"Beta" = "Beta";
|
||||||
|
|
||||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
|
@ -246,13 +246,13 @@
|
|||||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
"Default" = "Predefinido";
|
"Default" = "Predefinido";
|
||||||
|
|
||||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
"Auto" = "Auto";
|
"Auto" = "Auto";
|
||||||
|
|
||||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
"Normal" = "Normal";
|
"Normal" = "Normal";
|
||||||
|
|
||||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
"Beta" = "Beta";
|
"Beta" = "Beta";
|
||||||
|
|
||||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
|
@ -213,12 +213,36 @@
|
|||||||
/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */
|
/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */
|
||||||
"One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Один или несколько ваших подключённых плагинов ещё не имеют поддержки Python 3.x. Пожалуйста, ознакомьтесь со списком во вкладке '{PLUGINS}' '{FILE}' > '{SETTINGS}'. Вы должны проверить наличие обновлённой версии, в противном случае предупредите разработчика о необходимости обновления кода на Python 3.x.\n\nВы можете отключить плагин, переименовав его папку в '{DISABLED}'.";
|
"One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Один или несколько ваших подключённых плагинов ещё не имеют поддержки Python 3.x. Пожалуйста, ознакомьтесь со списком во вкладке '{PLUGINS}' '{FILE}' > '{SETTINGS}'. Вы должны проверить наличие обновлённой версии, в противном случае предупредите разработчика о необходимости обновления кода на Python 3.x.\n\nВы можете отключить плагин, переименовав его папку в '{DISABLED}'.";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */
|
||||||
|
"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID файлы не найдены! Некоторые функции, связанные с товарами, могут быть отключены.\n\nВы хотите открыть страницу Wiki о том, как настроить вспомогательные модули?";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
|
||||||
|
"FDevIDs: Missing Commodity Files" = "FDevIDs: отсутствуют файлы товаров";
|
||||||
|
|
||||||
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
||||||
"Plugins" = "Плагины";
|
"Plugins" = "Плагины";
|
||||||
|
|
||||||
/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */
|
/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */
|
||||||
"EDMC: Plugins Without Python 3.x Support" = "EDMC: Без поддержки Python 3.x";
|
"EDMC: Plugins Without Python 3.x Support" = "EDMC: Без поддержки Python 3.x";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */
|
||||||
|
"EDMC: Broken Plugins" = "EDMC: Неработающие плагины";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
|
||||||
|
"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Не удалось загрузить один или несколько включенных плагинов. Пожалуйста, посмотрите список на вкладке '{PLUGINS}' в разделе '{FILE}' > '{SETTINGS}'. Причиной может быть неправильная структура папок. Файл load.py должен находиться в папке plugins/PLUGIN_NAME/load.py.\n\nВы можете отключить плагин, переименовав его папку так, чтобы в конце названия стояло '{DISABLED}'.";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
|
||||||
|
"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Один или несколько ваших URL-провайдеров оказались некорректными и были сброшены:";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
|
||||||
|
"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} был установлен на {OLDPROV} и был сброшен на {NEWPROV}";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
|
||||||
|
"EDMC: Default Providers Reset" = "EDMC: сброс провайдеров по умолчанию";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
|
||||||
|
"Awaiting Full CMDR Login" = "Ожидание полного входа в систему";
|
||||||
|
|
||||||
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
||||||
"Journal directory already locked" = "Каталог журнала уже заблокирован";
|
"Journal directory already locked" = "Каталог журнала уже заблокирован";
|
||||||
|
|
||||||
@ -234,13 +258,13 @@
|
|||||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
"Default" = "По умолчанию";
|
"Default" = "По умолчанию";
|
||||||
|
|
||||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
"Auto" = "Автоматический";
|
"Auto" = "Автоматический";
|
||||||
|
|
||||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
"Normal" = "Стандартный";
|
"Normal" = "Стандартный";
|
||||||
|
|
||||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
"Beta" = "Бета";
|
"Beta" = "Бета";
|
||||||
|
|
||||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
@ -471,6 +495,9 @@
|
|||||||
/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */
|
/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */
|
||||||
"Information on migrating plugins" = "Информация о мигрирующих плагинах";
|
"Information on migrating plugins" = "Информация о мигрирующих плагинах";
|
||||||
|
|
||||||
|
/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
|
||||||
|
"Broken Plugins" = "Неработающие плагины";
|
||||||
|
|
||||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||||
"Disabled Plugins" = "Отключенные плагины";
|
"Disabled Plugins" = "Отключенные плагины";
|
||||||
|
|
||||||
@ -774,3 +801,5 @@
|
|||||||
/* stats.py: Status dialog title; In files: stats.py:418; */
|
/* stats.py: Status dialog title; In files: stats.py:418; */
|
||||||
"Ships" = "Корабли";
|
"Ships" = "Корабли";
|
||||||
|
|
||||||
|
/* update.py: Update Available Text; In files: update.py:229; */
|
||||||
|
"{NEWVER} is available" = "{NEWVER} доступен";
|
||||||
|
@ -246,13 +246,13 @@
|
|||||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
"Default" = "Standardna";
|
"Default" = "Standardna";
|
||||||
|
|
||||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
"Auto" = "Automatski";
|
"Auto" = "Automatski";
|
||||||
|
|
||||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
"Normal" = "Normalni";
|
"Normal" = "Normalni";
|
||||||
|
|
||||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
"Beta" = "Beta";
|
"Beta" = "Beta";
|
||||||
|
|
||||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
|
@ -231,6 +231,18 @@
|
|||||||
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
|
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
|
||||||
"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Jedan ili više vaših aktivnih pluginova se nije učitao. Molimo pogledajte listu na '{PLUGINS}' tabu u okviru '{FILE}' > '{SETTINGS}'. Obo vi moglo da se desi zbog pogrešne strukture foldera. load.py fajl bi trebao da se nalazi unutar plugins/PLUGIN_NAME/load.py.\n\nPojedinačni plugin možete deaktivirati promenom imena njegovog foldera dodavanjem '{DISABLED}' na kraju.";
|
"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Jedan ili više vaših aktivnih pluginova se nije učitao. Molimo pogledajte listu na '{PLUGINS}' tabu u okviru '{FILE}' > '{SETTINGS}'. Obo vi moglo da se desi zbog pogrešne strukture foldera. load.py fajl bi trebao da se nalazi unutar plugins/PLUGIN_NAME/load.py.\n\nPojedinačni plugin možete deaktivirati promenom imena njegovog foldera dodavanjem '{DISABLED}' na kraju.";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
|
||||||
|
"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Jedan ili više vaših URL Provajdera su pogrešni, pa su resetovani:\n";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
|
||||||
|
"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} je bio postavljen na {OLDPROV} i sada je resetovan na {NEWPROV}\n";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
|
||||||
|
"EDMC: Default Providers Reset" = "EDMC: Podrazumevani Provajderi resetovani";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
|
||||||
|
"Awaiting Full CMDR Login" = "Čekam da se CMDR potpuno uloguje";
|
||||||
|
|
||||||
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
||||||
"Journal directory already locked" = "Žurnal direktorijum je već zaključan";
|
"Journal directory already locked" = "Žurnal direktorijum je već zaključan";
|
||||||
|
|
||||||
@ -246,13 +258,13 @@
|
|||||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
"Default" = "Podrazumevano";
|
"Default" = "Podrazumevano";
|
||||||
|
|
||||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
"Auto" = "Auto";
|
"Auto" = "Auto";
|
||||||
|
|
||||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
"Normal" = "Normal";
|
"Normal" = "Normal";
|
||||||
|
|
||||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
"Beta" = "Beta";
|
"Beta" = "Beta";
|
||||||
|
|
||||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
@ -789,3 +801,5 @@
|
|||||||
/* stats.py: Status dialog title; In files: stats.py:418; */
|
/* stats.py: Status dialog title; In files: stats.py:418; */
|
||||||
"Ships" = "Brodovi";
|
"Ships" = "Brodovi";
|
||||||
|
|
||||||
|
/* update.py: Update Available Text; In files: update.py:229; */
|
||||||
|
"{NEWVER} is available" = "{NEWVER} je dostupna";
|
||||||
|
805
L10n/tr.strings
Normal file
805
L10n/tr.strings
Normal file
@ -0,0 +1,805 @@
|
|||||||
|
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||||
|
"Send flight log and CMDR status to EDSM" = "Uçuş günlüğünü ve CMDR durumunu EDSM'e gönder";
|
||||||
|
|
||||||
|
/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */
|
||||||
|
"Open Log Folder" = "Günlük Klasörünü Aç";
|
||||||
|
|
||||||
|
/* inara.py:Text Inara Show API key; In files: inara.py:305; */
|
||||||
|
"Show API Key" = "API Anahtarını Göster";
|
||||||
|
/* Language name */
|
||||||
|
"!Language" = "Türkçe";
|
||||||
|
|
||||||
|
/* companion.py: Frontier CAPI didn't respond; In files: companion.py:226; */
|
||||||
|
"Error: Frontier CAPI didn't respond" = "Hata: Frontier CAPI yanıt vermedi";
|
||||||
|
|
||||||
|
/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:245; */
|
||||||
|
"Error: Frontier server is lagging" = "Hata: Frontier sunucusu gecikmesi";
|
||||||
|
|
||||||
|
/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:261; */
|
||||||
|
"Docked but unknown station: EDO Settlement?" = "Kenetlendi ancak bilinmeyen istasyon: EDO yerleşimi?";
|
||||||
|
|
||||||
|
/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:271; */
|
||||||
|
"Error: Invalid Credentials" = "Hata: Geçersiz Kimlik";
|
||||||
|
|
||||||
|
/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:296; */
|
||||||
|
"Error: Wrong Cmdr" = "Hata: Yanlış CMDR";
|
||||||
|
|
||||||
|
/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:432; companion.py:517; */
|
||||||
|
"Error" = "Hata";
|
||||||
|
|
||||||
|
/* companion.py: Frontier auth, no 'usr' section in returned data; companion.py: Frontier auth, no 'customer_id' in 'usr' section in returned data; In files: companion.py:475; companion.py:480; */
|
||||||
|
"Error: Couldn't check token customer_id" = "Hata: token kontrol edilemedi customer_id";
|
||||||
|
|
||||||
|
/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:486; */
|
||||||
|
"Error: customer_id doesn't match!" = "Hata: customer_id uyuşmuyor!";
|
||||||
|
|
||||||
|
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
|
||||||
|
"Error: unable to get token" = "Hata: token erişilemedi";
|
||||||
|
|
||||||
|
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
|
||||||
|
"Frontier CAPI down for maintenance" = "Frontier CAPI bakım için kapalı durumda";
|
||||||
|
|
||||||
|
/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:856; */
|
||||||
|
"Frontier CAPI query failure" = "Frontier CAPI sorgulama hatası";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Main UI Update button; EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:601; EDMarketConnector.py:919; EDMarketConnector.py:1748; */
|
||||||
|
"Update" = "Güncelle";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Appearance - Label for checkbox to select if application always on top; prefs.py: Appearance - Label for checkbox to select if application always on top; In files: EDMarketConnector.py:710; prefs.py:875; */
|
||||||
|
"Always on top" = "Her zaman üstte";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:837; */
|
||||||
|
"Unknown" = "Bilinmeyen";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:906; */
|
||||||
|
"Error: Check E:D journal file location" = "Hata: E:D günlük dosyası konumunu kontrol edin";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Label for commander name in main window; edsm.py: Game Commander name label in EDSM settings; stats.py: Cmdr stats; theme.py: Label for commander name in main window; In files: EDMarketConnector.py:913; edsm.py:332; stats.py:57; theme.py:290; */
|
||||||
|
"Cmdr" = "Cmdr";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: Multicrew role label in main window; In files: EDMarketConnector.py:915; EDMarketConnector.py:1487; */
|
||||||
|
"Role" = "Rol";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: 'Ship' label in main UI; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:915; EDMarketConnector.py:1497; EDMarketConnector.py:1520; stats.py:405; */
|
||||||
|
"Ship" = "Gemi";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:916; */
|
||||||
|
"Suit" = "Süit";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Label for 'System' line in main UI; prefs.py: Configuration - Label for selection of 'System' provider website; stats.py: Main window; In files: EDMarketConnector.py:917; prefs.py:614; stats.py:407; */
|
||||||
|
"System" = "Sistem";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Label for 'Station' line in main UI; prefs.py: Configuration - Label for selection of 'Station' provider website; prefs.py: Appearance - Example 'Normal' text; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:918; prefs.py:632; prefs.py:770; stats.py:408; */
|
||||||
|
"Station" = "İstasyon";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:921; EDMarketConnector.py:939; EDMarketConnector.py:942; EDMarketConnector.py:2264; */
|
||||||
|
"File" = "Dosya";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||||
|
"Edit" = "Düzenle";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||||
|
"View" = "Görüntüle";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||||
|
"Window" = "Pencere";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||||
|
"Help" = "Yardım";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:928; EDMarketConnector.py:959; EDMarketConnector.py:1804; */
|
||||||
|
"About {APP}" = "Hakkında {APP}";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
|
||||||
|
"Check for Updates..." = "Güncellemeleri Denetle...";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
|
||||||
|
"Save Raw Data..." = "Ham Verileri Kaydet...";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:932; EDMarketConnector.py:947; stats.py:402; */
|
||||||
|
"Status" = "Durum";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:933; EDMarketConnector.py:953; */
|
||||||
|
"Documentation" = "Dokümantasyon";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Help > Troubleshooting; In files: EDMarketConnector.py:934; EDMarketConnector.py:954; */
|
||||||
|
"Troubleshooting" = "Sorun giderme";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Help > Report A Bug; In files: EDMarketConnector.py:935; EDMarketConnector.py:955; */
|
||||||
|
"Report A Bug" = "Hata Bildir";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:936; EDMarketConnector.py:956; */
|
||||||
|
"Privacy Policy" = "Gizlilik Politikası";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:937; EDMarketConnector.py:957; EDMarketConnector.py:1838; */
|
||||||
|
"Release Notes" = "Sürüm notları";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:949; EDMarketConnector.py:2265; prefs.py:241; */
|
||||||
|
"Settings" = "Ayarlar";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:950; */
|
||||||
|
"Exit" = "Çıkış";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */
|
||||||
|
"Copy" = "Kopyala";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */
|
||||||
|
"CAPI auth disabled by killswitch" = "CAPI kimlik doğrulaması killswitch tarafından devre dışı bırakıldı";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:978; */
|
||||||
|
"Logging in..." = "Giriş yapılıyor...";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:994; EDMarketConnector.py:1657; */
|
||||||
|
"Authentication successful" = "Kimlik doğrulama başarılı";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:1025; */
|
||||||
|
"You're not docked at a station!" = "Bir istasyona kenetli değilsiniz!";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:1033; */
|
||||||
|
"Station doesn't have anything!" = "İstasyon içeriği mevcut değil.";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:1038; */
|
||||||
|
"Station doesn't have a market!" = "İstasyon'da market bulunmuyor!";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because Cmdr name is unknown; In files: EDMarketConnector.py:1077; EDMarketConnector.py:1164; */
|
||||||
|
"CAPI query aborted: Cmdr name unknown" = "CAPI sorgusu iptal edildi: Cmdr adı bilinmiyor";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:1083; */
|
||||||
|
"CAPI query aborted: Game mode unknown" = "CAPI sorgusu iptal edildi: Oyun modu bilinmiyor";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: CAPI queries aborted because GameVersion unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because GameVersion unknown; In files: EDMarketConnector.py:1089; EDMarketConnector.py:1170; */
|
||||||
|
"CAPI query aborted: GameVersion unknown" = "CAPI sorgusu iptal edildi: GameVersion bilinmiyor";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:1095; */
|
||||||
|
"CAPI query aborted: Current system unknown" = "CAPI sorgusu iptal edildi: Mevcut sistem bilinmiyor";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:1101; */
|
||||||
|
"CAPI query aborted: In other-ship multi-crew" = "CAPI sorgusu iptal edildi: Başka gemi çoklu-mürettebatı";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:1107; */
|
||||||
|
"CAPI query aborted: CQC (Arena) detected" = "CAPI sorgusu iptal edildi: CQC (Arena) tespit edildi";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:1128; EDMarketConnector.py:1179; */
|
||||||
|
"Fetching data..." = "Veri işleniyor...";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||||
|
"CAPI fleetcarrier disabled by killswitch" = "CAPI filo taşıyıcısı killswitch tarafından devre dışı bırakıldı";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||||
|
"CAPI: No fleetcarrier data returned" = "CAPI: Filo taşıyıcı verisi gelmedi.";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||||
|
"CAPI: Fleetcarrier data incomplete" = "CAPI: Filo taşıyıcı verileri eksik";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||||
|
"CAPI: No commander data returned" = "CAPI: Cmdr verisi döndürülmedi";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1246; stats.py:333; */
|
||||||
|
"Who are you?!" = "Sen kimsin?!";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1252; stats.py:341; */
|
||||||
|
"Where are you?!" = "Neredesin?!";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1259; stats.py:349; */
|
||||||
|
"What are you flying?!" = "Ne uçuruyorsun?!";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1384; */
|
||||||
|
"Frontier CAPI server error" = "Frontier CAPI sunucu hatası";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Frontier CAPI Access Token expired, trying to get a new one; In files: EDMarketConnector.py:1390; */
|
||||||
|
"CAPI: Refreshing access token..." = "CAPI: Erişim token yenileniyor...";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1434; */
|
||||||
|
"Last updated at %H:%M:%S" = "Son güncelleme %H:%M:%S";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1462; */
|
||||||
|
"Fighter" = "Avcı";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1463; */
|
||||||
|
"Gunner" = "Nişancı";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1464; */
|
||||||
|
"Helm" = "Dümen";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1742; */
|
||||||
|
"cooldown {SS}s" = "bekleme süresi {SS}s";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1864; prefs.py:292; */
|
||||||
|
"OK" = "Tamam";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1936; */
|
||||||
|
"Shutting down..." = "Kapanıyor...";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */
|
||||||
|
"One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Etkin eklentilerinizden bir veya daha fazlası henüz Python 3.x desteğine sahip değil. '{PLUGINS}' sekmesindeki '{FILE}' > '{SETTINGS}' bölümünde bulunan listeyi bulun. Güncellenmiş bir sürümün mevcut olup olmadığını kontrol edin, aksi takdirde geliştiriciyi Python 3.x kodunu güncellemesi gerektiği konusunda uyarmalısınız.\n\nBir eklentiyi, klasörünü adının sonunda '{DISABLED}' olacak şekilde yeniden adlandırarak devre dışı bırakabilirsiniz.";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */
|
||||||
|
"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID Dosyaları bulunamadı! Ürünlerle ilgili bazı işlevler devre dışı bırakılabilir.\n\nAlt modüllerin nasıl kurulacağına ilişkin Wiki sayfasını açmak ister misiniz?";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
|
||||||
|
"FDevIDs: Missing Commodity Files" = "FDevIDs: Eksik Ürün Dosyaları";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
||||||
|
"Plugins" = "Eklentiler";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */
|
||||||
|
"EDMC: Plugins Without Python 3.x Support" = "EDMC: Python 3.x Desteği Olmayan Eklentiler";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */
|
||||||
|
"EDMC: Broken Plugins" = "EDMC: Bozuk Eklentiler";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
|
||||||
|
"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Etkin eklentilerinizden bir veya daha fazlası yüklenemedi. '{PLUGINS}' sekmesindeki '{FILE}' > '{SETTINGS}' bölümünde bulunan listeyi bulun. Sorunun sebebi yanlış klasör yapısı olabilir. Load.py dosyası, plugins/PLUGIN_NAME/load.py altında bulunmalıdır.\n\nBir eklentiyi, klasörünü adının sonunda '{DISABLED}' olacak şekilde yeniden adlandırarak devre dışı bırakabilirsiniz.";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
|
||||||
|
"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "URL Sağlayıcılarınızdan bir veya daha fazlası geçersizdi ve sıfırlandı:";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
|
||||||
|
"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} , {OLDPROV}'a ayarlanmıştı ancak {NEWPROV}'a sıfırlandı";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
|
||||||
|
"EDMC: Default Providers Reset" = "EDMC: Varsayılan Sağlayıcıların Sıfırlanması";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
|
||||||
|
"Awaiting Full CMDR Login" = "Tam CMDR Girişi Bekleniyor";
|
||||||
|
|
||||||
|
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
||||||
|
"Journal directory already locked" = "Günlük dizini zaten kilitli";
|
||||||
|
|
||||||
|
/* journal_lock.py: Text for when newly selected Journal directory is already locked; In files: journal_lock.py:225:226; */
|
||||||
|
"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "Yeni Günlük Dizini konumu zaten kilitli.{CR}Çözüm için Tekrar deneyebilir veya Yoksay'ı seçebilirsiniz.";
|
||||||
|
|
||||||
|
/* journal_lock.py: Generic 'Retry' button label; In files: journal_lock.py:230; */
|
||||||
|
"Retry" = "Tekrar Dene";
|
||||||
|
|
||||||
|
/* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:234; */
|
||||||
|
"Ignore" = "Yoksay";
|
||||||
|
|
||||||
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
|
"Default" = "Varsayılan";
|
||||||
|
|
||||||
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
|
"Auto" = "Otomatik";
|
||||||
|
|
||||||
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
|
"Normal" = "Normal";
|
||||||
|
|
||||||
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
|
"Beta" = "Beta";
|
||||||
|
|
||||||
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
|
"Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='" = "Coriolis.io gemi ekipman tasarımları ile kullanılacak URL'yi ayarlayın. Link'in '/import?data=' ile bitmesi GEREKTİĞİNİ unutmayın.";
|
||||||
|
|
||||||
|
/* coriolis.py: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL; In files: coriolis.py:97; */
|
||||||
|
"Normal URL" = "Normal URL";
|
||||||
|
|
||||||
|
/* coriolis.py: Generic 'Reset' button label; In files: coriolis.py:100; coriolis.py:109; */
|
||||||
|
"Reset" = "Sıfırla";
|
||||||
|
|
||||||
|
/* coriolis.py: Settings>Coriolis: Label for 'alpha/beta game version' URL; In files: coriolis.py:106; */
|
||||||
|
"Beta URL" = "Beta URL";
|
||||||
|
|
||||||
|
/* coriolis.py: Settings>Coriolis: Label for selection of using Normal, Beta or 'auto' Coriolis URL; In files: coriolis.py:116; */
|
||||||
|
"Override Beta/Normal Selection" = "Beta/Normal Seçimi Geçersiz Kıl";
|
||||||
|
|
||||||
|
/* coriolis.py: Settings>Coriolis - invalid override mode found; In files: coriolis.py:156; */
|
||||||
|
"Invalid Coriolis override mode!" = "Geçersiz Coriolis geçersiz kılma modu!";
|
||||||
|
|
||||||
|
/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:458; eddn.py:2413; eddn.py:2451; eddn.py:2519; */
|
||||||
|
"Error: Can't connect to EDDN" = "Hata: EDDN'e bağlanılamıyor";
|
||||||
|
|
||||||
|
/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:576; */
|
||||||
|
"EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN Hatası: EDMC sürümü EDDN için çok eski. Lütfen güncelle.";
|
||||||
|
|
||||||
|
/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:582; */
|
||||||
|
"EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "EDDN Hatası: Doğrulama Başarısız Oldu (EDMC sürümü eski olabilir?). Günlüğe bakın";
|
||||||
|
|
||||||
|
/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:587; */
|
||||||
|
"EDDN Error: Returned {STATUS} status code" = "EDDN Hatası: {STATUS} durum kodu ile döndü.";
|
||||||
|
|
||||||
|
/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:2041; */
|
||||||
|
"Send station data to the Elite Dangerous Data Network" = "İstasyon verilerini Elite Dangerous Data Network'e gönderin";
|
||||||
|
|
||||||
|
/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:2052; */
|
||||||
|
"Send system and scan data to the Elite Dangerous Data Network" = "Sistem ve tarama verilerini Elite Dangerous Data Network'e gönderin";
|
||||||
|
|
||||||
|
/* eddn.py: EDDN delay sending until docked option is on, this message notes that a send was skipped due to this; In files: eddn.py:2063; */
|
||||||
|
"Delay sending until docked" = "Kenetlenmeye kadar gönderimi ertele";
|
||||||
|
|
||||||
|
/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:2178; */
|
||||||
|
"EDDN journal handler disabled. See Log." = "EDDN günlük düzenleyici devre dışı. Kayıt geçmişini kontrol edin.";
|
||||||
|
|
||||||
|
/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:2507; */
|
||||||
|
"Sending data to EDDN..." = "Veriler EDDN'e gönderiliyor...";
|
||||||
|
|
||||||
|
/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:319; */
|
||||||
|
"Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map kimlik bilgileri";
|
||||||
|
|
||||||
|
/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:341; */
|
||||||
|
"Commander Name" = "Cmdr ismi";
|
||||||
|
|
||||||
|
/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:350; inara.py:278; */
|
||||||
|
"API Key" = "API Anahtarı";
|
||||||
|
|
||||||
|
/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:394; prefs.py:527; prefs.py:1157; prefs.py:1190; stats.py:154; stats.py:173; stats.py:192; stats.py:209; */
|
||||||
|
"None" = "Hiçbiri";
|
||||||
|
|
||||||
|
/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:516; */
|
||||||
|
"EDSM Handler disabled. See Log." = "EDSM düzenleyici devre dışı. Kayıt geçmişini kontrol edin.";
|
||||||
|
|
||||||
|
/* edsm.py: EDSM - Only Live data; In files: edsm.py:632; */
|
||||||
|
"EDSM only accepts Live galaxy data" = "EDSM sadece canlı galaxy verilerini kabul eder";
|
||||||
|
|
||||||
|
/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:916; edsm.py:1048; */
|
||||||
|
"Error: EDSM {MSG}" = "Hata: EDSM {MSG}";
|
||||||
|
|
||||||
|
/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:953; edsm.py:1043; */
|
||||||
|
"Error: Can't connect to EDSM" = "Hata: EDSM'e bağlanılamıyor";
|
||||||
|
|
||||||
|
/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:257; */
|
||||||
|
"Send flight log and Cmdr status to Inara" = "Uçuş günlüğünü ve Cmdr durumunu Inara'ya gönder";
|
||||||
|
|
||||||
|
/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:269; */
|
||||||
|
"Inara credentials" = "Inara kimlik bilgileri";
|
||||||
|
|
||||||
|
/* inara.py: The Inara API only accepts Live galaxy data, not Legacy galaxy data; inara.py: Inara - Only Live data; In files: inara.py:384; inara.py:386; */
|
||||||
|
"Inara only accepts Live galaxy data" = "Inara sadece canlı galaxy verilerini kabul eder";
|
||||||
|
|
||||||
|
/* inara.py: INARA support disabled via killswitch; In files: inara.py:395; */
|
||||||
|
"Inara disabled. See Log." = "Inara devre dışı. Kayıt geçmişini kontrol edin.";
|
||||||
|
|
||||||
|
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||||
|
"Error: Inara {MSG}" = "Hata: Inara {MSG}";
|
||||||
|
|
||||||
|
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||||
|
"Preferences" = "Tercihler";
|
||||||
|
|
||||||
|
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||||
|
"Please choose what data to save" = "Lütfen hangi verilerin kaydedileceğini seçin";
|
||||||
|
|
||||||
|
/* prefs.py: Settings > Output option; In files: prefs.py:341; */
|
||||||
|
"Market data in CSV format file" = "CSV formatında Market verileri";
|
||||||
|
|
||||||
|
/* prefs.py: Settings > Output option; In files: prefs.py:350; */
|
||||||
|
"Market data in Trade Dangerous format file" = "Trade Dangerous formatında Market verileri";
|
||||||
|
|
||||||
|
/* prefs.py: Settings > Output option; In files: prefs.py:360; */
|
||||||
|
"Ship loadout" = "Gemi Ekipmanları";
|
||||||
|
|
||||||
|
/* prefs.py: Settings > Output option; In files: prefs.py:370; */
|
||||||
|
"Automatically update on docking" = "Kenetlenme sırasında otomatik güncelle";
|
||||||
|
|
||||||
|
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||||
|
"File location" = "Dosya konumu";
|
||||||
|
|
||||||
|
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||||
|
"Change..." = "Değiştir...";
|
||||||
|
|
||||||
|
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||||
|
"Browse..." = "Gezin...";
|
||||||
|
|
||||||
|
/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:405; */
|
||||||
|
"Output" = "Kayıt";
|
||||||
|
|
||||||
|
/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
|
||||||
|
"E:D journal file location" = "E:D günlük dosya konumu";
|
||||||
|
|
||||||
|
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||||
|
"CAPI Settings" = "CAPI ayarları";
|
||||||
|
|
||||||
|
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||||
|
"Enable Fleetcarrier CAPI Queries" = "Filo Taşıyıcı CAPI sorgulamalarını etkinleştir";
|
||||||
|
|
||||||
|
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||||
|
"Keyboard shortcut" = "Klavye Kısayolu";
|
||||||
|
|
||||||
|
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||||
|
"Hotkey" = "Kısayoltuşu";
|
||||||
|
|
||||||
|
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||||
|
"Re-start {APP} to use shortcuts" = "Kısayolları kullanmak için {APP}'i yeniden başlat";
|
||||||
|
|
||||||
|
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||||
|
"{APP} needs permission to use shortcuts" = "{APP}'nin kısayolları kullanabilmesi için izne ihtiyacı var";
|
||||||
|
|
||||||
|
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||||
|
"Open System Preferences" = "Sistem Tercihlerini Aç";
|
||||||
|
|
||||||
|
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||||
|
"Only when Elite: Dangerous is the active app" = "Sadece Elite: Dangerous aktif uygulama olduğunda";
|
||||||
|
|
||||||
|
/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:549; */
|
||||||
|
"Play sound" = "Ses çal";
|
||||||
|
|
||||||
|
/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:564; */
|
||||||
|
"Disable Automatic Application Updates Check when in-game" = "Otomatik Uygulama Güncellemelerini Oyun sırasında Devre Dışı Bırak";
|
||||||
|
|
||||||
|
/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:577; */
|
||||||
|
"Preferred websites" = "Tercih edilen web siteleri";
|
||||||
|
|
||||||
|
/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:588; */
|
||||||
|
"Shipyard" = "Tersane";
|
||||||
|
|
||||||
|
/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:600; */
|
||||||
|
"Use alternate URL method" = "Alternatif URL yöntemi kullan";
|
||||||
|
|
||||||
|
/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:653; */
|
||||||
|
"Log Level" = "Günlük Seviyesi";
|
||||||
|
|
||||||
|
/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:681; */
|
||||||
|
"Configuration" = "Yapılandırma";
|
||||||
|
|
||||||
|
/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:690; */
|
||||||
|
"Main UI privacy options" = "Ana Arayüz gizlilik seçenekleri";
|
||||||
|
|
||||||
|
/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:695; */
|
||||||
|
"Hide private group name in UI" = "Özel grup ismini arayüzde gizle";
|
||||||
|
|
||||||
|
/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:699; */
|
||||||
|
"Hide multi-crew captain name" = "Çoklu-mürettebat ismini gizle";
|
||||||
|
|
||||||
|
/* prefs.py: Preferences privacy tab title; In files: prefs.py:703; */
|
||||||
|
"Privacy" = "Gizlilik";
|
||||||
|
|
||||||
|
/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:716; */
|
||||||
|
"Normal text" = "Normal metin";
|
||||||
|
|
||||||
|
/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:718; */
|
||||||
|
"Highlighted text" = "Vurgulanan metin";
|
||||||
|
|
||||||
|
/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:727; */
|
||||||
|
"Language" = "Dil";
|
||||||
|
|
||||||
|
/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:737; */
|
||||||
|
"Theme" = "Tema";
|
||||||
|
|
||||||
|
/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:749; */
|
||||||
|
"Dark" = "Karanlık";
|
||||||
|
|
||||||
|
/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:756; */
|
||||||
|
"Transparent" = "Şeffaflık";
|
||||||
|
|
||||||
|
/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:802; */
|
||||||
|
"UI Scale Percentage" = "Arayüz ölçek yüzdesi";
|
||||||
|
|
||||||
|
/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:823; */
|
||||||
|
"100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 Varsayılan değerdir. Değişikliklerin geçerli olması için {CR}Yeniden Başlatma Gereklidir{CR}!";
|
||||||
|
|
||||||
|
/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:833; */
|
||||||
|
"Main window transparency" = "Ana pencere şeffaflığı";
|
||||||
|
|
||||||
|
/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:853:856; */
|
||||||
|
"100 means fully opaque.{CR}Window is updated in real time" = "100 tamamen opak anlamına gelir.{CR}Pencere gerçek zamanlı olarak güncellenir";
|
||||||
|
|
||||||
|
/* prefs.py: Appearance option for Windows "minimize to system tray"; In files: prefs.py:885; */
|
||||||
|
"Minimize to system tray" = "Simge durumuna küçült";
|
||||||
|
|
||||||
|
/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:893; */
|
||||||
|
"Appearance" = "Görünüm";
|
||||||
|
|
||||||
|
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||||
|
"Plugins folder" = "Eklentiler klasörü";
|
||||||
|
|
||||||
|
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||||
|
"Open" = "Aç";
|
||||||
|
|
||||||
|
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||||
|
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "İpucu: Bir eklentiyi, klasör adına{CR}'{EXT}' ekleyerek devre dışı bırakabilirsiniz";
|
||||||
|
|
||||||
|
/* prefs.py: Label on list of enabled plugins; In files: prefs.py:934; */
|
||||||
|
"Enabled Plugins" = "Etkin Eklentiler";
|
||||||
|
|
||||||
|
/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:954; */
|
||||||
|
"Plugins Without Python 3.x Support" = "Python 3.x Desteği Olmayan Eklentiler";
|
||||||
|
|
||||||
|
/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */
|
||||||
|
"Information on migrating plugins" = "Eklentilerin taşınmasıyla ilgili bilgiler";
|
||||||
|
|
||||||
|
/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
|
||||||
|
"Broken Plugins" = "Bozuk Eklentiler";
|
||||||
|
|
||||||
|
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||||
|
"Disabled Plugins" = "Devre Dışı Eklentiler";
|
||||||
|
|
||||||
|
/* stats.py: Cmdr stats; In files: stats.py:58; */
|
||||||
|
"Balance" = "Bakiye";
|
||||||
|
|
||||||
|
/* stats.py: Cmdr stats; In files: stats.py:59; */
|
||||||
|
"Loan" = "Borç";
|
||||||
|
|
||||||
|
/* stats.py: Top rank; In files: stats.py:63; */
|
||||||
|
"Elite" = "Elite";
|
||||||
|
|
||||||
|
/* stats.py: Top rank +1; In files: stats.py:64; */
|
||||||
|
"Elite I" = "Elite I";
|
||||||
|
|
||||||
|
/* stats.py: Top rank +2; In files: stats.py:65; */
|
||||||
|
"Elite II" = "Elite II";
|
||||||
|
|
||||||
|
/* stats.py: Top rank +3; In files: stats.py:66; */
|
||||||
|
"Elite III" = "Elite III";
|
||||||
|
|
||||||
|
/* stats.py: Top rank +4; In files: stats.py:67; */
|
||||||
|
"Elite IV" = "Elite IV";
|
||||||
|
|
||||||
|
/* stats.py: Top rank +5; In files: stats.py:68; */
|
||||||
|
"Elite V" = "Elite V";
|
||||||
|
|
||||||
|
/* stats.py: Ranking; In files: stats.py:74; */
|
||||||
|
"Combat" = "Combat";
|
||||||
|
|
||||||
|
/* stats.py: Ranking; In files: stats.py:75; */
|
||||||
|
"Trade" = "Trade";
|
||||||
|
|
||||||
|
/* stats.py: Ranking; In files: stats.py:76; */
|
||||||
|
"Explorer" = "Explorer";
|
||||||
|
|
||||||
|
/* stats.py: Ranking; In files: stats.py:77; */
|
||||||
|
"Mercenary" = "Mercenary";
|
||||||
|
|
||||||
|
/* stats.py: Ranking; In files: stats.py:78; */
|
||||||
|
"Exobiologist" = "Exobiologist";
|
||||||
|
|
||||||
|
/* stats.py: Ranking; In files: stats.py:79; */
|
||||||
|
"CQC" = "CQC";
|
||||||
|
|
||||||
|
/* stats.py: Ranking; In files: stats.py:80; */
|
||||||
|
"Federation" = "Federation";
|
||||||
|
|
||||||
|
/* stats.py: Ranking; In files: stats.py:81; */
|
||||||
|
"Empire" = "Empire";
|
||||||
|
|
||||||
|
/* stats.py: Ranking; In files: stats.py:82; */
|
||||||
|
"Powerplay" = "Powerplay";
|
||||||
|
|
||||||
|
/* stats.py: Combat rank; In files: stats.py:91; */
|
||||||
|
"Harmless" = "Harmless";
|
||||||
|
|
||||||
|
/* stats.py: Combat rank; In files: stats.py:92; */
|
||||||
|
"Mostly Harmless" = "Mostly Harmless";
|
||||||
|
|
||||||
|
/* stats.py: Combat rank; In files: stats.py:93; */
|
||||||
|
"Novice" = "Novice";
|
||||||
|
|
||||||
|
/* stats.py: Combat rank; In files: stats.py:94; */
|
||||||
|
"Competent" = "Competent";
|
||||||
|
|
||||||
|
/* stats.py: Combat rank; In files: stats.py:95; */
|
||||||
|
"Expert" = "Expert";
|
||||||
|
|
||||||
|
/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:96; stats.py:176; */
|
||||||
|
"Master" = "Master";
|
||||||
|
|
||||||
|
/* stats.py: Combat rank; In files: stats.py:97; */
|
||||||
|
"Dangerous" = "Dangerous";
|
||||||
|
|
||||||
|
/* stats.py: Combat rank; In files: stats.py:98; */
|
||||||
|
"Deadly" = "Deadly";
|
||||||
|
|
||||||
|
/* stats.py: Trade rank; In files: stats.py:101; */
|
||||||
|
"Penniless" = "Penniless";
|
||||||
|
|
||||||
|
/* stats.py: Trade rank; In files: stats.py:102; */
|
||||||
|
"Mostly Penniless" = "Mostly Penniless";
|
||||||
|
|
||||||
|
/* stats.py: Trade rank; In files: stats.py:103; */
|
||||||
|
"Peddler" = "Peddler";
|
||||||
|
|
||||||
|
/* stats.py: Trade rank; In files: stats.py:104; */
|
||||||
|
"Dealer" = "Dealer";
|
||||||
|
|
||||||
|
/* stats.py: Trade rank; In files: stats.py:105; */
|
||||||
|
"Merchant" = "Merchant";
|
||||||
|
|
||||||
|
/* stats.py: Trade rank; In files: stats.py:106; */
|
||||||
|
"Broker" = "Broker";
|
||||||
|
|
||||||
|
/* stats.py: Trade rank; In files: stats.py:107; */
|
||||||
|
"Entrepreneur" = "Entrepreneur";
|
||||||
|
|
||||||
|
/* stats.py: Trade rank; In files: stats.py:108; */
|
||||||
|
"Tycoon" = "Tycoon";
|
||||||
|
|
||||||
|
/* stats.py: Explorer rank; In files: stats.py:111; */
|
||||||
|
"Aimless" = "Aimless";
|
||||||
|
|
||||||
|
/* stats.py: Explorer rank; In files: stats.py:112; */
|
||||||
|
"Mostly Aimless" = "Mostly Aimless";
|
||||||
|
|
||||||
|
/* stats.py: Explorer rank; In files: stats.py:113; */
|
||||||
|
"Scout" = "Scout";
|
||||||
|
|
||||||
|
/* stats.py: Explorer rank; In files: stats.py:114; */
|
||||||
|
"Surveyor" = "Surveyor";
|
||||||
|
|
||||||
|
/* stats.py: Explorer rank; In files: stats.py:115; */
|
||||||
|
"Trailblazer" = "Trailblazer";
|
||||||
|
|
||||||
|
/* stats.py: Explorer rank; In files: stats.py:116; */
|
||||||
|
"Pathfinder" = "Pathfinder";
|
||||||
|
|
||||||
|
/* stats.py: Explorer rank; In files: stats.py:117; */
|
||||||
|
"Ranger" = "Ranger";
|
||||||
|
|
||||||
|
/* stats.py: Explorer rank; In files: stats.py:118; */
|
||||||
|
"Pioneer" = "Pioneer";
|
||||||
|
|
||||||
|
/* stats.py: Mercenary rank; In files: stats.py:122; */
|
||||||
|
"Defenceless" = "Defenceless";
|
||||||
|
|
||||||
|
/* stats.py: Mercenary rank; In files: stats.py:123; */
|
||||||
|
"Mostly Defenceless" = "Mostly Defenceless";
|
||||||
|
|
||||||
|
/* stats.py: Mercenary rank; In files: stats.py:124; */
|
||||||
|
"Rookie" = "Rookie";
|
||||||
|
|
||||||
|
/* stats.py: Mercenary rank; In files: stats.py:125; */
|
||||||
|
"Soldier" = "Soldier";
|
||||||
|
|
||||||
|
/* stats.py: Mercenary rank; In files: stats.py:126; stats.py:128; */
|
||||||
|
"Gunslinger" = "Gunslinger";
|
||||||
|
|
||||||
|
/* stats.py: Mercenary rank; In files: stats.py:127; */
|
||||||
|
"Warrior" = "Warrior";
|
||||||
|
|
||||||
|
/* stats.py: Mercenary rank; In files: stats.py:129; */
|
||||||
|
"Deadeye" = "Deadeye";
|
||||||
|
|
||||||
|
/* stats.py: Exobiologist rank; In files: stats.py:132; */
|
||||||
|
"Directionless" = "Directionless";
|
||||||
|
|
||||||
|
/* stats.py: Exobiologist rank; In files: stats.py:133; */
|
||||||
|
"Mostly Directionless" = "Mostly Directionless";
|
||||||
|
|
||||||
|
/* stats.py: Exobiologist rank; In files: stats.py:134; */
|
||||||
|
"Compiler" = "Compiler";
|
||||||
|
|
||||||
|
/* stats.py: Exobiologist rank; In files: stats.py:135; */
|
||||||
|
"Collector" = "Collector";
|
||||||
|
|
||||||
|
/* stats.py: Exobiologist rank; In files: stats.py:136; */
|
||||||
|
"Cataloguer" = "Cataloguer";
|
||||||
|
|
||||||
|
/* stats.py: Exobiologist rank; In files: stats.py:137; */
|
||||||
|
"Taxonomist" = "Taxonomist";
|
||||||
|
|
||||||
|
/* stats.py: Exobiologist rank; In files: stats.py:138; */
|
||||||
|
"Ecologist" = "Ecologist";
|
||||||
|
|
||||||
|
/* stats.py: Exobiologist rank; In files: stats.py:139; */
|
||||||
|
"Geneticist" = "Geneticist";
|
||||||
|
|
||||||
|
/* stats.py: CQC rank; In files: stats.py:142; */
|
||||||
|
"Helpless" = "Helpless";
|
||||||
|
|
||||||
|
/* stats.py: CQC rank; In files: stats.py:143; */
|
||||||
|
"Mostly Helpless" = "Mostly Helpless";
|
||||||
|
|
||||||
|
/* stats.py: CQC rank; In files: stats.py:144; */
|
||||||
|
"Amateur" = "Amateur";
|
||||||
|
|
||||||
|
/* stats.py: CQC rank; In files: stats.py:145; */
|
||||||
|
"Semi Professional" = "Semi Professional";
|
||||||
|
|
||||||
|
/* stats.py: CQC rank; In files: stats.py:146; */
|
||||||
|
"Professional" = "Professional";
|
||||||
|
|
||||||
|
/* stats.py: CQC rank; In files: stats.py:147; */
|
||||||
|
"Champion" = "Champion";
|
||||||
|
|
||||||
|
/* stats.py: CQC rank; In files: stats.py:148; */
|
||||||
|
"Hero" = "Hero";
|
||||||
|
|
||||||
|
/* stats.py: CQC rank; In files: stats.py:149; */
|
||||||
|
"Gladiator" = "Gladiator";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:155; */
|
||||||
|
"Recruit" = "Recruit";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:156; */
|
||||||
|
"Cadet" = "Cadet";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:157; */
|
||||||
|
"Midshipman" = "Midshipman";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:158; */
|
||||||
|
"Petty Officer" = "Petty Officer";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:159; */
|
||||||
|
"Chief Petty Officer" = "Chief Petty Officer";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:160; */
|
||||||
|
"Warrant Officer" = "Warrant Officer";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:161; */
|
||||||
|
"Ensign" = "Ensign";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:162; */
|
||||||
|
"Lieutenant" = "Lieutenant";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:163; */
|
||||||
|
"Lieutenant Commander" = "Lieutenant Commander";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:164; */
|
||||||
|
"Post Commander" = "Post Commander";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:165; */
|
||||||
|
"Post Captain" = "Post Captain";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:166; */
|
||||||
|
"Rear Admiral" = "Rear Admiral";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:167; */
|
||||||
|
"Vice Admiral" = "Vice Admiral";
|
||||||
|
|
||||||
|
/* stats.py: Federation rank; In files: stats.py:168; */
|
||||||
|
"Admiral" = "Admiral";
|
||||||
|
|
||||||
|
/* stats.py: Empire rank; In files: stats.py:174; */
|
||||||
|
"Outsider" = "Outsider";
|
||||||
|
|
||||||
|
/* stats.py: Empire rank; In files: stats.py:175; */
|
||||||
|
"Serf" = "Serf";
|
||||||
|
|
||||||
|
/* stats.py: Empire rank; In files: stats.py:177; */
|
||||||
|
"Squire" = "Squire";
|
||||||
|
|
||||||
|
/* stats.py: Empire rank; In files: stats.py:178; */
|
||||||
|
"Knight" = "Knight";
|
||||||
|
|
||||||
|
/* stats.py: Empire rank; In files: stats.py:179; */
|
||||||
|
"Lord" = "Lord";
|
||||||
|
|
||||||
|
/* stats.py: Empire rank; In files: stats.py:180; */
|
||||||
|
"Baron" = "Baron";
|
||||||
|
|
||||||
|
/* stats.py: Empire rank; In files: stats.py:181; */
|
||||||
|
"Viscount" = "Viscount";
|
||||||
|
|
||||||
|
/* stats.py: Empire rank; In files: stats.py:182; */
|
||||||
|
"Count" = "Count";
|
||||||
|
|
||||||
|
/* stats.py: Empire rank; In files: stats.py:183; */
|
||||||
|
"Earl" = "Earl";
|
||||||
|
|
||||||
|
/* stats.py: Empire rank; In files: stats.py:184; */
|
||||||
|
"Marquis" = "Marquis";
|
||||||
|
|
||||||
|
/* stats.py: Empire rank; In files: stats.py:185; */
|
||||||
|
"Duke" = "Duke";
|
||||||
|
|
||||||
|
/* stats.py: Empire rank; In files: stats.py:186; */
|
||||||
|
"Prince" = "Prince";
|
||||||
|
|
||||||
|
/* stats.py: Empire rank; In files: stats.py:187; */
|
||||||
|
"King" = "King";
|
||||||
|
|
||||||
|
/* stats.py: Power rank; In files: stats.py:193; */
|
||||||
|
"Rating 1" = "Reyting 1";
|
||||||
|
|
||||||
|
/* stats.py: Power rank; In files: stats.py:194; */
|
||||||
|
"Rating 2" = "Reyting 2";
|
||||||
|
|
||||||
|
/* stats.py: Power rank; In files: stats.py:195; */
|
||||||
|
"Rating 3" = "Reyting 3";
|
||||||
|
|
||||||
|
/* stats.py: Power rank; In files: stats.py:196; */
|
||||||
|
"Rating 4" = "Reyting 4";
|
||||||
|
|
||||||
|
/* stats.py: Power rank; In files: stats.py:197; */
|
||||||
|
"Rating 5" = "Reyting 5";
|
||||||
|
|
||||||
|
/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:315; */
|
||||||
|
"Status: Don't yet know your Commander name" = "Durum: Cmdr adınızı henüz bilmiyorum";
|
||||||
|
|
||||||
|
/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:323; */
|
||||||
|
"Status: No CAPI data yet" = "Durum: Henüz CAPI verisi yok";
|
||||||
|
|
||||||
|
/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:409; */
|
||||||
|
"Value" = "Değer";
|
||||||
|
|
||||||
|
/* stats.py: Status dialog title; In files: stats.py:418; */
|
||||||
|
"Ships" = "Gemiler";
|
||||||
|
|
||||||
|
/* update.py: Update Available Text; In files: update.py:229; */
|
||||||
|
"{NEWVER} is available" = "{NEWVER} sürüm mevcut";
|
@ -226,13 +226,13 @@
|
|||||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||||
"Default" = "默认";
|
"Default" = "默认";
|
||||||
|
|
||||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
|
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||||
"Auto" = "自动";
|
"Auto" = "自动";
|
||||||
|
|
||||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
|
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||||
"Normal" = "普通版";
|
"Normal" = "普通版";
|
||||||
|
|
||||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
|
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||||
"Beta" = "测试版";
|
"Beta" = "测试版";
|
||||||
|
|
||||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||||
|
3
build.py
3
build.py
@ -76,10 +76,8 @@ def generate_data_files(
|
|||||||
"ChangeLog.md",
|
"ChangeLog.md",
|
||||||
"snd_good.wav",
|
"snd_good.wav",
|
||||||
"snd_bad.wav",
|
"snd_bad.wav",
|
||||||
"modules.p", # TODO: Remove in 6.0
|
|
||||||
"modules.json",
|
"modules.json",
|
||||||
"ships.json",
|
"ships.json",
|
||||||
"ships.p", # TODO: Remove in 6.0
|
|
||||||
f"{app_name}.ico",
|
f"{app_name}.ico",
|
||||||
f"resources/{appcmdname}.ico",
|
f"resources/{appcmdname}.ico",
|
||||||
"EDMarketConnector - TRACE.bat",
|
"EDMarketConnector - TRACE.bat",
|
||||||
@ -133,7 +131,6 @@ def build() -> None:
|
|||||||
"distutils",
|
"distutils",
|
||||||
"_markerlib",
|
"_markerlib",
|
||||||
"optparse",
|
"optparse",
|
||||||
"PIL",
|
|
||||||
"simplejson",
|
"simplejson",
|
||||||
"unittest",
|
"unittest",
|
||||||
"doctest",
|
"doctest",
|
||||||
|
@ -7,7 +7,6 @@ See LICENSE file.
|
|||||||
|
|
||||||
Windows uses the Registry to store values in a flat manner.
|
Windows uses the Registry to store values in a flat manner.
|
||||||
Linux uses a file, but for commonality it's still a flat data structure.
|
Linux uses a file, but for commonality it's still a flat data structure.
|
||||||
macOS uses a 'defaults' object.
|
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@ -54,7 +53,7 @@ appcmdname = 'EDMC'
|
|||||||
# <https://semver.org/#semantic-versioning-specification-semver>
|
# <https://semver.org/#semantic-versioning-specification-semver>
|
||||||
# Major.Minor.Patch(-prerelease)(+buildmetadata)
|
# Major.Minor.Patch(-prerelease)(+buildmetadata)
|
||||||
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
|
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
|
||||||
_static_appversion = '5.10.3'
|
_static_appversion = '5.10.4'
|
||||||
|
|
||||||
_cached_version: semantic_version.Version | None = None
|
_cached_version: semantic_version.Version | None = None
|
||||||
copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'
|
copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'
|
||||||
@ -468,10 +467,6 @@ def get_config(*args, **kwargs) -> AbstractConfig:
|
|||||||
:param kwargs: Args to be passed through to implementation.
|
:param kwargs: Args to be passed through to implementation.
|
||||||
:return: Instance of the implementation.
|
:return: Instance of the implementation.
|
||||||
"""
|
"""
|
||||||
if sys.platform == "darwin": # pragma: sys-platform-darwin
|
|
||||||
from .darwin import MacConfig
|
|
||||||
return MacConfig(*args, **kwargs)
|
|
||||||
|
|
||||||
if sys.platform == "win32": # pragma: sys-platform-win32
|
if sys.platform == "win32": # pragma: sys-platform-win32
|
||||||
from .windows import WinConfig
|
from .windows import WinConfig
|
||||||
return WinConfig(*args, **kwargs)
|
return WinConfig(*args, **kwargs)
|
||||||
|
191
config/darwin.py
191
config/darwin.py
@ -1,191 +0,0 @@
|
|||||||
"""
|
|
||||||
darwin.py - Darwin/macOS implementation of AbstractConfig.
|
|
||||||
|
|
||||||
Copyright (c) EDCD, All Rights Reserved
|
|
||||||
Licensed under the GNU General Public License.
|
|
||||||
See LICENSE file.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pathlib
|
|
||||||
import sys
|
|
||||||
from typing import Any
|
|
||||||
from Foundation import ( # type: ignore
|
|
||||||
NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains, NSUserDefaults,
|
|
||||||
NSUserDomainMask
|
|
||||||
)
|
|
||||||
from config import AbstractConfig, appname, logger
|
|
||||||
|
|
||||||
assert sys.platform == 'darwin'
|
|
||||||
|
|
||||||
|
|
||||||
class MacConfig(AbstractConfig):
|
|
||||||
"""MacConfig is the implementation of AbstractConfig for Darwin based OSes."""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
super().__init__()
|
|
||||||
support_path = pathlib.Path(
|
|
||||||
NSSearchPathForDirectoriesInDomains(
|
|
||||||
NSApplicationSupportDirectory, NSUserDomainMask, True
|
|
||||||
)[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.app_dir_path = support_path / appname
|
|
||||||
self.app_dir_path.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
self.plugin_dir_path = self.app_dir_path / 'plugins'
|
|
||||||
self.plugin_dir_path.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
# Bundle IDs identify a singled app though out a system
|
|
||||||
|
|
||||||
if getattr(sys, 'frozen', False):
|
|
||||||
exe_dir = pathlib.Path(sys.executable).parent
|
|
||||||
self.internal_plugin_dir_path = exe_dir.parent / 'Library' / 'plugins'
|
|
||||||
self.respath_path = exe_dir.parent / 'Resources'
|
|
||||||
self.identifier = NSBundle.mainBundle().bundleIdentifier()
|
|
||||||
|
|
||||||
else:
|
|
||||||
file_dir = pathlib.Path(__file__).parent.parent
|
|
||||||
self.internal_plugin_dir_path = file_dir / 'plugins'
|
|
||||||
self.respath_path = file_dir
|
|
||||||
|
|
||||||
self.identifier = f'uk.org.marginal.{appname.lower()}'
|
|
||||||
NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier
|
|
||||||
|
|
||||||
self.default_journal_dir_path = support_path / 'Frontier Developments' / 'Elite Dangerous'
|
|
||||||
self._defaults: Any = NSUserDefaults.standardUserDefaults()
|
|
||||||
self._settings: dict[str, int | str | list] = dict(
|
|
||||||
self._defaults.persistentDomainForName_(self.identifier) or {}
|
|
||||||
) # make writeable
|
|
||||||
|
|
||||||
if (out_dir := self.get_str('out_dir')) is None or not pathlib.Path(out_dir).exists():
|
|
||||||
self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0])
|
|
||||||
|
|
||||||
def __raw_get(self, key: str) -> None | list | str | int:
|
|
||||||
"""
|
|
||||||
Retrieve the raw data for the given key.
|
|
||||||
|
|
||||||
:param str: str - The key data is being requested for.
|
|
||||||
:return: The requested data.
|
|
||||||
"""
|
|
||||||
res = self._settings.get(key)
|
|
||||||
# On MacOS Catalina, with python.org python 3.9.2 any 'list'
|
|
||||||
# has type __NSCFArray so a simple `isinstance(res, list)` is
|
|
||||||
# False. So, check it's not-None, and not the other types.
|
|
||||||
#
|
|
||||||
# If we can find where to import the definition of NSCFArray
|
|
||||||
# then we could possibly test against that.
|
|
||||||
if res is not None and not isinstance(res, str) and not isinstance(res, int):
|
|
||||||
return list(res)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
def get_str(self, key: str, *, default: str = None) -> str:
|
|
||||||
"""
|
|
||||||
Return the string referred to by the given key if it exists, or the default.
|
|
||||||
|
|
||||||
Implements :meth:`AbstractConfig.get_str`.
|
|
||||||
"""
|
|
||||||
res = self.__raw_get(key)
|
|
||||||
if res is None:
|
|
||||||
return default # Yes it could be None, but we're _assuming_ that people gave us a default
|
|
||||||
|
|
||||||
if not isinstance(res, str):
|
|
||||||
raise ValueError(f'unexpected data returned from __raw_get: {type(res)=} {res}')
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
def get_list(self, key: str, *, default: list = None) -> list:
|
|
||||||
"""
|
|
||||||
Return the list referred to by the given key if it exists, or the default.
|
|
||||||
|
|
||||||
Implements :meth:`AbstractConfig.get_list`.
|
|
||||||
"""
|
|
||||||
res = self.__raw_get(key)
|
|
||||||
if res is None:
|
|
||||||
return default # Yes it could be None, but we're _assuming_ that people gave us a default
|
|
||||||
|
|
||||||
if not isinstance(res, list):
|
|
||||||
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
def get_int(self, key: str, *, default: int = 0) -> int:
|
|
||||||
"""
|
|
||||||
Return the int referred to by key if it exists in the config.
|
|
||||||
|
|
||||||
Implements :meth:`AbstractConfig.get_int`.
|
|
||||||
"""
|
|
||||||
res = self.__raw_get(key)
|
|
||||||
if res is None:
|
|
||||||
return default
|
|
||||||
|
|
||||||
if not isinstance(res, (str, int)):
|
|
||||||
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
|
|
||||||
|
|
||||||
try:
|
|
||||||
return int(res)
|
|
||||||
|
|
||||||
except ValueError as e:
|
|
||||||
logger.error(f'__raw_get returned {res!r} which cannot be parsed to an int: {e}')
|
|
||||||
return default # Yes it could be None, but we're _assuming_ that people gave us a default
|
|
||||||
|
|
||||||
def get_bool(self, key: str, *, default: bool = None) -> bool:
|
|
||||||
"""
|
|
||||||
Return the bool referred to by the given key if it exists, or the default.
|
|
||||||
|
|
||||||
Implements :meth:`AbstractConfig.get_bool`.
|
|
||||||
"""
|
|
||||||
res = self.__raw_get(key)
|
|
||||||
if res is None:
|
|
||||||
return default # Yes it could be None, but we're _assuming_ that people gave us a default
|
|
||||||
|
|
||||||
if not isinstance(res, bool):
|
|
||||||
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
def set(self, key: str, val: int | str | list[str] | bool) -> None:
|
|
||||||
"""
|
|
||||||
Set the given key's data to the given value.
|
|
||||||
|
|
||||||
Implements :meth:`AbstractConfig.set`.
|
|
||||||
"""
|
|
||||||
if self._settings is None:
|
|
||||||
raise ValueError('attempt to use a closed _settings')
|
|
||||||
|
|
||||||
if not isinstance(val, (bool, str, int, list)):
|
|
||||||
raise ValueError(f'Unexpected type for value {type(val)=}')
|
|
||||||
|
|
||||||
self._settings[key] = val
|
|
||||||
|
|
||||||
def delete(self, key: str, *, suppress=False) -> None:
|
|
||||||
"""
|
|
||||||
Delete the given key from the config.
|
|
||||||
|
|
||||||
Implements :meth:`AbstractConfig.delete`.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
del self._settings[key]
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
if suppress:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def save(self) -> None:
|
|
||||||
"""
|
|
||||||
Save the current configuration.
|
|
||||||
|
|
||||||
Implements :meth:`AbstractConfig.save`.
|
|
||||||
"""
|
|
||||||
self._defaults.setPersistentDomain_forName_(self._settings, self.identifier)
|
|
||||||
self._defaults.synchronize()
|
|
||||||
|
|
||||||
def close(self) -> None:
|
|
||||||
"""
|
|
||||||
Close this config and release any associated resources.
|
|
||||||
|
|
||||||
Implements :meth:`AbstractConfig.close`.
|
|
||||||
"""
|
|
||||||
self.save()
|
|
||||||
self._defaults = None
|
|
@ -5,6 +5,8 @@ Copyright (c) EDCD, All Rights Reserved
|
|||||||
Licensed under the GNU General Public License.
|
Licensed under the GNU General Public License.
|
||||||
See LICENSE file.
|
See LICENSE file.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import sys
|
import sys
|
||||||
|
@ -20,13 +20,13 @@ from EDMCLogging import get_main_logger
|
|||||||
|
|
||||||
logger = get_main_logger()
|
logger = get_main_logger()
|
||||||
|
|
||||||
if sys.platform in ('darwin', 'win32'):
|
if sys.platform == 'win32':
|
||||||
from watchdog.events import FileSystemEventHandler
|
from watchdog.events import FileSystemEventHandler
|
||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
else:
|
else:
|
||||||
# Linux's inotify doesn't work over CIFS or NFS, so poll
|
# Linux's inotify doesn't work over CIFS or NFS, so poll
|
||||||
class FileSystemEventHandler: # type: ignore
|
class FileSystemEventHandler: # type: ignore
|
||||||
"""Dummy class to represent a file system event handler on platforms other than macOS and Windows."""
|
"""Dummy class to represent a file system event handler on platforms other than Windows."""
|
||||||
|
|
||||||
|
|
||||||
class Dashboard(FileSystemEventHandler):
|
class Dashboard(FileSystemEventHandler):
|
||||||
@ -160,7 +160,7 @@ class Dashboard(FileSystemEventHandler):
|
|||||||
|
|
||||||
def on_modified(self, event) -> None:
|
def on_modified(self, event) -> None:
|
||||||
"""
|
"""
|
||||||
Watchdog callback - DirModifiedEvent on macOS, FileModifiedEvent on Windows.
|
Watchdog callback - FileModifiedEvent on Windows.
|
||||||
|
|
||||||
:param event: Watchdog event.
|
:param event: Watchdog event.
|
||||||
"""
|
"""
|
||||||
|
@ -7,6 +7,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
|
||||||
import myNotebook as nb # noqa: N813
|
import myNotebook as nb # noqa: N813
|
||||||
from config import appname, config
|
from config import appname, config
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ class ClickCounter:
|
|||||||
"""
|
"""
|
||||||
self.on_preferences_closed("", False) # Save our prefs
|
self.on_preferences_closed("", False) # Save our prefs
|
||||||
|
|
||||||
def setup_preferences(self, parent: nb.Notebook, cmdr: str, is_beta: bool) -> tk.Frame | None:
|
def setup_preferences(self, parent: nb.Notebook, cmdr: str, is_beta: bool) -> nb.Frame | None:
|
||||||
"""
|
"""
|
||||||
setup_preferences is called by plugin_prefs below.
|
setup_preferences is called by plugin_prefs below.
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ class ClickCounter:
|
|||||||
|
|
||||||
# setup our config in a "Click Count: number"
|
# setup our config in a "Click Count: number"
|
||||||
nb.Label(frame, text='Click Count').grid(row=current_row)
|
nb.Label(frame, text='Click Count').grid(row=current_row)
|
||||||
nb.Entry(frame, textvariable=self.click_count).grid(row=current_row, column=1)
|
nb.EntryMenu(frame, textvariable=self.click_count).grid(row=current_row, column=1)
|
||||||
current_row += 1 # Always increment our row counter, makes for far easier tkinter design.
|
current_row += 1 # Always increment our row counter, makes for far easier tkinter design.
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
@ -126,7 +127,7 @@ def plugin_stop() -> None:
|
|||||||
return cc.on_unload()
|
return cc.on_unload()
|
||||||
|
|
||||||
|
|
||||||
def plugin_prefs(parent: nb.Notebook, cmdr: str, is_beta: bool) -> tk.Frame | None:
|
def plugin_prefs(parent: nb.Notebook, cmdr: str, is_beta: bool) -> nb.Frame | None:
|
||||||
"""
|
"""
|
||||||
Handle preferences tab for the plugin.
|
Handle preferences tab for the plugin.
|
||||||
|
|
||||||
|
31
edmc_data.py
31
edmc_data.py
@ -69,6 +69,8 @@ outfitting_weapon_map = {
|
|||||||
'advancedtorppylon': 'Torpedo Pylon',
|
'advancedtorppylon': 'Torpedo Pylon',
|
||||||
'atdumbfiremissile': 'AX Missile Rack',
|
'atdumbfiremissile': 'AX Missile Rack',
|
||||||
'atmulticannon': 'AX Multi-Cannon',
|
'atmulticannon': 'AX Multi-Cannon',
|
||||||
|
('atmulticannon', 'v2'): 'Enhanced AX Multi-Cannon',
|
||||||
|
('atdumbfiremissile', 'v2'): 'Enhanced AX Missile Rack',
|
||||||
'basicmissilerack': 'Seeker Missile Rack',
|
'basicmissilerack': 'Seeker Missile Rack',
|
||||||
'beamlaser': 'Beam Laser',
|
'beamlaser': 'Beam Laser',
|
||||||
('beamlaser', 'heat'): 'Retributor Beam Laser',
|
('beamlaser', 'heat'): 'Retributor Beam Laser',
|
||||||
@ -88,6 +90,8 @@ outfitting_weapon_map = {
|
|||||||
'mining_abrblstr': 'Abrasion Blaster',
|
'mining_abrblstr': 'Abrasion Blaster',
|
||||||
'mining_seismchrgwarhd': 'Seismic Charge Launcher',
|
'mining_seismchrgwarhd': 'Seismic Charge Launcher',
|
||||||
'mining_subsurfdispmisle': 'Sub-Surface Displacement Missile',
|
'mining_subsurfdispmisle': 'Sub-Surface Displacement Missile',
|
||||||
|
'human_extraction': 'Sub-Surface Extraction Missile',
|
||||||
|
'atventdisruptorpylon': 'Guardian Nanite Torpedo Pylon',
|
||||||
'mininglaser': 'Mining Laser',
|
'mininglaser': 'Mining Laser',
|
||||||
('mininglaser', 'advanced'): 'Mining Lance Beam Laser',
|
('mininglaser', 'advanced'): 'Mining Lance Beam Laser',
|
||||||
'multicannon': 'Multi-Cannon',
|
'multicannon': 'Multi-Cannon',
|
||||||
@ -266,6 +270,11 @@ outfitting_weaponrating_map = {
|
|||||||
'hpt_slugshot_turret_medium': 'D',
|
'hpt_slugshot_turret_medium': 'D',
|
||||||
'hpt_slugshot_turret_large': 'C',
|
'hpt_slugshot_turret_large': 'C',
|
||||||
'hpt_xenoscannermk2_basic_tiny': '?',
|
'hpt_xenoscannermk2_basic_tiny': '?',
|
||||||
|
'hpt_atmulticannon_gimbal_large': 'C',
|
||||||
|
'hpt_atmulticannon_gimbal_medium': 'E',
|
||||||
|
'hpt_human_extraction_fixed_medium': 'B',
|
||||||
|
'hpt_atventdisruptorpylon_fixed_medium': 'I',
|
||||||
|
'hpt_atventdisruptorpylon_fixed_large': 'I',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Old standard weapon variants
|
# Old standard weapon variants
|
||||||
@ -278,13 +287,14 @@ outfitting_weaponoldvariant_map = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
outfitting_countermeasure_map = {
|
outfitting_countermeasure_map = {
|
||||||
'antiunknownshutdown': ('Shutdown Field Neutraliser', 'F'),
|
'antiunknownshutdown': ('Shutdown Field Neutraliser', 'F'),
|
||||||
'chafflauncher': ('Chaff Launcher', 'I'),
|
('antiunknownshutdown', 'v2'): ('Thargoid Pulse Neutraliser', 'E'),
|
||||||
'electroniccountermeasure': ('Electronic Countermeasure', 'F'),
|
'chafflauncher': ('Chaff Launcher', 'I'),
|
||||||
'heatsinklauncher': ('Heat Sink Launcher', 'I'),
|
'electroniccountermeasure': ('Electronic Countermeasure', 'F'),
|
||||||
'plasmapointdefence': ('Point Defence', 'I'),
|
'heatsinklauncher': ('Heat Sink Launcher', 'I'),
|
||||||
'xenoscanner': ('Xeno Scanner', 'E'),
|
'plasmapointdefence': ('Point Defence', 'I'),
|
||||||
'xenoscannermk2': ('Unknown Xeno Scanner Mk II', '?'),
|
'xenoscanner': ('Xeno Scanner', 'E'),
|
||||||
|
'xenoscannermk2': ('Unknown Xeno Scanner Mk II', '?'),
|
||||||
}
|
}
|
||||||
|
|
||||||
outfitting_utility_map = {
|
outfitting_utility_map = {
|
||||||
@ -347,6 +357,7 @@ outfitting_standard_map = {
|
|||||||
'guardianpowerdistributor': 'Guardian Hybrid Power Distributor',
|
'guardianpowerdistributor': 'Guardian Hybrid Power Distributor',
|
||||||
'guardianpowerplant': 'Guardian Hybrid Power Plant',
|
'guardianpowerplant': 'Guardian Hybrid Power Plant',
|
||||||
'hyperdrive': 'Frame Shift Drive',
|
'hyperdrive': 'Frame Shift Drive',
|
||||||
|
('hyperdrive', 'overcharge'): 'Frame Shift Drive',
|
||||||
'lifesupport': 'Life Support',
|
'lifesupport': 'Life Support',
|
||||||
# 'planetapproachsuite': handled separately
|
# 'planetapproachsuite': handled separately
|
||||||
'powerdistributor': 'Power Distributor',
|
'powerdistributor': 'Power Distributor',
|
||||||
@ -376,6 +387,11 @@ outfitting_internal_map = {
|
|||||||
'refinery': 'Refinery',
|
'refinery': 'Refinery',
|
||||||
'recon': 'Recon Limpet Controller',
|
'recon': 'Recon Limpet Controller',
|
||||||
'repair': 'Repair Limpet Controller',
|
'repair': 'Repair Limpet Controller',
|
||||||
|
'rescue': 'Rescue Limpet Controller',
|
||||||
|
'mining': 'Mining Multi Limpet Controller',
|
||||||
|
'xeno': 'Xeno Multi Limpet Controller',
|
||||||
|
'operations': 'Operations Multi Limpet Controller',
|
||||||
|
'universal': 'Universal Multi Limpet Controller',
|
||||||
'repairer': 'Auto Field-Maintenance Unit',
|
'repairer': 'Auto Field-Maintenance Unit',
|
||||||
'resourcesiphon': 'Hatch Breaker Limpet Controller',
|
'resourcesiphon': 'Hatch Breaker Limpet Controller',
|
||||||
'shieldcellbank': 'Shield Cell Bank',
|
'shieldcellbank': 'Shield Cell Bank',
|
||||||
@ -383,6 +399,7 @@ outfitting_internal_map = {
|
|||||||
('shieldgenerator', 'fast'): 'Bi-Weave Shield Generator',
|
('shieldgenerator', 'fast'): 'Bi-Weave Shield Generator',
|
||||||
('shieldgenerator', 'strong'): 'Prismatic Shield Generator',
|
('shieldgenerator', 'strong'): 'Prismatic Shield Generator',
|
||||||
'unkvesselresearch': 'Research Limpet Controller',
|
'unkvesselresearch': 'Research Limpet Controller',
|
||||||
|
'expmodulestabiliser': 'Experimental Weapon Stabiliser',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Dashboard Flags constants
|
# Dashboard Flags constants
|
||||||
|
@ -76,10 +76,6 @@ def get_hotkeymgr() -> AbstractHotkeyMgr:
|
|||||||
:return: Appropriate class instance.
|
:return: Appropriate class instance.
|
||||||
:raises ValueError: If unsupported platform.
|
:raises ValueError: If unsupported platform.
|
||||||
"""
|
"""
|
||||||
if sys.platform == 'darwin':
|
|
||||||
from hotkey.darwin import MacHotkeyMgr
|
|
||||||
return MacHotkeyMgr()
|
|
||||||
|
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
from hotkey.windows import WindowsHotkeyMgr
|
from hotkey.windows import WindowsHotkeyMgr
|
||||||
return WindowsHotkeyMgr()
|
return WindowsHotkeyMgr()
|
||||||
|
276
hotkey/darwin.py
276
hotkey/darwin.py
@ -1,276 +0,0 @@
|
|||||||
"""darwin/macOS implementation of hotkey.AbstractHotkeyMgr."""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pathlib
|
|
||||||
import sys
|
|
||||||
import tkinter as tk
|
|
||||||
from typing import Callable
|
|
||||||
assert sys.platform == 'darwin'
|
|
||||||
|
|
||||||
import objc
|
|
||||||
from AppKit import (
|
|
||||||
NSAlternateKeyMask, NSApplication, NSBeep, NSClearLineFunctionKey, NSCommandKeyMask, NSControlKeyMask,
|
|
||||||
NSDeleteFunctionKey, NSDeviceIndependentModifierFlagsMask, NSEvent, NSF1FunctionKey, NSF35FunctionKey,
|
|
||||||
NSFlagsChanged, NSKeyDown, NSKeyDownMask, NSKeyUp, NSNumericPadKeyMask, NSShiftKeyMask, NSSound, NSWorkspace
|
|
||||||
)
|
|
||||||
|
|
||||||
from config import config
|
|
||||||
from EDMCLogging import get_main_logger
|
|
||||||
from hotkey import AbstractHotkeyMgr
|
|
||||||
|
|
||||||
logger = get_main_logger()
|
|
||||||
|
|
||||||
|
|
||||||
class MacHotkeyMgr(AbstractHotkeyMgr):
|
|
||||||
"""Hot key management."""
|
|
||||||
|
|
||||||
POLL = 250
|
|
||||||
# https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSEvent_Class/#//apple_ref/doc/constant_group/Function_Key_Unicodes
|
|
||||||
DISPLAY = {
|
|
||||||
0x03: u'⌅', 0x09: u'⇥', 0xd: u'↩', 0x19: u'⇤', 0x1b: u'esc', 0x20: u'⏘', 0x7f: u'⌫',
|
|
||||||
0xf700: u'↑', 0xf701: u'↓', 0xf702: u'←', 0xf703: u'→',
|
|
||||||
0xf727: u'Ins',
|
|
||||||
0xf728: u'⌦', 0xf729: u'↖', 0xf72a: u'Fn', 0xf72b: u'↘',
|
|
||||||
0xf72c: u'⇞', 0xf72d: u'⇟', 0xf72e: u'PrtScr', 0xf72f: u'ScrollLock',
|
|
||||||
0xf730: u'Pause', 0xf731: u'SysReq', 0xf732: u'Break', 0xf733: u'Reset',
|
|
||||||
0xf739: u'⌧',
|
|
||||||
}
|
|
||||||
(ACQUIRE_INACTIVE, ACQUIRE_ACTIVE, ACQUIRE_NEW) = range(3)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.MODIFIERMASK = NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask \
|
|
||||||
| NSNumericPadKeyMask
|
|
||||||
self.root: tk.Tk
|
|
||||||
|
|
||||||
self.keycode = 0
|
|
||||||
self.modifiers = 0
|
|
||||||
self.activated = False
|
|
||||||
self.observer = None
|
|
||||||
|
|
||||||
self.acquire_key = 0
|
|
||||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
|
||||||
|
|
||||||
self.tkProcessKeyEvent_old: Callable
|
|
||||||
|
|
||||||
self.snd_good = NSSound.alloc().initWithContentsOfFile_byReference_(
|
|
||||||
pathlib.Path(config.respath_path) / 'snd_good.wav', False
|
|
||||||
)
|
|
||||||
self.snd_bad = NSSound.alloc().initWithContentsOfFile_byReference_(
|
|
||||||
pathlib.Path(config.respath_path) / 'snd_bad.wav', False
|
|
||||||
)
|
|
||||||
|
|
||||||
def register(self, root: tk.Tk, keycode: int, modifiers: int) -> None:
|
|
||||||
"""
|
|
||||||
Register current hotkey for monitoring.
|
|
||||||
|
|
||||||
:param root: parent window.
|
|
||||||
:param keycode: Key to monitor.
|
|
||||||
:param modifiers: Any modifiers to take into account.
|
|
||||||
"""
|
|
||||||
self.root = root
|
|
||||||
self.keycode = keycode
|
|
||||||
self.modifiers = modifiers
|
|
||||||
self.activated = False
|
|
||||||
|
|
||||||
if keycode:
|
|
||||||
if not self.observer:
|
|
||||||
self.root.after_idle(self._observe)
|
|
||||||
self.root.after(MacHotkeyMgr.POLL, self._poll)
|
|
||||||
|
|
||||||
# Monkey-patch tk (tkMacOSXKeyEvent.c)
|
|
||||||
if not callable(self.tkProcessKeyEvent_old):
|
|
||||||
sel = b'tkProcessKeyEvent:'
|
|
||||||
cls = NSApplication.sharedApplication().class__() # type: ignore
|
|
||||||
self.tkProcessKeyEvent_old = NSApplication.sharedApplication().methodForSelector_(sel) # type: ignore
|
|
||||||
newmethod = objc.selector( # type: ignore
|
|
||||||
self.tkProcessKeyEvent,
|
|
||||||
selector=self.tkProcessKeyEvent_old.selector,
|
|
||||||
signature=self.tkProcessKeyEvent_old.signature
|
|
||||||
)
|
|
||||||
objc.classAddMethod(cls, sel, newmethod) # type: ignore
|
|
||||||
|
|
||||||
def tkProcessKeyEvent(self, cls, the_event): # noqa: N802
|
|
||||||
"""
|
|
||||||
Monkey-patch tk (tkMacOSXKeyEvent.c).
|
|
||||||
|
|
||||||
- workaround crash on OSX 10.9 & 10.10 on seeing a composing character
|
|
||||||
- notice when modifier key state changes
|
|
||||||
- keep a copy of NSEvent.charactersIgnoringModifiers, which is what we need for the hotkey
|
|
||||||
|
|
||||||
(Would like to use a decorator but need to ensure the application is created before this is installed)
|
|
||||||
:param cls: ???
|
|
||||||
:param the_event: tk event
|
|
||||||
:return: ???
|
|
||||||
"""
|
|
||||||
if self.acquire_state:
|
|
||||||
if the_event.type() == NSFlagsChanged:
|
|
||||||
self.acquire_key = the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask
|
|
||||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW
|
|
||||||
# suppress the event by not chaining the old function
|
|
||||||
return the_event
|
|
||||||
|
|
||||||
if the_event.type() in (NSKeyDown, NSKeyUp):
|
|
||||||
c = the_event.charactersIgnoringModifiers()
|
|
||||||
self.acquire_key = (c and ord(c[0]) or 0) | \
|
|
||||||
(the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask)
|
|
||||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW
|
|
||||||
# suppress the event by not chaining the old function
|
|
||||||
return the_event
|
|
||||||
|
|
||||||
# replace empty characters with charactersIgnoringModifiers to avoid crash
|
|
||||||
elif the_event.type() in (NSKeyDown, NSKeyUp) and not the_event.characters():
|
|
||||||
the_event = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_( # noqa: E501
|
|
||||||
# noqa: E501
|
|
||||||
the_event.type(),
|
|
||||||
the_event.locationInWindow(),
|
|
||||||
the_event.modifierFlags(),
|
|
||||||
the_event.timestamp(),
|
|
||||||
the_event.windowNumber(),
|
|
||||||
the_event.context(),
|
|
||||||
the_event.charactersIgnoringModifiers(),
|
|
||||||
the_event.charactersIgnoringModifiers(),
|
|
||||||
the_event.isARepeat(),
|
|
||||||
the_event.keyCode()
|
|
||||||
)
|
|
||||||
return self.tkProcessKeyEvent_old(cls, the_event)
|
|
||||||
|
|
||||||
def _observe(self):
|
|
||||||
# Must be called after root.mainloop() so that the app's message loop has been created
|
|
||||||
self.observer = NSEvent.addGlobalMonitorForEventsMatchingMask_handler_(NSKeyDownMask, self._handler)
|
|
||||||
|
|
||||||
def _poll(self):
|
|
||||||
if config.shutting_down:
|
|
||||||
return
|
|
||||||
|
|
||||||
# No way of signalling to Tkinter from within the callback handler block that doesn't
|
|
||||||
# cause Python to crash, so poll.
|
|
||||||
if self.activated:
|
|
||||||
self.activated = False
|
|
||||||
self.root.event_generate('<<Invoke>>', when="tail")
|
|
||||||
|
|
||||||
if self.keycode or self.modifiers:
|
|
||||||
self.root.after(MacHotkeyMgr.POLL, self._poll)
|
|
||||||
|
|
||||||
def unregister(self) -> None:
|
|
||||||
"""Remove hotkey registration."""
|
|
||||||
self.keycode = 0
|
|
||||||
self.modifiers = 0
|
|
||||||
|
|
||||||
@objc.callbackFor(NSEvent.addGlobalMonitorForEventsMatchingMask_handler_)
|
|
||||||
def _handler(self, event) -> None:
|
|
||||||
# use event.charactersIgnoringModifiers to handle composing characters like Alt-e
|
|
||||||
if (
|
|
||||||
(event.modifierFlags() & self.MODIFIERMASK) == self.modifiers
|
|
||||||
and ord(event.charactersIgnoringModifiers()[0]) == self.keycode
|
|
||||||
):
|
|
||||||
if config.get_int('hotkey_always'):
|
|
||||||
self.activated = True
|
|
||||||
|
|
||||||
else: # Only trigger if game client is front process
|
|
||||||
front = NSWorkspace.sharedWorkspace().frontmostApplication()
|
|
||||||
if front and front.bundleIdentifier() == 'uk.co.frontier.EliteDangerous':
|
|
||||||
self.activated = True
|
|
||||||
|
|
||||||
def acquire_start(self) -> None:
|
|
||||||
"""Start acquiring hotkey state via polling."""
|
|
||||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE
|
|
||||||
self.root.after_idle(self._acquire_poll)
|
|
||||||
|
|
||||||
def acquire_stop(self) -> None:
|
|
||||||
"""Stop acquiring hotkey state."""
|
|
||||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
|
||||||
|
|
||||||
def _acquire_poll(self) -> None:
|
|
||||||
"""Perform a poll of current hotkey state."""
|
|
||||||
if config.shutting_down:
|
|
||||||
return
|
|
||||||
|
|
||||||
# No way of signalling to Tkinter from within the monkey-patched event handler that doesn't
|
|
||||||
# cause Python to crash, so poll.
|
|
||||||
if self.acquire_state:
|
|
||||||
if self.acquire_state == MacHotkeyMgr.ACQUIRE_NEW:
|
|
||||||
# Abuse tkEvent's keycode field to hold our acquired key & modifier
|
|
||||||
self.root.event_generate('<KeyPress>', keycode=self.acquire_key)
|
|
||||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE
|
|
||||||
self.root.after(50, self._acquire_poll)
|
|
||||||
|
|
||||||
def fromevent(self, event) -> bool | tuple | None:
|
|
||||||
"""
|
|
||||||
Return configuration (keycode, modifiers) or None=clear or False=retain previous.
|
|
||||||
|
|
||||||
:param event: tk event ?
|
|
||||||
:return: False to retain previous, None to not use, else (keycode, modifiers)
|
|
||||||
"""
|
|
||||||
(keycode, modifiers) = (event.keycode & 0xffff, event.keycode & 0xffff0000) # Set by _acquire_poll()
|
|
||||||
if (
|
|
||||||
keycode
|
|
||||||
and not (modifiers & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask))
|
|
||||||
):
|
|
||||||
if keycode == 0x1b: # Esc = retain previous
|
|
||||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
|
||||||
return False
|
|
||||||
|
|
||||||
# BkSp, Del, Clear = clear hotkey
|
|
||||||
if keycode in (0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)):
|
|
||||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
|
||||||
return None
|
|
||||||
|
|
||||||
# don't allow keys needed for typing in System Map
|
|
||||||
if keycode in (0x13, 0x20, 0x2d) or 0x61 <= keycode <= 0x7a:
|
|
||||||
NSBeep()
|
|
||||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
|
||||||
return None
|
|
||||||
|
|
||||||
return keycode, modifiers
|
|
||||||
|
|
||||||
def display(self, keycode, modifiers) -> str:
|
|
||||||
"""
|
|
||||||
Return displayable form of given hotkey + modifiers.
|
|
||||||
|
|
||||||
:param keycode:
|
|
||||||
:param modifiers:
|
|
||||||
:return: string form
|
|
||||||
"""
|
|
||||||
text = ''
|
|
||||||
if modifiers & NSControlKeyMask:
|
|
||||||
text += u'⌃'
|
|
||||||
|
|
||||||
if modifiers & NSAlternateKeyMask:
|
|
||||||
text += u'⌥'
|
|
||||||
|
|
||||||
if modifiers & NSShiftKeyMask:
|
|
||||||
text += u'⇧'
|
|
||||||
|
|
||||||
if modifiers & NSCommandKeyMask:
|
|
||||||
text += u'⌘'
|
|
||||||
|
|
||||||
if (modifiers & NSNumericPadKeyMask) and keycode <= 0x7f:
|
|
||||||
text += u'№'
|
|
||||||
|
|
||||||
if not keycode:
|
|
||||||
pass
|
|
||||||
|
|
||||||
elif ord(NSF1FunctionKey) <= keycode <= ord(NSF35FunctionKey):
|
|
||||||
text += f'F{keycode + 1 - ord(NSF1FunctionKey)}'
|
|
||||||
|
|
||||||
elif keycode in MacHotkeyMgr.DISPLAY: # specials
|
|
||||||
text += MacHotkeyMgr.DISPLAY[keycode]
|
|
||||||
|
|
||||||
elif keycode < 0x20: # control keys
|
|
||||||
text += chr(keycode + 0x40)
|
|
||||||
|
|
||||||
elif keycode < 0xf700: # key char
|
|
||||||
text += chr(keycode).upper()
|
|
||||||
|
|
||||||
else:
|
|
||||||
text += u'⁈'
|
|
||||||
|
|
||||||
return text
|
|
||||||
|
|
||||||
def play_good(self):
|
|
||||||
"""Play the 'good' sound."""
|
|
||||||
self.snd_good.play()
|
|
||||||
|
|
||||||
def play_bad(self):
|
|
||||||
"""Play the 'bad' sound."""
|
|
||||||
self.snd_bad.play()
|
|
@ -218,10 +218,6 @@ class JournalLock:
|
|||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
self.attributes('-toolwindow', tk.TRUE)
|
self.attributes('-toolwindow', tk.TRUE)
|
||||||
|
|
||||||
elif sys.platform == 'darwin':
|
|
||||||
# http://wiki.tcl.tk/13428
|
|
||||||
parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
|
|
||||||
|
|
||||||
self.resizable(tk.FALSE, tk.FALSE)
|
self.resizable(tk.FALSE, tk.FALSE)
|
||||||
|
|
||||||
frame = ttk.Frame(self)
|
frame = ttk.Frame(self)
|
||||||
|
@ -361,8 +361,8 @@ def fetch_kill_switches(target=DEFAULT_KILLSWITCH_URL) -> KillSwitchJSONFile | N
|
|||||||
logger.warning(f"Failed to get kill switches, data was invalid: {e}")
|
logger.warning(f"Failed to get kill switches, data was invalid: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except (requests.exceptions.BaseHTTPError, requests.exceptions.ConnectionError) as e: # type: ignore
|
except requests.exceptions.RequestException as requ_err:
|
||||||
logger.warning(f"unable to connect to {target!r}: {e}")
|
logger.warning(f"unable to connect to {target!r}: {requ_err}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
50
l10n.py
50
l10n.py
@ -17,8 +17,8 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from os import listdir, makedirs, pardir, sep
|
from os import listdir, sep, makedirs
|
||||||
from os.path import abspath, basename, dirname, exists, isdir, isfile, join
|
from os.path import basename, dirname, isdir, join, abspath, exists
|
||||||
from typing import TYPE_CHECKING, Iterable, TextIO, cast
|
from typing import TYPE_CHECKING, Iterable, TextIO, cast
|
||||||
|
|
||||||
from config import config
|
from config import config
|
||||||
@ -40,12 +40,7 @@ logger = get_main_logger()
|
|||||||
LANGUAGE_ID = '!Language'
|
LANGUAGE_ID = '!Language'
|
||||||
LOCALISATION_DIR = 'L10n'
|
LOCALISATION_DIR = 'L10n'
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'win32':
|
||||||
from Foundation import ( # type: ignore # exists on Darwin
|
|
||||||
NSLocale, NSNumberFormatter, NSNumberFormatterDecimalStyle
|
|
||||||
)
|
|
||||||
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
import ctypes
|
import ctypes
|
||||||
from ctypes.wintypes import BOOL, DWORD, LPCVOID, LPCWSTR, LPWSTR
|
from ctypes.wintypes import BOOL, DWORD, LPCVOID, LPCWSTR, LPWSTR
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -199,14 +194,8 @@ class _Translations:
|
|||||||
def available(self) -> set[str]:
|
def available(self) -> set[str]:
|
||||||
"""Return a list of available language codes."""
|
"""Return a list of available language codes."""
|
||||||
path = self.respath()
|
path = self.respath()
|
||||||
if getattr(sys, 'frozen', False) and sys.platform == 'darwin':
|
|
||||||
available = {
|
|
||||||
x[:-len('.lproj')] for x in listdir(path)
|
|
||||||
if x.endswith('.lproj') and isfile(join(x, 'Localizable.strings'))
|
|
||||||
}
|
|
||||||
|
|
||||||
else:
|
available = {x[:-len('.strings')] for x in listdir(path) if x.endswith('.strings')}
|
||||||
available = {x[:-len('.strings')] for x in listdir(path) if x.endswith('.strings')}
|
|
||||||
|
|
||||||
return available
|
return available
|
||||||
|
|
||||||
@ -227,9 +216,6 @@ class _Translations:
|
|||||||
def respath(self) -> str:
|
def respath(self) -> str:
|
||||||
"""Path to localisation files."""
|
"""Path to localisation files."""
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
if sys.platform == 'darwin':
|
|
||||||
return abspath(join(dirname(sys.executable), pardir, 'Resources'))
|
|
||||||
|
|
||||||
return abspath(join(dirname(sys.executable), LOCALISATION_DIR))
|
return abspath(join(dirname(sys.executable), LOCALISATION_DIR))
|
||||||
|
|
||||||
if __file__:
|
if __file__:
|
||||||
@ -255,10 +241,6 @@ class _Translations:
|
|||||||
except OSError:
|
except OSError:
|
||||||
logger.exception(f'could not open {file_path}')
|
logger.exception(f'could not open {file_path}')
|
||||||
|
|
||||||
elif getattr(sys, 'frozen', False) and sys.platform == 'darwin':
|
|
||||||
res_path = join(self.respath(), f'{lang}.lproj', 'Localizable.strings')
|
|
||||||
return open(res_path, encoding='utf-16')
|
|
||||||
|
|
||||||
res_path = join(self.respath(), f'{lang}.strings')
|
res_path = join(self.respath(), f'{lang}.strings')
|
||||||
return open(res_path, encoding='utf-8')
|
return open(res_path, encoding='utf-8')
|
||||||
|
|
||||||
@ -266,15 +248,6 @@ class _Translations:
|
|||||||
class _Locale:
|
class _Locale:
|
||||||
"""Locale holds a few utility methods to convert data to and from localized versions."""
|
"""Locale holds a few utility methods to convert data to and from localized versions."""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
if sys.platform == 'darwin':
|
|
||||||
self.int_formatter = NSNumberFormatter.alloc().init()
|
|
||||||
self.int_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle)
|
|
||||||
self.float_formatter = NSNumberFormatter.alloc().init()
|
|
||||||
self.float_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle)
|
|
||||||
self.float_formatter.setMinimumFractionDigits_(5)
|
|
||||||
self.float_formatter.setMaximumFractionDigits_(5)
|
|
||||||
|
|
||||||
def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str: # noqa: N802
|
def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str: # noqa: N802
|
||||||
warnings.warn(DeprecationWarning('use _Locale.string_from_number instead.'))
|
warnings.warn(DeprecationWarning('use _Locale.string_from_number instead.'))
|
||||||
return self.string_from_number(number, decimals) # type: ignore
|
return self.string_from_number(number, decimals) # type: ignore
|
||||||
@ -300,14 +273,6 @@ class _Locale:
|
|||||||
if decimals == 0 and not isinstance(number, numbers.Integral):
|
if decimals == 0 and not isinstance(number, numbers.Integral):
|
||||||
number = int(round(number))
|
number = int(round(number))
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
|
||||||
if not decimals and isinstance(number, numbers.Integral):
|
|
||||||
return self.int_formatter.stringFromNumber_(number)
|
|
||||||
|
|
||||||
self.float_formatter.setMinimumFractionDigits_(decimals)
|
|
||||||
self.float_formatter.setMaximumFractionDigits_(decimals)
|
|
||||||
return self.float_formatter.stringFromNumber_(number)
|
|
||||||
|
|
||||||
if not decimals and isinstance(number, numbers.Integral):
|
if not decimals and isinstance(number, numbers.Integral):
|
||||||
return locale.format_string('%d', number, True)
|
return locale.format_string('%d', number, True)
|
||||||
return locale.format_string('%.*f', (decimals, number), True)
|
return locale.format_string('%.*f', (decimals, number), True)
|
||||||
@ -320,9 +285,6 @@ class _Locale:
|
|||||||
:param string: The string to convert
|
:param string: The string to convert
|
||||||
:return: None if the string cannot be parsed, otherwise an int or float dependant on input data.
|
:return: None if the string cannot be parsed, otherwise an int or float dependant on input data.
|
||||||
"""
|
"""
|
||||||
if sys.platform == 'darwin':
|
|
||||||
return self.float_formatter.numberFromString_(string)
|
|
||||||
|
|
||||||
with suppress(ValueError):
|
with suppress(ValueError):
|
||||||
return locale.atoi(string)
|
return locale.atoi(string)
|
||||||
|
|
||||||
@ -353,10 +315,8 @@ class _Locale:
|
|||||||
:return: The preferred language list
|
:return: The preferred language list
|
||||||
"""
|
"""
|
||||||
languages: Iterable[str]
|
languages: Iterable[str]
|
||||||
if sys.platform == 'darwin':
|
|
||||||
languages = NSLocale.preferredLanguages()
|
|
||||||
|
|
||||||
elif sys.platform != 'win32':
|
if sys.platform != 'win32':
|
||||||
# POSIX
|
# POSIX
|
||||||
lang = locale.getlocale()[0]
|
lang = locale.getlocale()[0]
|
||||||
languages = [lang.replace('_', '-')] if lang else []
|
languages = [lang.replace('_', '-')] if lang else []
|
||||||
|
27
monitor.py
27
monitor.py
@ -38,16 +38,7 @@ if TYPE_CHECKING:
|
|||||||
def _(x: str) -> str:
|
def _(x: str) -> str:
|
||||||
return x
|
return x
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'win32':
|
||||||
from fcntl import fcntl
|
|
||||||
|
|
||||||
from AppKit import NSWorkspace
|
|
||||||
from watchdog.events import FileSystemEventHandler
|
|
||||||
from watchdog.observers import Observer
|
|
||||||
from watchdog.observers.api import BaseObserver
|
|
||||||
F_GLOBAL_NOCACHE = 55
|
|
||||||
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
import ctypes
|
import ctypes
|
||||||
from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR
|
from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR
|
||||||
|
|
||||||
@ -63,6 +54,8 @@ elif sys.platform == 'win32':
|
|||||||
GetWindowText = ctypes.windll.user32.GetWindowTextW
|
GetWindowText = ctypes.windll.user32.GetWindowTextW
|
||||||
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
|
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
|
||||||
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
|
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
|
||||||
|
GetWindowTextLength.argtypes = [ctypes.wintypes.HWND]
|
||||||
|
GetWindowTextLength.restype = ctypes.c_int
|
||||||
|
|
||||||
GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd
|
GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd
|
||||||
|
|
||||||
@ -382,8 +375,6 @@ class EDLogs(FileSystemEventHandler):
|
|||||||
logfile = self.logfile
|
logfile = self.logfile
|
||||||
if logfile:
|
if logfile:
|
||||||
loghandle: BinaryIO = open(logfile, 'rb', 0) # unbuffered
|
loghandle: BinaryIO = open(logfile, 'rb', 0) # unbuffered
|
||||||
if sys.platform == 'darwin':
|
|
||||||
fcntl(loghandle, F_GLOBAL_NOCACHE, -1) # required to avoid corruption on macOS over SMB
|
|
||||||
|
|
||||||
self.catching_up = True
|
self.catching_up = True
|
||||||
for line in loghandle:
|
for line in loghandle:
|
||||||
@ -450,7 +441,7 @@ class EDLogs(FileSystemEventHandler):
|
|||||||
new_journal_file = None
|
new_journal_file = None
|
||||||
|
|
||||||
if logfile:
|
if logfile:
|
||||||
loghandle.seek(0, SEEK_END) # required to make macOS notice log change over SMB
|
loghandle.seek(0, SEEK_END) # required for macOS to notice log change over SMB. TODO: Do we need this?
|
||||||
loghandle.seek(log_pos, SEEK_SET) # reset EOF flag # TODO: log_pos reported as possibly unbound
|
loghandle.seek(log_pos, SEEK_SET) # reset EOF flag # TODO: log_pos reported as possibly unbound
|
||||||
for line in loghandle:
|
for line in loghandle:
|
||||||
# Paranoia check to see if we're shutting down
|
# Paranoia check to see if we're shutting down
|
||||||
@ -483,9 +474,6 @@ class EDLogs(FileSystemEventHandler):
|
|||||||
|
|
||||||
if logfile:
|
if logfile:
|
||||||
loghandle = open(logfile, 'rb', 0) # unbuffered
|
loghandle = open(logfile, 'rb', 0) # unbuffered
|
||||||
if sys.platform == 'darwin':
|
|
||||||
fcntl(loghandle, F_GLOBAL_NOCACHE, -1) # required to avoid corruption on macOS over SMB
|
|
||||||
|
|
||||||
log_pos = 0
|
log_pos = 0
|
||||||
|
|
||||||
sleep(self._POLL)
|
sleep(self._POLL)
|
||||||
@ -2144,12 +2132,7 @@ class EDLogs(FileSystemEventHandler):
|
|||||||
|
|
||||||
:return: bool - True if the game is running.
|
:return: bool - True if the game is running.
|
||||||
"""
|
"""
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'win32':
|
||||||
for app in NSWorkspace.sharedWorkspace().runningApplications():
|
|
||||||
if app.bundleIdentifier() == 'uk.co.frontier.EliteDangerous':
|
|
||||||
return True
|
|
||||||
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
def WindowTitle(h): # noqa: N802
|
def WindowTitle(h): # noqa: N802
|
||||||
if h:
|
if h:
|
||||||
length = GetWindowTextLength(h) + 1
|
length = GetWindowTextLength(h) + 1
|
||||||
|
220
myNotebook.py
220
myNotebook.py
@ -1,12 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
Custom `ttk.Notebook` to fix various display issues.
|
Custom `ttk.Notebook` to fix various display issues.
|
||||||
|
|
||||||
Hacks to fix various display issues with notebooks and their child widgets on
|
Hacks to fix various display issues with notebooks and their child widgets on Windows.
|
||||||
OSX and Windows.
|
|
||||||
|
|
||||||
- Windows: page background should be White, not SystemButtonFace
|
- Windows: page background should be White, not SystemButtonFace
|
||||||
- OSX: page background should be a darker gray than systemWindowBody
|
|
||||||
selected tab foreground should be White when the window is active
|
|
||||||
|
|
||||||
Entire file may be imported by plugins.
|
Entire file may be imported by plugins.
|
||||||
"""
|
"""
|
||||||
@ -14,15 +11,14 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk, messagebox
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from PIL import ImageGrab
|
||||||
|
|
||||||
# Can't do this with styles on OSX - http://www.tkdocs.com/tutorial/styles.html#whydifficult
|
if TYPE_CHECKING:
|
||||||
if sys.platform == 'darwin':
|
def _(x: str) -> str: return x
|
||||||
from platform import mac_ver
|
|
||||||
PAGEFG = 'systemButtonText'
|
|
||||||
PAGEBG = 'systemButtonActiveDarkShadow'
|
|
||||||
|
|
||||||
elif sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
PAGEFG = 'SystemWindowText'
|
PAGEFG = 'SystemWindowText'
|
||||||
PAGEBG = 'SystemWindow' # typically white
|
PAGEBG = 'SystemWindow' # typically white
|
||||||
|
|
||||||
@ -32,39 +28,22 @@ class Notebook(ttk.Notebook):
|
|||||||
|
|
||||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||||
|
|
||||||
ttk.Notebook.__init__(self, master, **kw)
|
super().__init__(master, **kw)
|
||||||
style = ttk.Style()
|
style = ttk.Style()
|
||||||
|
if sys.platform == 'win32':
|
||||||
if sys.platform == 'darwin':
|
|
||||||
if list(map(int, mac_ver()[0].split('.'))) >= [10, 10]:
|
|
||||||
# Hack for tab appearance with 8.5 on Yosemite & El Capitan. For proper fix see
|
|
||||||
# https://github.com/tcltk/tk/commit/55c4dfca9353bbd69bbcec5d63bf1c8dfb461e25
|
|
||||||
style.configure('TNotebook.Tab', padding=(12, 10, 12, 2))
|
|
||||||
style.map('TNotebook.Tab', foreground=[('selected', '!background', 'systemWhite')])
|
|
||||||
self.grid(sticky=tk.NSEW) # Already padded apropriately
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
style.configure('nb.TFrame', background=PAGEBG)
|
style.configure('nb.TFrame', background=PAGEBG)
|
||||||
style.configure('nb.TButton', background=PAGEBG)
|
style.configure('nb.TButton', background=PAGEBG)
|
||||||
style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
|
style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
|
||||||
style.configure('nb.TMenubutton', foreground=PAGEFG, background=PAGEBG)
|
style.configure('nb.TMenubutton', foreground=PAGEFG, background=PAGEBG)
|
||||||
style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG)
|
style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG)
|
||||||
self.grid(padx=10, pady=10, sticky=tk.NSEW)
|
self.grid(padx=10, pady=10, sticky=tk.NSEW)
|
||||||
else:
|
|
||||||
self.grid(padx=10, pady=10, sticky=tk.NSEW)
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME: The real fix for this 'dynamic type' would be to split this whole
|
class Frame(ttk.Frame):
|
||||||
# thing into being a module with per-platform files, as we've done with config
|
"""Custom ttk.Frame class to fix some display issues."""
|
||||||
# That would also make the code cleaner.
|
|
||||||
class Frame(sys.platform == 'darwin' and tk.Frame or ttk.Frame): # type: ignore
|
|
||||||
"""Custom t(t)k.Frame class to fix some display issues."""
|
|
||||||
|
|
||||||
def __init__(self, master: ttk.Notebook | None = None, **kw):
|
def __init__(self, master: ttk.Notebook | None = None, **kw):
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'win32':
|
||||||
kw['background'] = kw.pop('background', PAGEBG)
|
|
||||||
tk.Frame.__init__(self, master, **kw)
|
|
||||||
tk.Frame(self).grid(pady=5)
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
ttk.Frame.__init__(self, master, style='nb.TFrame', **kw)
|
ttk.Frame.__init__(self, master, style='nb.TFrame', **kw)
|
||||||
ttk.Frame(self).grid(pady=5) # top spacer
|
ttk.Frame(self).grid(pady=5) # top spacer
|
||||||
else:
|
else:
|
||||||
@ -77,105 +56,124 @@ class Label(tk.Label):
|
|||||||
"""Custom tk.Label class to fix some display issues."""
|
"""Custom tk.Label class to fix some display issues."""
|
||||||
|
|
||||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||||
# This format chosen over `sys.platform in (...)` as mypy and friends dont understand that
|
kw['foreground'] = kw.pop('foreground', PAGEFG if sys.platform == 'win32'
|
||||||
if sys.platform in ('darwin', 'win32'):
|
else ttk.Style().lookup('TLabel', 'foreground'))
|
||||||
kw['foreground'] = kw.pop('foreground', PAGEFG)
|
kw['background'] = kw.pop('background', PAGEBG if sys.platform == 'win32'
|
||||||
kw['background'] = kw.pop('background', PAGEBG)
|
else ttk.Style().lookup('TLabel', 'background'))
|
||||||
else:
|
super().__init__(master, **kw)
|
||||||
kw['foreground'] = kw.pop('foreground', ttk.Style().lookup('TLabel', 'foreground'))
|
|
||||||
kw['background'] = kw.pop('background', ttk.Style().lookup('TLabel', 'background'))
|
|
||||||
tk.Label.__init__(self, master, **kw) # Just use tk.Label on all platforms
|
|
||||||
|
|
||||||
|
|
||||||
class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry): # type: ignore
|
class EntryMenu(ttk.Entry):
|
||||||
"""Custom t(t)k.Entry class to fix some display issues."""
|
"""Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
|
ttk.Entry.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
self.menu = tk.Menu(self, tearoff=False)
|
||||||
|
self.menu.add_command(label="Copy", command=self.copy)
|
||||||
|
self.menu.add_command(label="Cut", command=self.cut)
|
||||||
|
self.menu.add_separator()
|
||||||
|
self.menu.add_command(label="Paste", command=self.paste)
|
||||||
|
self.menu.add_separator()
|
||||||
|
self.menu.add_command(label="Select All", command=self.select_all)
|
||||||
|
|
||||||
|
self.bind("<Button-3>", self.display_popup)
|
||||||
|
|
||||||
|
def display_popup(self, event: tk.Event) -> None:
|
||||||
|
"""Display the menu popup."""
|
||||||
|
self.menu.post(event.x_root, event.y_root)
|
||||||
|
|
||||||
|
def select_all(self) -> None:
|
||||||
|
"""Select all the text within the Entry."""
|
||||||
|
self.selection_range(0, tk.END)
|
||||||
|
self.focus_set()
|
||||||
|
|
||||||
|
def copy(self) -> None:
|
||||||
|
"""Copy the selected Entry text."""
|
||||||
|
if self.selection_present():
|
||||||
|
self.clipboard_clear()
|
||||||
|
self.clipboard_append(self.selection_get())
|
||||||
|
|
||||||
|
def cut(self) -> None:
|
||||||
|
"""Cut the selected Entry text."""
|
||||||
|
if self.selection_present():
|
||||||
|
self.copy()
|
||||||
|
self.delete(tk.SEL_FIRST, tk.SEL_LAST)
|
||||||
|
|
||||||
|
def paste(self) -> None:
|
||||||
|
"""Paste the selected Entry text."""
|
||||||
|
try:
|
||||||
|
# Attempt to grab an image from the clipboard (apprently also works for files)
|
||||||
|
img = ImageGrab.grabclipboard()
|
||||||
|
if img:
|
||||||
|
# Hijack existing translation, yes it doesn't exactly match here.
|
||||||
|
# LANG: Generic error prefix - following text is from Frontier auth service;
|
||||||
|
messagebox.showwarning(_('Error'),
|
||||||
|
_('Cannot paste non-text content.')) # LANG: Can't Paste Images or Files in Text
|
||||||
|
return
|
||||||
|
text = self.clipboard_get()
|
||||||
|
if self.selection_present() and text:
|
||||||
|
self.delete(tk.SEL_FIRST, tk.SEL_LAST)
|
||||||
|
self.insert(tk.INSERT, text)
|
||||||
|
except tk.TclError:
|
||||||
|
# No text in clipboard or clipboard is not text
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Entry(EntryMenu):
|
||||||
|
"""Custom ttk.Entry class to fix some display issues."""
|
||||||
|
|
||||||
|
# DEPRECATED: Migrate to EntryMenu. Will remove in 5.12 or later.
|
||||||
|
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||||
|
EntryMenu.__init__(self, master, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
class Button(ttk.Button):
|
||||||
|
"""Custom ttk.Button class to fix some display issues."""
|
||||||
|
|
||||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'win32':
|
||||||
kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG)
|
|
||||||
tk.Entry.__init__(self, master, **kw)
|
|
||||||
else:
|
|
||||||
ttk.Entry.__init__(self, master, **kw)
|
|
||||||
|
|
||||||
|
|
||||||
class Button(sys.platform == 'darwin' and tk.Button or ttk.Button): # type: ignore
|
|
||||||
"""Custom t(t)k.Button class to fix some display issues."""
|
|
||||||
|
|
||||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
|
||||||
if sys.platform == 'darwin':
|
|
||||||
kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG)
|
|
||||||
tk.Button.__init__(self, master, **kw)
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
ttk.Button.__init__(self, master, style='nb.TButton', **kw)
|
ttk.Button.__init__(self, master, style='nb.TButton', **kw)
|
||||||
else:
|
else:
|
||||||
ttk.Button.__init__(self, master, **kw)
|
ttk.Button.__init__(self, master, **kw)
|
||||||
|
|
||||||
|
|
||||||
class ColoredButton(sys.platform == 'darwin' and tk.Label or tk.Button): # type: ignore
|
class ColoredButton(tk.Button):
|
||||||
"""Custom t(t)k.ColoredButton class to fix some display issues."""
|
"""Custom tk.Button class to fix some display issues."""
|
||||||
|
|
||||||
|
# DEPRECATED: Migrate to tk.Button. Will remove in 5.12 or later.
|
||||||
|
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||||
|
tk.Button.__init__(self, master, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
class Checkbutton(ttk.Checkbutton):
|
||||||
|
"""Custom ttk.Checkbutton class to fix some display issues."""
|
||||||
|
|
||||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||||
if sys.platform == 'darwin':
|
style = 'nb.TCheckbutton' if sys.platform == 'win32' else None
|
||||||
# Can't set Button background on OSX, so use a Label instead
|
super().__init__(master, style=style, **kw) # type: ignore
|
||||||
kw['relief'] = kw.pop('relief', tk.RAISED)
|
|
||||||
self._command = kw.pop('command', None)
|
|
||||||
tk.Label.__init__(self, master, **kw)
|
|
||||||
self.bind('<Button-1>', self._press)
|
|
||||||
else:
|
|
||||||
tk.Button.__init__(self, master, **kw)
|
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
|
||||||
def _press(self, event):
|
|
||||||
self._command()
|
|
||||||
|
|
||||||
|
|
||||||
class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton): # type: ignore
|
class Radiobutton(ttk.Radiobutton):
|
||||||
"""Custom t(t)k.Checkbutton class to fix some display issues."""
|
"""Custom ttk.Radiobutton class to fix some display issues."""
|
||||||
|
|
||||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||||
if sys.platform == 'darwin':
|
style = 'nb.TRadiobutton' if sys.platform == 'win32' else None
|
||||||
kw['foreground'] = kw.pop('foreground', PAGEFG)
|
super().__init__(master, style=style, **kw) # type: ignore
|
||||||
kw['background'] = kw.pop('background', PAGEBG)
|
|
||||||
tk.Checkbutton.__init__(self, master, **kw)
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
ttk.Checkbutton.__init__(self, master, style='nb.TCheckbutton', **kw)
|
|
||||||
else:
|
|
||||||
ttk.Checkbutton.__init__(self, master, **kw)
|
|
||||||
|
|
||||||
|
|
||||||
class Radiobutton(sys.platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton): # type: ignore
|
class OptionMenu(ttk.OptionMenu):
|
||||||
"""Custom t(t)k.Radiobutton class to fix some display issues."""
|
"""Custom ttk.OptionMenu class to fix some display issues."""
|
||||||
|
|
||||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
|
||||||
if sys.platform == 'darwin':
|
|
||||||
kw['foreground'] = kw.pop('foreground', PAGEFG)
|
|
||||||
kw['background'] = kw.pop('background', PAGEBG)
|
|
||||||
tk.Radiobutton.__init__(self, master, **kw)
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
ttk.Radiobutton.__init__(self, master, style='nb.TRadiobutton', **kw)
|
|
||||||
else:
|
|
||||||
ttk.Radiobutton.__init__(self, master, **kw)
|
|
||||||
|
|
||||||
|
|
||||||
class OptionMenu(sys.platform == 'darwin' and tk.OptionMenu or ttk.OptionMenu): # type: ignore
|
|
||||||
"""Custom t(t)k.OptionMenu class to fix some display issues."""
|
|
||||||
|
|
||||||
def __init__(self, master, variable, default=None, *values, **kw):
|
def __init__(self, master, variable, default=None, *values, **kw):
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'win32':
|
||||||
variable.set(default)
|
|
||||||
bg = kw.pop('background', PAGEBG)
|
|
||||||
tk.OptionMenu.__init__(self, master, variable, *values, **kw)
|
|
||||||
self['background'] = bg
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
# OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style
|
# OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style
|
||||||
ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw)
|
ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw)
|
||||||
self['menu'].configure(background=PAGEBG)
|
self['menu'].configure(background=PAGEBG)
|
||||||
# Workaround for https://bugs.python.org/issue25684
|
|
||||||
for i in range(0, self['menu'].index('end')+1):
|
|
||||||
self['menu'].entryconfig(i, variable=variable)
|
|
||||||
else:
|
else:
|
||||||
ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw)
|
ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw)
|
||||||
self['menu'].configure(background=ttk.Style().lookup('TMenu', 'background'))
|
self['menu'].configure(background=ttk.Style().lookup('TMenu', 'background'))
|
||||||
# Workaround for https://bugs.python.org/issue25684
|
|
||||||
for i in range(0, self['menu'].index('end')+1):
|
# Workaround for https://bugs.python.org/issue25684
|
||||||
self['menu'].entryconfig(i, variable=variable)
|
for i in range(0, self['menu'].index('end') + 1):
|
||||||
|
self['menu'].entryconfig(i, variable=variable)
|
||||||
|
@ -98,6 +98,12 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
|
|||||||
elif not entitled and name[1] == 'planetapproachsuite':
|
elif not entitled and name[1] == 'planetapproachsuite':
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# V2 Shutdown Field Neutralizer - Hpt_AntiUnknownShutdown_Tiny_V2
|
||||||
|
elif name[0] == 'hpt' and name[1] in countermeasure_map and len(name) == 4 and name[3] == 'v2':
|
||||||
|
new['category'] = 'utility'
|
||||||
|
new['name'], new['rating'] = countermeasure_map[name[1]]
|
||||||
|
new['class'] = weaponclass_map[name[-2]]
|
||||||
|
|
||||||
# Countermeasures - e.g. Hpt_PlasmaPointDefence_Turret_Tiny
|
# Countermeasures - e.g. Hpt_PlasmaPointDefence_Turret_Tiny
|
||||||
elif name[0] == 'hpt' and name[1] in countermeasure_map:
|
elif name[0] == 'hpt' and name[1] in countermeasure_map:
|
||||||
new['category'] = 'utility'
|
new['category'] = 'utility'
|
||||||
@ -179,12 +185,18 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
|
|||||||
if name[1] == 'dronecontrol': # e.g. Int_DroneControl_Collection_Size1_Class1
|
if name[1] == 'dronecontrol': # e.g. Int_DroneControl_Collection_Size1_Class1
|
||||||
name.pop(0)
|
name.pop(0)
|
||||||
|
|
||||||
|
elif name[1] == 'multidronecontrol': # e.g. Int_MultiDroneControl_Rescue_Size3_Class3
|
||||||
|
name.pop(0)
|
||||||
|
|
||||||
elif name[-1] == 'free': # Starter Sidewinder or Freagle modules - just treat them like vanilla modules
|
elif name[-1] == 'free': # Starter Sidewinder or Freagle modules - just treat them like vanilla modules
|
||||||
name.pop()
|
name.pop()
|
||||||
|
|
||||||
if name[1] in standard_map: # e.g. Int_Engine_Size2_Class1, Int_ShieldGenerator_Size8_Class5_Strong
|
if name[1] in standard_map: # e.g. Int_Engine_Size2_Class1, Int_ShieldGenerator_Size8_Class5_Strong
|
||||||
new['category'] = 'standard'
|
new['category'] = 'standard'
|
||||||
new['name'] = standard_map[len(name) > 4 and (name[1], name[4]) or name[1]]
|
if name[2] == 'overcharge':
|
||||||
|
new['name'] = standard_map[(name[1], name[2])]
|
||||||
|
else:
|
||||||
|
new['name'] = standard_map[len(name) > 4 and (name[1], name[4]) or name[1]]
|
||||||
|
|
||||||
elif name[1] in internal_map: # e.g. Int_CargoRack_Size8_Class1
|
elif name[1] in internal_map: # e.g. Int_CargoRack_Size8_Class1
|
||||||
new['category'] = 'internal'
|
new['category'] = 'internal'
|
||||||
@ -209,6 +221,9 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
|
|||||||
elif len(name) < 4 and name[1] == 'guardianfsdbooster': # Hack! No class.
|
elif len(name) < 4 and name[1] == 'guardianfsdbooster': # Hack! No class.
|
||||||
(new['class'], new['rating']) = (str(name[2][4:]), 'H')
|
(new['class'], new['rating']) = (str(name[2][4:]), 'H')
|
||||||
|
|
||||||
|
elif len(name) > 4 and name[1] == 'hyperdrive': # e.g. Int_Hyperdrive_Overcharge_Size6_Class3
|
||||||
|
(new['class'], new['rating']) = (str(name[4][-1:]), 'C')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if len(name) < 3:
|
if len(name) < 3:
|
||||||
raise AssertionError(f'{name}: length < 3]')
|
raise AssertionError(f'{name}: length < 3]')
|
||||||
|
2
plug.py
2
plug.py
@ -126,7 +126,7 @@ class Plugin:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_prefs(self, parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame | None:
|
def get_prefs(self, parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame | None:
|
||||||
"""
|
"""
|
||||||
If the plugin provides a prefs frame, create and return it.
|
If the plugin provides a prefs frame, create and return it.
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ def plugin_start3(path: str) -> str:
|
|||||||
return 'Coriolis'
|
return 'Coriolis'
|
||||||
|
|
||||||
|
|
||||||
def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame:
|
def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame:
|
||||||
"""Set up plugin preferences."""
|
"""Set up plugin preferences."""
|
||||||
PADX = 10 # noqa: N806
|
PADX = 10 # noqa: N806
|
||||||
PADY = 1 # noqa: N806
|
PADY = 1 # noqa: N806
|
||||||
@ -106,8 +106,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
|
|||||||
|
|
||||||
# LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL
|
# LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL
|
||||||
nb.Label(conf_frame, text=_('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
|
nb.Label(conf_frame, text=_('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
|
||||||
nb.Entry(conf_frame,
|
nb.EntryMenu(conf_frame, textvariable=coriolis_config.normal_textvar).grid(
|
||||||
textvariable=coriolis_config.normal_textvar).grid(
|
|
||||||
sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
|
sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
|
||||||
)
|
)
|
||||||
# LANG: Generic 'Reset' button label
|
# LANG: Generic 'Reset' button label
|
||||||
@ -119,8 +118,8 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
|
|||||||
|
|
||||||
# LANG: Settings>Coriolis: Label for 'alpha/beta game version' URL
|
# LANG: Settings>Coriolis: Label for 'alpha/beta game version' URL
|
||||||
nb.Label(conf_frame, text=_('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
|
nb.Label(conf_frame, text=_('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
|
||||||
nb.Entry(conf_frame, textvariable=coriolis_config.beta_textvar).grid(
|
nb.EntryMenu(conf_frame, textvariable=coriolis_config.beta_textvar).grid(
|
||||||
sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
|
sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
|
||||||
)
|
)
|
||||||
# LANG: Generic 'Reset' button label
|
# LANG: Generic 'Reset' button label
|
||||||
nb.Button(conf_frame, text=_('Reset'),
|
nb.Button(conf_frame, text=_('Reset'),
|
||||||
|
@ -27,7 +27,6 @@ import os
|
|||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from platform import system
|
from platform import system
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
@ -282,7 +281,7 @@ class EDDNSender:
|
|||||||
msg['header'] = {
|
msg['header'] = {
|
||||||
# We have to lie and say it's *this* version, but denote that
|
# We have to lie and say it's *this* version, but denote that
|
||||||
# it might not actually be this version.
|
# it might not actually be this version.
|
||||||
'softwareName': f'{applongname} [{system() if sys.platform != "darwin" else "Mac OS"}]'
|
'softwareName': f'{applongname} [{system()}]'
|
||||||
' (legacy replay)',
|
' (legacy replay)',
|
||||||
'softwareVersion': str(appversion_nobuild()),
|
'softwareVersion': str(appversion_nobuild()),
|
||||||
'uploaderID': cmdr,
|
'uploaderID': cmdr,
|
||||||
@ -1074,7 +1073,7 @@ class EDDN:
|
|||||||
gb = this.game_build
|
gb = this.game_build
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'softwareName': f'{applongname} [{system() if sys.platform != "darwin" else "Mac OS"}]',
|
'softwareName': f'{applongname} [{system()}]',
|
||||||
'softwareVersion': str(appversion_nobuild()),
|
'softwareVersion': str(appversion_nobuild()),
|
||||||
'uploaderID': this.cmdr_name,
|
'uploaderID': this.cmdr_name,
|
||||||
'gameversion': gv,
|
'gameversion': gv,
|
||||||
@ -1899,7 +1898,7 @@ class EDDN:
|
|||||||
:param cmdr: the commander under which this upload is made
|
:param cmdr: the commander under which this upload is made
|
||||||
:param is_beta: whether or not we are in beta mode
|
:param is_beta: whether or not we are in beta mode
|
||||||
:param entry: the journal entry to send
|
:param entry: the journal entry to send
|
||||||
|
___
|
||||||
Example:
|
Example:
|
||||||
{
|
{
|
||||||
"timestamp":"2022-06-10T10:09:41Z",
|
"timestamp":"2022-06-10T10:09:41Z",
|
||||||
@ -1933,7 +1932,7 @@ class EDDN:
|
|||||||
:param cmdr: the commander under which this upload is made
|
:param cmdr: the commander under which this upload is made
|
||||||
:param is_beta: whether or not we are in beta mode
|
:param is_beta: whether or not we are in beta mode
|
||||||
:param entry: the journal entry to send
|
:param entry: the journal entry to send
|
||||||
|
___
|
||||||
Example:
|
Example:
|
||||||
{
|
{
|
||||||
"timestamp":"2023-10-01T14:56:34Z",
|
"timestamp":"2023-10-01T14:56:34Z",
|
||||||
|
@ -113,10 +113,10 @@ class This:
|
|||||||
self.cmdr_text: nb.Label | None = None
|
self.cmdr_text: nb.Label | None = None
|
||||||
|
|
||||||
self.user_label: nb.Label | None = None
|
self.user_label: nb.Label | None = None
|
||||||
self.user: nb.Entry | None = None
|
self.user: nb.EntryMenu | None = None
|
||||||
|
|
||||||
self.apikey_label: nb.Label | None = None
|
self.apikey_label: nb.Label | None = None
|
||||||
self.apikey: nb.Entry | None = None
|
self.apikey: nb.EntryMenu | None = None
|
||||||
|
|
||||||
|
|
||||||
this = This()
|
this = This()
|
||||||
@ -279,7 +279,7 @@ def toggle_password_visibility():
|
|||||||
this.apikey.config(show="*") # type: ignore
|
this.apikey.config(show="*") # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame:
|
def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame:
|
||||||
"""
|
"""
|
||||||
Plugin preferences setup hook.
|
Plugin preferences setup hook.
|
||||||
|
|
||||||
@ -345,14 +345,14 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
|
|||||||
# LANG: EDSM Commander name label in EDSM settings
|
# LANG: EDSM Commander name label in EDSM settings
|
||||||
this.user_label = nb.Label(frame, text=_('Commander Name'))
|
this.user_label = nb.Label(frame, text=_('Commander Name'))
|
||||||
this.user_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
|
this.user_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
|
||||||
this.user = nb.Entry(frame)
|
this.user = nb.EntryMenu(frame)
|
||||||
this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
||||||
|
|
||||||
cur_row += 1
|
cur_row += 1
|
||||||
# LANG: EDSM API key label
|
# LANG: EDSM API key label
|
||||||
this.apikey_label = nb.Label(frame, text=_('API Key'))
|
this.apikey_label = nb.Label(frame, text=_('API Key'))
|
||||||
this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
|
this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
|
||||||
this.apikey = nb.Entry(frame, show="*", width=50)
|
this.apikey = nb.EntryMenu(frame, show="*", width=50)
|
||||||
this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
||||||
cur_row += 1
|
cur_row += 1
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ class This:
|
|||||||
self.log: 'tk.IntVar'
|
self.log: 'tk.IntVar'
|
||||||
self.log_button: nb.Checkbutton
|
self.log_button: nb.Checkbutton
|
||||||
self.label: HyperlinkLabel
|
self.label: HyperlinkLabel
|
||||||
self.apikey: nb.Entry
|
self.apikey: nb.EntryMenu
|
||||||
self.apikey_label: tk.Label
|
self.apikey_label: tk.Label
|
||||||
|
|
||||||
self.events: dict[Credentials, Deque[Event]] = defaultdict(deque)
|
self.events: dict[Credentials, Deque[Event]] = defaultdict(deque)
|
||||||
@ -244,7 +244,7 @@ def toggle_password_visibility():
|
|||||||
this.apikey.config(show="*")
|
this.apikey.config(show="*")
|
||||||
|
|
||||||
|
|
||||||
def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> tk.Frame:
|
def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame:
|
||||||
"""Plugin Preferences UI hook."""
|
"""Plugin Preferences UI hook."""
|
||||||
PADX = 10 # noqa: N806
|
PADX = 10 # noqa: N806
|
||||||
BUTTONX = 12 # noqa: N806 # indent Checkbuttons and Radiobuttons
|
BUTTONX = 12 # noqa: N806 # indent Checkbuttons and Radiobuttons
|
||||||
@ -292,7 +292,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> tk.Frame:
|
|||||||
# LANG: Inara API key label
|
# LANG: Inara API key label
|
||||||
this.apikey_label = nb.Label(frame, text=_('API Key')) # Inara setting
|
this.apikey_label = nb.Label(frame, text=_('API Key')) # Inara setting
|
||||||
this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
|
this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
|
||||||
this.apikey = nb.Entry(frame, show="*", width=50)
|
this.apikey = nb.EntryMenu(frame, show="*", width=50)
|
||||||
this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
||||||
cur_row += 1
|
cur_row += 1
|
||||||
|
|
||||||
|
205
prefs.py
205
prefs.py
@ -18,7 +18,7 @@ from typing import TYPE_CHECKING, Any, Callable, Optional, Type
|
|||||||
|
|
||||||
import myNotebook as nb # noqa: N813
|
import myNotebook as nb # noqa: N813
|
||||||
import plug
|
import plug
|
||||||
from config import applongname, appversion_nobuild, config
|
from config import appversion_nobuild, config
|
||||||
from EDMCLogging import edmclogger, get_main_logger
|
from EDMCLogging import edmclogger, get_main_logger
|
||||||
from constants import appname
|
from constants import appname
|
||||||
from hotkey import hotkeymgr
|
from hotkey import hotkeymgr
|
||||||
@ -49,9 +49,6 @@ def help_open_log_folder() -> None:
|
|||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
# On Windows, use the "start" command to open the folder
|
# On Windows, use the "start" command to open the folder
|
||||||
system(f'start "" "{logfile_loc}"')
|
system(f'start "" "{logfile_loc}"')
|
||||||
elif sys.platform.startswith('darwin'):
|
|
||||||
# On macOS, use the "open" command to open the folder
|
|
||||||
system(f'open "{logfile_loc}"')
|
|
||||||
elif sys.platform.startswith('linux'):
|
elif sys.platform.startswith('linux'):
|
||||||
# On Linux, use the "xdg-open" command to open the folder
|
# On Linux, use the "xdg-open" command to open the folder
|
||||||
system(f'xdg-open "{logfile_loc}"')
|
system(f'xdg-open "{logfile_loc}"')
|
||||||
@ -172,32 +169,7 @@ class AutoInc(contextlib.AbstractContextManager):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'win32':
|
||||||
import objc # type: ignore
|
|
||||||
from Foundation import NSFileManager # type: ignore
|
|
||||||
try:
|
|
||||||
from ApplicationServices import ( # type: ignore
|
|
||||||
AXIsProcessTrusted, AXIsProcessTrustedWithOptions, kAXTrustedCheckOptionPrompt
|
|
||||||
)
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
HIServices = objc.loadBundle(
|
|
||||||
'HIServices',
|
|
||||||
globals(),
|
|
||||||
'/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework'
|
|
||||||
)
|
|
||||||
|
|
||||||
objc.loadBundleFunctions(
|
|
||||||
HIServices,
|
|
||||||
globals(),
|
|
||||||
[('AXIsProcessTrusted', 'B'), ('AXIsProcessTrustedWithOptions', 'B@')]
|
|
||||||
)
|
|
||||||
|
|
||||||
objc.loadBundleVariables(HIServices, globals(), [('kAXTrustedCheckOptionPrompt', '@^{__CFString=}')])
|
|
||||||
|
|
||||||
was_accessible_at_launch = AXIsProcessTrusted() # type: ignore
|
|
||||||
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import winreg
|
import winreg
|
||||||
from ctypes.wintypes import HINSTANCE, HWND, LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT
|
from ctypes.wintypes import HINSTANCE, HWND, LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT
|
||||||
@ -251,30 +223,21 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
|
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
if sys.platform == 'darwin':
|
# LANG: File > Settings (macOS)
|
||||||
# LANG: File > Preferences menu entry for macOS
|
self.title(_('Settings'))
|
||||||
self.title(_('Preferences'))
|
|
||||||
|
|
||||||
else:
|
|
||||||
# LANG: File > Settings (macOS)
|
|
||||||
self.title(_('Settings'))
|
|
||||||
|
|
||||||
if parent.winfo_viewable():
|
if parent.winfo_viewable():
|
||||||
self.transient(parent)
|
self.transient(parent)
|
||||||
|
|
||||||
# position over parent
|
# position over parent
|
||||||
if sys.platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
# http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
||||||
# TODO this is fixed supposedly.
|
# TODO this is fixed supposedly.
|
||||||
self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}')
|
self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}')
|
||||||
|
|
||||||
# remove decoration
|
# remove decoration
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
self.attributes('-toolwindow', tk.TRUE)
|
self.attributes('-toolwindow', tk.TRUE)
|
||||||
|
|
||||||
elif sys.platform == 'darwin':
|
|
||||||
# http://wiki.tcl.tk/13428
|
|
||||||
parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
|
|
||||||
|
|
||||||
self.resizable(tk.FALSE, tk.FALSE)
|
self.resizable(tk.FALSE, tk.FALSE)
|
||||||
|
|
||||||
self.cmdr: str | bool | None = False # Note if Cmdr changes in the Journal
|
self.cmdr: str | bool | None = False # Note if Cmdr changes in the Journal
|
||||||
@ -302,19 +265,15 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
self.__setup_appearance_tab(notebook)
|
self.__setup_appearance_tab(notebook)
|
||||||
self.__setup_plugin_tab(notebook)
|
self.__setup_plugin_tab(notebook)
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
buttonframe = ttk.Frame(frame)
|
||||||
self.protocol("WM_DELETE_WINDOW", self.apply) # close button applies changes
|
buttonframe.grid(padx=self.PADX, pady=self.PADX, sticky=tk.NSEW)
|
||||||
|
buttonframe.columnconfigure(0, weight=1)
|
||||||
else:
|
ttk.Label(buttonframe).grid(row=0, column=0) # spacer
|
||||||
buttonframe = ttk.Frame(frame)
|
# LANG: 'OK' button on Settings/Preferences window
|
||||||
buttonframe.grid(padx=self.PADX, pady=self.PADX, sticky=tk.NSEW)
|
button = ttk.Button(buttonframe, text=_('OK'), command=self.apply)
|
||||||
buttonframe.columnconfigure(0, weight=1)
|
button.grid(row=0, column=1, sticky=tk.E)
|
||||||
ttk.Label(buttonframe).grid(row=0, column=0) # spacer
|
button.bind("<Return>", lambda event: self.apply())
|
||||||
# LANG: 'OK' button on Settings/Preferences window
|
self.protocol("WM_DELETE_WINDOW", self._destroy)
|
||||||
button = ttk.Button(buttonframe, text=_('OK'), command=self.apply)
|
|
||||||
button.grid(row=0, column=1, sticky=tk.E)
|
|
||||||
button.bind("<Return>", lambda event: self.apply())
|
|
||||||
self.protocol("WM_DELETE_WINDOW", self._destroy)
|
|
||||||
|
|
||||||
# FIXME: Why are these being called when *creating* the Settings window?
|
# FIXME: Why are these being called when *creating* the Settings window?
|
||||||
# Selectively disable buttons depending on output settings
|
# Selectively disable buttons depending on output settings
|
||||||
@ -326,7 +285,7 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
|
|
||||||
# wait for window to appear on screen before calling grab_set
|
# wait for window to appear on screen before calling grab_set
|
||||||
self.parent.update_idletasks()
|
self.parent.update_idletasks()
|
||||||
self.parent.wm_attributes('-topmost', 0) # needed for dialog to appear ontop of parent on OSX & Linux
|
self.parent.wm_attributes('-topmost', 0) # needed for dialog to appear ontop of parent on Linux
|
||||||
self.wait_visibility()
|
self.wait_visibility()
|
||||||
self.grab_set()
|
self.grab_set()
|
||||||
|
|
||||||
@ -402,16 +361,12 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
# Type ignored due to incorrect type annotation. a 2 tuple does padding for each side
|
# Type ignored due to incorrect type annotation. a 2 tuple does padding for each side
|
||||||
self.outdir_label.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) # type: ignore
|
self.outdir_label.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) # type: ignore
|
||||||
|
|
||||||
self.outdir_entry = nb.Entry(output_frame, takefocus=False)
|
self.outdir_entry = ttk.Entry(output_frame, takefocus=False)
|
||||||
self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
text = _('Browse...') # LANG: NOT-macOS Settings - files location selection button
|
||||||
text = (_('Change...')) # LANG: macOS Preferences - files location selection button
|
|
||||||
|
|
||||||
else:
|
self.outbutton = ttk.Button(
|
||||||
text = (_('Browse...')) # LANG: NOT-macOS Settings - files location selection button
|
|
||||||
|
|
||||||
self.outbutton = nb.Button(
|
|
||||||
output_frame,
|
output_frame,
|
||||||
text=text,
|
text=text,
|
||||||
# Technically this is different from the label in Settings > Output, as *this* is used
|
# Technically this is different from the label in Settings > Output, as *this* is used
|
||||||
@ -421,8 +376,6 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
)
|
)
|
||||||
self.outbutton.grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=row.get())
|
self.outbutton.grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=row.get())
|
||||||
|
|
||||||
nb.Frame(output_frame).grid(row=row.get()) # bottom spacer # TODO: does nothing?
|
|
||||||
|
|
||||||
# LANG: Label for 'Output' Settings/Preferences tab
|
# LANG: Label for 'Output' Settings/Preferences tab
|
||||||
root_notebook.add(output_frame, text=_('Output')) # Tab heading in settings
|
root_notebook.add(output_frame, text=_('Output')) # Tab heading in settings
|
||||||
|
|
||||||
@ -444,7 +397,7 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
logdir = default
|
logdir = default
|
||||||
|
|
||||||
self.logdir.set(logdir)
|
self.logdir.set(logdir)
|
||||||
self.logdir_entry = nb.Entry(config_frame, takefocus=False)
|
self.logdir_entry = ttk.Entry(config_frame, takefocus=False)
|
||||||
|
|
||||||
# Location of the Journal files
|
# Location of the Journal files
|
||||||
nb.Label(
|
nb.Label(
|
||||||
@ -455,14 +408,10 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
|
|
||||||
self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
text = _('Browse...') # LANG: NOT-macOS Setting - files location selection button
|
||||||
text = (_('Change...')) # LANG: macOS Preferences - files location selection button
|
|
||||||
|
|
||||||
else:
|
|
||||||
text = (_('Browse...')) # LANG: NOT-macOS Setting - files location selection button
|
|
||||||
|
|
||||||
with row as cur_row:
|
with row as cur_row:
|
||||||
self.logbutton = nb.Button(
|
self.logbutton = ttk.Button(
|
||||||
config_frame,
|
config_frame,
|
||||||
text=text,
|
text=text,
|
||||||
# LANG: Settings > Configuration - Label for Journal files location
|
# LANG: Settings > Configuration - Label for Journal files location
|
||||||
@ -472,7 +421,7 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
|
|
||||||
if config.default_journal_dir_path:
|
if config.default_journal_dir_path:
|
||||||
# Appearance theme and language setting
|
# Appearance theme and language setting
|
||||||
nb.Button(
|
ttk.Button(
|
||||||
config_frame,
|
config_frame,
|
||||||
# LANG: Settings > Configuration - Label on 'reset journal files location to default' button
|
# LANG: Settings > Configuration - Label on 'reset journal files location to default' button
|
||||||
text=_('Default'),
|
text=_('Default'),
|
||||||
@ -499,7 +448,7 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
variable=self.capi_fleetcarrier
|
variable=self.capi_fleetcarrier
|
||||||
).grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get())
|
).grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||||
|
|
||||||
if sys.platform in ('darwin', 'win32'):
|
if sys.platform == 'win32':
|
||||||
ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid(
|
ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid(
|
||||||
columnspan=4, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
|
columnspan=4, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
|
||||||
)
|
)
|
||||||
@ -511,49 +460,21 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
with row as cur_row:
|
with row as cur_row:
|
||||||
nb.Label(
|
nb.Label(
|
||||||
config_frame,
|
config_frame,
|
||||||
text=_('Keyboard shortcut') if # LANG: Hotkey/Shortcut settings prompt on OSX
|
text=_('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows
|
||||||
sys.platform == 'darwin' else
|
|
||||||
_('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows
|
|
||||||
).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||||
|
|
||||||
if sys.platform == 'darwin' and not was_accessible_at_launch:
|
self.hotkey_text = ttk.Entry(config_frame, width=30, justify=tk.CENTER)
|
||||||
if AXIsProcessTrusted():
|
self.hotkey_text.insert(
|
||||||
# Shortcut settings prompt on OSX
|
0,
|
||||||
nb.Label(
|
# No hotkey/shortcut currently defined
|
||||||
config_frame,
|
# TODO: display Only shows up on windows
|
||||||
# LANG: macOS Preferences > Configuration - restart the app message
|
# LANG: No hotkey/shortcut set
|
||||||
text=_('Re-start {APP} to use shortcuts').format(APP=applongname),
|
hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None')
|
||||||
foreground='firebrick'
|
)
|
||||||
).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
|
||||||
|
|
||||||
else:
|
self.hotkey_text.bind('<FocusIn>', self.hotkeystart)
|
||||||
# Shortcut settings prompt on OSX
|
self.hotkey_text.bind('<FocusOut>', self.hotkeyend)
|
||||||
nb.Label(
|
self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row)
|
||||||
config_frame,
|
|
||||||
# LANG: macOS - Configuration - need to grant the app permission for keyboard shortcuts
|
|
||||||
text=_('{APP} needs permission to use shortcuts').format(APP=applongname),
|
|
||||||
foreground='firebrick'
|
|
||||||
).grid(columnspan=4, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
|
||||||
|
|
||||||
# LANG: Shortcut settings button on OSX
|
|
||||||
nb.Button(config_frame, text=_('Open System Preferences'), command=self.enableshortcuts).grid(
|
|
||||||
padx=self.PADX, pady=self.BOXY, sticky=tk.E, row=cur_row
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.hotkey_text = nb.Entry(config_frame, width=(
|
|
||||||
20 if sys.platform == 'darwin' else 30), justify=tk.CENTER)
|
|
||||||
self.hotkey_text.insert(
|
|
||||||
0,
|
|
||||||
# No hotkey/shortcut currently defined
|
|
||||||
# TODO: display Only shows up on darwin or windows
|
|
||||||
# LANG: No hotkey/shortcut set
|
|
||||||
hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None')
|
|
||||||
)
|
|
||||||
|
|
||||||
self.hotkey_text.bind('<FocusIn>', self.hotkeystart)
|
|
||||||
self.hotkey_text.bind('<FocusOut>', self.hotkeyend)
|
|
||||||
self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row)
|
|
||||||
|
|
||||||
# Hotkey/Shortcut setting
|
# Hotkey/Shortcut setting
|
||||||
self.hotkey_only_btn = nb.Checkbutton(
|
self.hotkey_only_btn = nb.Checkbutton(
|
||||||
@ -700,7 +621,7 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
self.loglevel_dropdown.configure(width=15)
|
self.loglevel_dropdown.configure(width=15)
|
||||||
self.loglevel_dropdown.grid(column=1, pady=self.BOXY, sticky=tk.W, row=cur_row)
|
self.loglevel_dropdown.grid(column=1, pady=self.BOXY, sticky=tk.W, row=cur_row)
|
||||||
|
|
||||||
nb.Button(
|
ttk.Button(
|
||||||
config_frame,
|
config_frame,
|
||||||
# LANG: Label on button used to open a filesystem folder
|
# LANG: Label on button used to open a filesystem folder
|
||||||
text=_('Open Log Folder'), # Button that opens a folder in Explorer/Finder
|
text=_('Open Log Folder'), # Button that opens a folder in Explorer/Finder
|
||||||
@ -803,7 +724,7 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
self.theme_label_0.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
self.theme_label_0.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||||
|
|
||||||
# Main window
|
# Main window
|
||||||
self.theme_button_0 = nb.ColoredButton(
|
self.theme_button_0 = tk.Button(
|
||||||
appearance_frame,
|
appearance_frame,
|
||||||
# LANG: Appearance - Example 'Normal' text
|
# LANG: Appearance - Example 'Normal' text
|
||||||
text=_('Station'),
|
text=_('Station'),
|
||||||
@ -816,7 +737,7 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
with row as cur_row:
|
with row as cur_row:
|
||||||
self.theme_label_1 = nb.Label(appearance_frame, text=self.theme_prompts[1])
|
self.theme_label_1 = nb.Label(appearance_frame, text=self.theme_prompts[1])
|
||||||
self.theme_label_1.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
self.theme_label_1.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||||
self.theme_button_1 = nb.ColoredButton(
|
self.theme_button_1 = tk.Button(
|
||||||
appearance_frame,
|
appearance_frame,
|
||||||
text=' Hutton Orbital ', # Do not translate
|
text=' Hutton Orbital ', # Do not translate
|
||||||
background='grey4',
|
background='grey4',
|
||||||
@ -947,7 +868,7 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
|
padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
|
||||||
)
|
)
|
||||||
|
|
||||||
plugdirentry = nb.Entry(plugins_frame, justify=tk.LEFT)
|
plugdirentry = ttk.Entry(plugins_frame, justify=tk.LEFT)
|
||||||
self.displaypath(plugdir, plugdirentry)
|
self.displaypath(plugdir, plugdirentry)
|
||||||
plugdirentry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
plugdirentry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
||||||
|
|
||||||
@ -959,7 +880,7 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
text=_("Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')
|
text=_("Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')
|
||||||
).grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
).grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||||
|
|
||||||
nb.Button(
|
ttk.Button(
|
||||||
plugins_frame,
|
plugins_frame,
|
||||||
# LANG: Label on button used to open a filesystem folder
|
# LANG: Label on button used to open a filesystem folder
|
||||||
text=_('Open'), # Button that opens a folder in Explorer/Finder
|
text=_('Open'), # Button that opens a folder in Explorer/Finder
|
||||||
@ -1070,14 +991,6 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
def tabchanged(self, event: tk.Event) -> None:
|
def tabchanged(self, event: tk.Event) -> None:
|
||||||
"""Handle preferences active tab changing."""
|
"""Handle preferences active tab changing."""
|
||||||
self.outvarchanged()
|
self.outvarchanged()
|
||||||
if sys.platform == 'darwin':
|
|
||||||
# Hack to recompute size so that buttons show up under Mojave
|
|
||||||
notebook = event.widget
|
|
||||||
frame = self.nametowidget(notebook.winfo_parent())
|
|
||||||
temp = nb.Label(frame)
|
|
||||||
temp.grid()
|
|
||||||
temp.update_idletasks()
|
|
||||||
temp.destroy()
|
|
||||||
|
|
||||||
def outvarchanged(self, event: Optional[tk.Event] = None) -> None:
|
def outvarchanged(self, event: Optional[tk.Event] = None) -> None:
|
||||||
"""Handle Output tab variable changes."""
|
"""Handle Output tab variable changes."""
|
||||||
@ -1139,16 +1052,6 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
entryfield.insert(0, '\\'.join(display))
|
entryfield.insert(0, '\\'.join(display))
|
||||||
|
|
||||||
# None if path doesn't exist
|
# None if path doesn't exist
|
||||||
elif sys.platform == 'darwin' and NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get()):
|
|
||||||
if pathvar.get().startswith(config.home):
|
|
||||||
display = ['~'] + NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())[
|
|
||||||
len(NSFileManager.defaultManager().componentsToDisplayForPath_(config.home)):
|
|
||||||
]
|
|
||||||
|
|
||||||
else:
|
|
||||||
display = NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())
|
|
||||||
|
|
||||||
entryfield.insert(0, '/'.join(display))
|
|
||||||
else:
|
else:
|
||||||
if pathvar.get().startswith(config.home):
|
if pathvar.get().startswith(config.home):
|
||||||
entryfield.insert(0, '~' + pathvar.get()[len(config.home):])
|
entryfield.insert(0, '~' + pathvar.get()[len(config.home):])
|
||||||
@ -1288,7 +1191,7 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
|
|
||||||
config.set('capi_fleetcarrier', self.capi_fleetcarrier.get())
|
config.set('capi_fleetcarrier', self.capi_fleetcarrier.get())
|
||||||
|
|
||||||
if sys.platform in ('darwin', 'win32'):
|
if sys.platform == 'win32':
|
||||||
config.set('hotkey_code', self.hotkey_code)
|
config.set('hotkey_code', self.hotkey_code)
|
||||||
config.set('hotkey_mods', self.hotkey_mods)
|
config.set('hotkey_mods', self.hotkey_mods)
|
||||||
config.set('hotkey_always', int(not self.hotkey_only.get()))
|
config.set('hotkey_always', int(not self.hotkey_only.get()))
|
||||||
@ -1333,25 +1236,3 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
|
|
||||||
self.parent.wm_attributes('-topmost', 1 if config.get_int('always_ontop') else 0)
|
self.parent.wm_attributes('-topmost', 1 if config.get_int('always_ontop') else 0)
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
|
||||||
def enableshortcuts(self) -> None:
|
|
||||||
"""Set up macOS preferences shortcut."""
|
|
||||||
self.apply()
|
|
||||||
# popup System Preferences dialog
|
|
||||||
try:
|
|
||||||
# http://stackoverflow.com/questions/6652598/cocoa-button-opens-a-system-preference-page/6658201
|
|
||||||
from ScriptingBridge import SBApplication # type: ignore
|
|
||||||
sysprefs = 'com.apple.systempreferences'
|
|
||||||
prefs = SBApplication.applicationWithBundleIdentifier_(sysprefs)
|
|
||||||
pane = [x for x in prefs.panes() if x.id() == 'com.apple.preference.security'][0]
|
|
||||||
prefs.setCurrentPane_(pane)
|
|
||||||
anchor = [x for x in pane.anchors() if x.name() == 'Privacy_Accessibility'][0]
|
|
||||||
anchor.reveal()
|
|
||||||
prefs.activate()
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
AXIsProcessTrustedWithOptions({kAXTrustedCheckOptionPrompt: True})
|
|
||||||
|
|
||||||
if not config.shutting_down:
|
|
||||||
self.parent.event_generate('<<Quit>>', when="tail")
|
|
||||||
|
85
protocol.py
85
protocol.py
@ -26,6 +26,7 @@ is_wine = False
|
|||||||
|
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
from ctypes import windll # type: ignore
|
from ctypes import windll # type: ignore
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if windll.ntdll.wine_get_version:
|
if windll.ntdll.wine_get_version:
|
||||||
is_wine = True
|
is_wine = True
|
||||||
@ -58,72 +59,13 @@ class GenericProtocolHandler:
|
|||||||
self.master.event_generate('<<CompanionAuthEvent>>', when="tail")
|
self.master.event_generate('<<CompanionAuthEvent>>', when="tail")
|
||||||
|
|
||||||
|
|
||||||
if sys.platform == 'darwin' and getattr(sys, 'frozen', False): # noqa: C901 # its guarding ALL macos stuff.
|
if (config.auth_force_edmc_protocol # noqa: C901
|
||||||
import struct
|
or (
|
||||||
|
sys.platform == 'win32'
|
||||||
import objc # type: ignore
|
and getattr(sys, 'frozen', False)
|
||||||
from AppKit import NSAppleEventManager, NSObject # type: ignore
|
and not is_wine
|
||||||
|
and not config.auth_force_localserver
|
||||||
kInternetEventClass = kAEGetURL = struct.unpack('>l', b'GURL')[0] # noqa: N816 # API names
|
)):
|
||||||
keyDirectObject = struct.unpack('>l', b'----')[0] # noqa: N816 # API names
|
|
||||||
|
|
||||||
class DarwinProtocolHandler(GenericProtocolHandler):
|
|
||||||
"""
|
|
||||||
MacOS protocol handler implementation.
|
|
||||||
|
|
||||||
Uses macOS event stuff.
|
|
||||||
"""
|
|
||||||
|
|
||||||
POLL = 100 # ms
|
|
||||||
|
|
||||||
def start(self, master: 'tkinter.Tk') -> None:
|
|
||||||
"""Start Protocol Handler."""
|
|
||||||
GenericProtocolHandler.start(self, master)
|
|
||||||
self.lasturl: str | None = None
|
|
||||||
self.eventhandler = EventHandler.alloc().init()
|
|
||||||
|
|
||||||
def poll(self) -> None:
|
|
||||||
"""Poll event until URL is updated."""
|
|
||||||
# No way of signalling to Tkinter from within the callback handler block that doesn't cause Python to crash,
|
|
||||||
# so poll. TODO: Resolved?
|
|
||||||
if self.lasturl and self.lasturl.startswith(self.redirect):
|
|
||||||
self.event(self.lasturl)
|
|
||||||
self.lasturl = None
|
|
||||||
|
|
||||||
class EventHandler(NSObject):
|
|
||||||
"""Handle NSAppleEventManager IPC stuff."""
|
|
||||||
|
|
||||||
def init(self) -> None:
|
|
||||||
"""
|
|
||||||
Init method for handler.
|
|
||||||
|
|
||||||
(I'd assume this is related to the subclassing of NSObject for why its not __init__)
|
|
||||||
"""
|
|
||||||
self = objc.super(EventHandler, self).init()
|
|
||||||
NSAppleEventManager.sharedAppleEventManager().setEventHandler_andSelector_forEventClass_andEventID_(
|
|
||||||
self,
|
|
||||||
'handleEvent:withReplyEvent:',
|
|
||||||
kInternetEventClass,
|
|
||||||
kAEGetURL
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def handleEvent_withReplyEvent_(self, event, replyEvent) -> None: # noqa: N802 N803 # Required to override
|
|
||||||
"""Actual event handling from NSAppleEventManager."""
|
|
||||||
protocolhandler.lasturl = parse.unquote(
|
|
||||||
event.paramDescriptorForKeyword_(keyDirectObject).stringValue()
|
|
||||||
).strip()
|
|
||||||
|
|
||||||
protocolhandler.master.after(DarwinProtocolHandler.POLL, protocolhandler.poll)
|
|
||||||
|
|
||||||
|
|
||||||
elif (config.auth_force_edmc_protocol
|
|
||||||
or (
|
|
||||||
sys.platform == 'win32'
|
|
||||||
and getattr(sys, 'frozen', False)
|
|
||||||
and not is_wine
|
|
||||||
and not config.auth_force_localserver
|
|
||||||
)):
|
|
||||||
# This could be false if you use auth_force_edmc_protocol, but then you get to keep the pieces
|
# This could be false if you use auth_force_edmc_protocol, but then you get to keep the pieces
|
||||||
assert sys.platform == 'win32'
|
assert sys.platform == 'win32'
|
||||||
# spell-checker: words HBRUSH HICON WPARAM wstring WNDCLASS HMENU HGLOBAL
|
# spell-checker: words HBRUSH HICON WPARAM wstring WNDCLASS HMENU HGLOBAL
|
||||||
@ -245,11 +187,11 @@ elif (config.auth_force_edmc_protocol
|
|||||||
# which we can read out as shown below, and then compare.
|
# which we can read out as shown below, and then compare.
|
||||||
|
|
||||||
target_is_valid = lparam_low == 0 or (
|
target_is_valid = lparam_low == 0 or (
|
||||||
GlobalGetAtomNameW(lparam_low, service, 256) and service.value == appname
|
GlobalGetAtomNameW(lparam_low, service, 256) and service.value == appname
|
||||||
)
|
)
|
||||||
|
|
||||||
topic_is_valid = lparam_high == 0 or (
|
topic_is_valid = lparam_high == 0 or (
|
||||||
GlobalGetAtomNameW(lparam_high, topic, 256) and topic.value.lower() == 'system'
|
GlobalGetAtomNameW(lparam_high, topic, 256) and topic.value.lower() == 'system'
|
||||||
)
|
)
|
||||||
|
|
||||||
if target_is_valid and topic_is_valid:
|
if target_is_valid and topic_is_valid:
|
||||||
@ -480,12 +422,9 @@ def get_handler_impl() -> Type[GenericProtocolHandler]:
|
|||||||
|
|
||||||
:return: An instantiatable GenericProtocolHandler
|
:return: An instantiatable GenericProtocolHandler
|
||||||
"""
|
"""
|
||||||
if sys.platform == 'darwin' and getattr(sys, 'frozen', False):
|
|
||||||
return DarwinProtocolHandler # pyright: reportUnboundVariable=false
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(sys.platform == 'win32' and config.auth_force_edmc_protocol)
|
(sys.platform == 'win32' and config.auth_force_edmc_protocol)
|
||||||
or (getattr(sys, 'frozen', False) and not is_wine and not config.auth_force_localserver)
|
or (getattr(sys, 'frozen', False) and not is_wine and not config.auth_force_localserver)
|
||||||
):
|
):
|
||||||
return WindowsProtocolHandler
|
return WindowsProtocolHandler
|
||||||
|
|
||||||
|
@ -23,6 +23,3 @@ sys-platform-not-darwin = "sys_platform == 'darwin'"
|
|||||||
sys-platform-linux = "sys_platform != 'linux'"
|
sys-platform-linux = "sys_platform != 'linux'"
|
||||||
sys-platform-not-linux = "sys_platform == 'linux'"
|
sys-platform-not-linux = "sys_platform == 'linux'"
|
||||||
sys-platform-not-known = "sys_platform in ('darwin', 'linux', 'win32')"
|
sys-platform-not-known = "sys_platform in ('darwin', 'linux', 'win32')"
|
||||||
|
|
||||||
[tool.pyright]
|
|
||||||
# pythonPlatform = 'Darwin'
|
|
||||||
|
@ -5,7 +5,7 @@ wheel
|
|||||||
# We can't rely on just picking this up from either the base (not venv),
|
# We can't rely on just picking this up from either the base (not venv),
|
||||||
# or venv-init-time version. Specify here so that dependabot will prod us
|
# or venv-init-time version. Specify here so that dependabot will prod us
|
||||||
# about new versions.
|
# about new versions.
|
||||||
setuptools==69.1.1
|
setuptools==69.2.0
|
||||||
|
|
||||||
# Static analysis tools
|
# Static analysis tools
|
||||||
flake8==7.0.0
|
flake8==7.0.0
|
||||||
@ -18,14 +18,14 @@ flake8-noqa==1.4.0
|
|||||||
flake8-polyfill==1.0.2
|
flake8-polyfill==1.0.2
|
||||||
flake8-use-fstring==1.4
|
flake8-use-fstring==1.4
|
||||||
|
|
||||||
mypy==1.8.0
|
mypy==1.9.0
|
||||||
pep8-naming==0.13.3
|
pep8-naming==0.13.3
|
||||||
safety==2.3.5
|
safety==3.0.1
|
||||||
types-requests==2.31.0.20240125
|
types-requests==2.31.0.20240311
|
||||||
types-pkg-resources==0.1.3
|
types-pkg-resources==0.1.3
|
||||||
|
|
||||||
# Code formatting tools
|
# Code formatting tools
|
||||||
autopep8==2.0.4
|
autopep8==2.1.0
|
||||||
|
|
||||||
# Git pre-commit checking
|
# Git pre-commit checking
|
||||||
pre-commit==3.6.2
|
pre-commit==3.6.2
|
||||||
@ -38,9 +38,9 @@ grip==4.6.2
|
|||||||
py2exe==0.13.0.1; sys_platform == 'win32'
|
py2exe==0.13.0.1; sys_platform == 'win32'
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
pytest==8.0.2
|
pytest==8.1.1
|
||||||
pytest-cov==4.1.0 # Pytest code coverage support
|
pytest-cov==4.1.0 # Pytest code coverage support
|
||||||
coverage[toml]==7.4.1 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
|
coverage[toml]==7.4.4 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
|
||||||
coverage-conditional-plugin==0.9.0
|
coverage-conditional-plugin==0.9.0
|
||||||
# For manipulating folder permissions and the like.
|
# For manipulating folder permissions and the like.
|
||||||
pywin32==306; sys_platform == 'win32'
|
pywin32==306; sys_platform == 'win32'
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
certifi==2023.11.17
|
certifi==2024.2.2
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
|
pillow==10.3.0
|
||||||
# requests depends on this now ?
|
# requests depends on this now ?
|
||||||
charset-normalizer==2.1.1
|
charset-normalizer==3.3.2
|
||||||
|
|
||||||
watchdog==3.0.0
|
watchdog==3.0.0
|
||||||
# Commented out because this doesn't package well with py2exe
|
# Commented out because this doesn't package well with py2exe
|
||||||
@ -9,6 +10,3 @@ infi.systray==0.1.12; sys_platform == 'win32'
|
|||||||
# argh==0.26.2 watchdog dep
|
# argh==0.26.2 watchdog dep
|
||||||
# pyyaml==5.3.1 watchdog dep
|
# pyyaml==5.3.1 watchdog dep
|
||||||
semantic-version==2.10.0
|
semantic-version==2.10.0
|
||||||
|
|
||||||
# Base requirement for MacOS
|
|
||||||
pyobjc; sys_platform == 'darwin'
|
|
||||||
|
13
stats.py
13
stats.py
@ -374,7 +374,7 @@ class StatsResults(tk.Toplevel):
|
|||||||
self.transient(parent)
|
self.transient(parent)
|
||||||
|
|
||||||
# position over parent
|
# position over parent
|
||||||
if sys.platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
if parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
||||||
self.geometry(f"+{parent.winfo_rootx()}+{parent.winfo_rooty()}")
|
self.geometry(f"+{parent.winfo_rootx()}+{parent.winfo_rooty()}")
|
||||||
|
|
||||||
# remove decoration
|
# remove decoration
|
||||||
@ -382,10 +382,6 @@ class StatsResults(tk.Toplevel):
|
|||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
self.attributes('-toolwindow', tk.TRUE)
|
self.attributes('-toolwindow', tk.TRUE)
|
||||||
|
|
||||||
elif sys.platform == 'darwin':
|
|
||||||
# http://wiki.tcl.tk/13428
|
|
||||||
parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
|
|
||||||
|
|
||||||
frame = ttk.Frame(self)
|
frame = ttk.Frame(self)
|
||||||
frame.grid(sticky=tk.NSEW)
|
frame.grid(sticky=tk.NSEW)
|
||||||
|
|
||||||
@ -423,13 +419,6 @@ class StatsResults(tk.Toplevel):
|
|||||||
ttk.Frame(page).grid(pady=5) # bottom spacer
|
ttk.Frame(page).grid(pady=5) # bottom spacer
|
||||||
notebook.add(page, text=_('Ships')) # LANG: Status dialog title
|
notebook.add(page, text=_('Ships')) # LANG: Status dialog title
|
||||||
|
|
||||||
if sys.platform != 'darwin':
|
|
||||||
buttonframe = ttk.Frame(frame)
|
|
||||||
buttonframe.grid(padx=10, pady=(0, 10), sticky=tk.NSEW) # type: ignore # the tuple is supported
|
|
||||||
buttonframe.columnconfigure(0, weight=1)
|
|
||||||
ttk.Label(buttonframe).grid(row=0, column=0) # spacer
|
|
||||||
ttk.Button(buttonframe, text='OK', command=self.destroy).grid(row=0, column=1, sticky=tk.E)
|
|
||||||
|
|
||||||
# wait for window to appear on screen before calling grab_set
|
# wait for window to appear on screen before calling grab_set
|
||||||
self.wait_visibility()
|
self.wait_visibility()
|
||||||
self.grab_set()
|
self.grab_set()
|
||||||
|
3
td.py
3
td.py
@ -1,7 +1,6 @@
|
|||||||
"""Export data for Trade Dangerous."""
|
"""Export data for Trade Dangerous."""
|
||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
@ -32,7 +31,7 @@ def export(data: CAPIData) -> None:
|
|||||||
with open(data_path / data_filename, 'wb') as h:
|
with open(data_path / data_filename, 'wb') as h:
|
||||||
# Format described here: https://github.com/eyeonus/Trade-Dangerous/wiki/Price-Data
|
# Format described here: https://github.com/eyeonus/Trade-Dangerous/wiki/Price-Data
|
||||||
h.write('#! trade.py import -\n'.encode('utf-8'))
|
h.write('#! trade.py import -\n'.encode('utf-8'))
|
||||||
this_platform = "Mac OS" if sys.platform == 'darwin' else system()
|
this_platform = system()
|
||||||
cmdr_name = data['commander']['name'].strip()
|
cmdr_name = data['commander']['name'].strip()
|
||||||
h.write(
|
h.write(
|
||||||
f'# Created by {applongname} {appversion()} on {this_platform} for Cmdr {cmdr_name}.\n'.encode('utf-8')
|
f'# Created by {applongname} {appversion()} on {this_platform} for Cmdr {cmdr_name}.\n'.encode('utf-8')
|
||||||
|
@ -5,21 +5,15 @@ import numbers
|
|||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from configparser import NoOptionError
|
from configparser import NoOptionError
|
||||||
from os import getenv, makedirs, mkdir, pardir
|
from os import getenv, makedirs, mkdir
|
||||||
from os.path import dirname, expanduser, isdir, join, normpath
|
from os.path import dirname, expanduser, isdir, join
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from config import applongname, appname, update_interval
|
from config import applongname, appname, update_interval
|
||||||
from EDMCLogging import get_main_logger
|
from EDMCLogging import get_main_logger
|
||||||
|
|
||||||
logger = get_main_logger()
|
logger = get_main_logger()
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'win32':
|
||||||
from Foundation import ( # type: ignore
|
|
||||||
NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains,
|
|
||||||
NSUserDefaults, NSUserDomainMask
|
|
||||||
)
|
|
||||||
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import uuid
|
import uuid
|
||||||
from ctypes.wintypes import DWORD, HANDLE, HKEY, LONG, LPCVOID, LPCWSTR
|
from ctypes.wintypes import DWORD, HANDLE, HKEY, LONG, LPCVOID, LPCWSTR
|
||||||
@ -115,91 +109,7 @@ class OldConfig:
|
|||||||
OUT_EDDN_DELAY = 4096
|
OUT_EDDN_DELAY = 4096
|
||||||
OUT_STATION_ANY = OUT_EDDN_SEND_STATION_DATA | OUT_MKT_TD | OUT_MKT_CSV
|
OUT_STATION_ANY = OUT_EDDN_SEND_STATION_DATA | OUT_MKT_TD | OUT_MKT_CSV
|
||||||
|
|
||||||
if sys.platform == 'darwin': # noqa: C901 # It's gating *all* the functions
|
if sys.platform == 'win32': # noqa: C901
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.app_dir = join(
|
|
||||||
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0], appname
|
|
||||||
)
|
|
||||||
if not isdir(self.app_dir):
|
|
||||||
mkdir(self.app_dir)
|
|
||||||
|
|
||||||
self.plugin_dir = join(self.app_dir, 'plugins')
|
|
||||||
if not isdir(self.plugin_dir):
|
|
||||||
mkdir(self.plugin_dir)
|
|
||||||
|
|
||||||
if getattr(sys, 'frozen', False):
|
|
||||||
self.internal_plugin_dir = normpath(join(dirname(sys.executable), pardir, 'Library', 'plugins'))
|
|
||||||
self.respath = normpath(join(dirname(sys.executable), pardir, 'Resources'))
|
|
||||||
self.identifier = NSBundle.mainBundle().bundleIdentifier()
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.internal_plugin_dir = join(dirname(__file__), 'plugins')
|
|
||||||
self.respath = dirname(__file__)
|
|
||||||
# Don't use Python's settings if interactive
|
|
||||||
self.identifier = f'uk.org.marginal.{appname.lower()}'
|
|
||||||
NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier
|
|
||||||
|
|
||||||
self.default_journal_dir: str | None = join(
|
|
||||||
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0],
|
|
||||||
'Frontier Developments',
|
|
||||||
'Elite Dangerous'
|
|
||||||
)
|
|
||||||
self.home = expanduser('~')
|
|
||||||
|
|
||||||
self.defaults = NSUserDefaults.standardUserDefaults()
|
|
||||||
self.settings = dict(self.defaults.persistentDomainForName_(self.identifier) or {}) # make writeable
|
|
||||||
|
|
||||||
# Check out_dir exists
|
|
||||||
if not self.get('outdir') or not isdir(str(self.get('outdir'))):
|
|
||||||
self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0])
|
|
||||||
|
|
||||||
def get(self, key: str, default: None | list | str = None) -> None | list | str:
|
|
||||||
"""Look up a string configuration value."""
|
|
||||||
val = self.settings.get(key)
|
|
||||||
if val is None:
|
|
||||||
return default
|
|
||||||
|
|
||||||
if isinstance(val, str):
|
|
||||||
return str(val)
|
|
||||||
|
|
||||||
if isinstance(val, list):
|
|
||||||
return list(val) # make writeable
|
|
||||||
|
|
||||||
return default
|
|
||||||
|
|
||||||
def getint(self, key: str, default: int = 0) -> int:
|
|
||||||
"""Look up an integer configuration value."""
|
|
||||||
try:
|
|
||||||
return int(self.settings.get(key, default)) # should already be int, but check by casting
|
|
||||||
|
|
||||||
except ValueError as e:
|
|
||||||
logger.error(f"Failed to int({key=})", exc_info=e)
|
|
||||||
return default
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug('The exception type is ...', exc_info=e)
|
|
||||||
return default
|
|
||||||
|
|
||||||
def set(self, key: str, val: int | str | list) -> None:
|
|
||||||
"""Set value on the specified configuration key."""
|
|
||||||
self.settings[key] = val
|
|
||||||
|
|
||||||
def delete(self, key: str) -> None:
|
|
||||||
"""Delete the specified configuration key."""
|
|
||||||
self.settings.pop(key, None)
|
|
||||||
|
|
||||||
def save(self) -> None:
|
|
||||||
"""Save current configuration to disk."""
|
|
||||||
self.defaults.setPersistentDomain_forName_(self.settings, self.identifier)
|
|
||||||
self.defaults.synchronize()
|
|
||||||
|
|
||||||
def close(self) -> None:
|
|
||||||
"""Close the configuration."""
|
|
||||||
self.save()
|
|
||||||
self.defaults = None
|
|
||||||
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname) # type: ignore
|
self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname) # type: ignore
|
||||||
|
23
theme.py
23
theme.py
@ -150,7 +150,7 @@ class _Theme:
|
|||||||
# the widget has explicit fg or bg attributes.
|
# the widget has explicit fg or bg attributes.
|
||||||
assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget
|
assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget
|
||||||
if not self.defaults:
|
if not self.defaults:
|
||||||
# Can't initialise this til window is created # Windows, MacOS
|
# Can't initialise this til window is created # Windows
|
||||||
self.defaults = {
|
self.defaults = {
|
||||||
'fg': tk.Label()['foreground'], # SystemButtonText, systemButtonText
|
'fg': tk.Label()['foreground'], # SystemButtonText, systemButtonText
|
||||||
'bg': tk.Label()['background'], # SystemButtonFace, White
|
'bg': tk.Label()['background'], # SystemButtonFace, White
|
||||||
@ -268,8 +268,7 @@ class _Theme:
|
|||||||
# (Mostly) system colors
|
# (Mostly) system colors
|
||||||
style = ttk.Style()
|
style = ttk.Style()
|
||||||
self.current = {
|
self.current = {
|
||||||
'background': (sys.platform == 'darwin' and 'systemMovableModalBackground' or
|
'background': (style.lookup('TLabel', 'background')),
|
||||||
style.lookup('TLabel', 'background')),
|
|
||||||
'foreground': style.lookup('TLabel', 'foreground'),
|
'foreground': style.lookup('TLabel', 'foreground'),
|
||||||
'activebackground': (sys.platform == 'win32' and 'SystemHighlight' or
|
'activebackground': (sys.platform == 'win32' and 'SystemHighlight' or
|
||||||
style.lookup('TLabel', 'background', ['active'])),
|
style.lookup('TLabel', 'background', ['active'])),
|
||||||
@ -366,8 +365,6 @@ class _Theme:
|
|||||||
if 'bg' not in attribs:
|
if 'bg' not in attribs:
|
||||||
widget['background'] = self.current['background']
|
widget['background'] = self.current['background']
|
||||||
widget['activebackground'] = self.current['activebackground']
|
widget['activebackground'] = self.current['activebackground']
|
||||||
if sys.platform == 'darwin' and isinstance(widget, tk.Button):
|
|
||||||
widget['highlightbackground'] = self.current['background']
|
|
||||||
|
|
||||||
if 'font' not in attribs:
|
if 'font' not in attribs:
|
||||||
widget['font'] = self.current['font']
|
widget['font'] = self.current['font']
|
||||||
@ -426,21 +423,7 @@ class _Theme:
|
|||||||
return # Don't need to mess with the window manager
|
return # Don't need to mess with the window manager
|
||||||
self.active = theme
|
self.active = theme
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'win32':
|
||||||
from AppKit import NSAppearance, NSApplication, NSMiniaturizableWindowMask, NSResizableWindowMask
|
|
||||||
root.update_idletasks() # need main window to be created
|
|
||||||
if theme == self.THEME_DEFAULT:
|
|
||||||
appearance = NSAppearance.appearanceNamed_('NSAppearanceNameAqua')
|
|
||||||
|
|
||||||
else: # Dark (Transparent only on win32)
|
|
||||||
appearance = NSAppearance.appearanceNamed_('NSAppearanceNameDarkAqua')
|
|
||||||
|
|
||||||
for window in NSApplication.sharedApplication().windows():
|
|
||||||
window.setStyleMask_(window.styleMask() & ~(
|
|
||||||
NSMiniaturizableWindowMask | NSResizableWindowMask)) # disable zoom
|
|
||||||
window.setAppearance_(appearance)
|
|
||||||
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
GWL_STYLE = -16 # noqa: N806 # ctypes
|
GWL_STYLE = -16 # noqa: N806 # ctypes
|
||||||
WS_MAXIMIZEBOX = 0x00010000 # noqa: N806 # ctypes
|
WS_MAXIMIZEBOX = 0x00010000 # noqa: N806 # ctypes
|
||||||
# tk8.5.9/win/tkWinWm.c:342
|
# tk8.5.9/win/tkWinWm.c:342
|
||||||
|
@ -22,7 +22,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
import warnings
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from tkinter import font as tk_font
|
from tkinter import font as tk_font
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
@ -32,8 +31,7 @@ if TYPE_CHECKING:
|
|||||||
def _(x: str) -> str: return x
|
def _(x: str) -> str: return x
|
||||||
|
|
||||||
|
|
||||||
# FIXME: Split this into multi-file module to separate the platforms
|
class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore
|
||||||
class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # type: ignore
|
|
||||||
"""Clickable label for HTTP links."""
|
"""Clickable label for HTTP links."""
|
||||||
|
|
||||||
def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> None:
|
def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> None:
|
||||||
@ -51,22 +49,14 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ
|
|||||||
self.foreground = kw.get('foreground', 'blue')
|
self.foreground = kw.get('foreground', 'blue')
|
||||||
self.disabledforeground = kw.pop('disabledforeground', ttk.Style().lookup(
|
self.disabledforeground = kw.pop('disabledforeground', ttk.Style().lookup(
|
||||||
'TLabel', 'foreground', ('disabled',))) # ttk.Label doesn't support disabledforeground option
|
'TLabel', 'foreground', ('disabled',))) # ttk.Label doesn't support disabledforeground option
|
||||||
|
ttk.Label.__init__(self, master, **kw)
|
||||||
if sys.platform == 'darwin':
|
|
||||||
# Use tk.Label 'cos can't set ttk.Label background - http://www.tkdocs.com/tutorial/styles.html#whydifficult
|
|
||||||
kw['background'] = kw.pop('background', 'systemDialogBackgroundActive')
|
|
||||||
kw['anchor'] = kw.pop('anchor', tk.W) # like ttk.Label
|
|
||||||
tk.Label.__init__(self, master, **kw)
|
|
||||||
|
|
||||||
else:
|
|
||||||
ttk.Label.__init__(self, master, **kw)
|
|
||||||
|
|
||||||
self.bind('<Button-1>', self._click)
|
self.bind('<Button-1>', self._click)
|
||||||
|
|
||||||
self.menu = tk.Menu(tearoff=tk.FALSE)
|
self.menu = tk.Menu(tearoff=tk.FALSE)
|
||||||
# LANG: Label for 'Copy' as in 'Copy and Paste'
|
# LANG: Label for 'Copy' as in 'Copy and Paste'
|
||||||
self.menu.add_command(label=_('Copy'), command=self.copy) # As in Copy and Paste
|
self.menu.add_command(label=_('Copy'), command=self.copy) # As in Copy and Paste
|
||||||
self.bind(sys.platform == 'darwin' and '<Button-2>' or '<Button-3>', self._contextmenu)
|
self.bind('<Button-3>', self._contextmenu)
|
||||||
|
|
||||||
self.bind('<Enter>', self._enter)
|
self.bind('<Enter>', self._enter)
|
||||||
self.bind('<Leave>', self._leave)
|
self.bind('<Leave>', self._leave)
|
||||||
@ -107,10 +97,9 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ
|
|||||||
if state == tk.DISABLED:
|
if state == tk.DISABLED:
|
||||||
kw['cursor'] = 'arrow' # System default
|
kw['cursor'] = 'arrow' # System default
|
||||||
elif self.url and (kw['text'] if 'text' in kw else self['text']):
|
elif self.url and (kw['text'] if 'text' in kw else self['text']):
|
||||||
kw['cursor'] = 'pointinghand' if sys.platform == 'darwin' else 'hand2'
|
kw['cursor'] = 'hand2'
|
||||||
else:
|
else:
|
||||||
kw['cursor'] = 'notallowed' if sys.platform == 'darwin' else (
|
kw['cursor'] = ('no' if sys.platform == 'win32' else 'circle')
|
||||||
'no' if sys.platform == 'win32' else 'circle')
|
|
||||||
|
|
||||||
return super().configure(cnf, **kw)
|
return super().configure(cnf, **kw)
|
||||||
|
|
||||||
@ -140,50 +129,9 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ
|
|||||||
|
|
||||||
def _contextmenu(self, event: tk.Event) -> None:
|
def _contextmenu(self, event: tk.Event) -> None:
|
||||||
if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy):
|
if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy):
|
||||||
self.menu.post(sys.platform == 'darwin' and event.x_root + 1 or event.x_root, event.y_root)
|
self.menu.post(event.x_root, event.y_root)
|
||||||
|
|
||||||
def copy(self) -> None:
|
def copy(self) -> None:
|
||||||
"""Copy the current text to the clipboard."""
|
"""Copy the current text to the clipboard."""
|
||||||
self.clipboard_clear()
|
self.clipboard_clear()
|
||||||
self.clipboard_append(self['text'])
|
self.clipboard_append(self['text'])
|
||||||
|
|
||||||
|
|
||||||
def openurl(url: str) -> None:
|
|
||||||
r"""
|
|
||||||
Open the given URL in appropriate browser.
|
|
||||||
|
|
||||||
2022-12-06:
|
|
||||||
Firefox itself will gladly attempt to use very long URLs in its URL
|
|
||||||
input. Up to 16384 was attempted, but the Apache instance this was
|
|
||||||
tested against only allowed up to 8207 total URL length to pass, that
|
|
||||||
being 8190 octets of REQUEST_URI (path + GET params).
|
|
||||||
|
|
||||||
Testing from Windows 10 Home 21H2 cmd.exe with:
|
|
||||||
|
|
||||||
"<path to>\firefox.exe" -osint -url "<test url>"
|
|
||||||
|
|
||||||
only allowed 8115 octest of REQUEST_URI to pass through.
|
|
||||||
|
|
||||||
Microsoft Edge yielded 8092 octets. Google Chrome yielded 8093 octets.
|
|
||||||
|
|
||||||
However, this is actually the limit of how long a CMD.EXE command-line
|
|
||||||
can be. The URL was being cut off *there*.
|
|
||||||
|
|
||||||
The 8207 octet URL makes it through `webbrowser.open(<url>)` to:
|
|
||||||
|
|
||||||
Firefox 107.0.1
|
|
||||||
Microsoft Edge 108.0.1462.42
|
|
||||||
Google Chrome 108.0.5359.95
|
|
||||||
|
|
||||||
This was also tested as working *with* the old winreg/subprocess code,
|
|
||||||
so it wasn't even suffering from the same limit as CMD.EXE.
|
|
||||||
|
|
||||||
Conclusion: No reason to not just use `webbrowser.open()`, as prior
|
|
||||||
to e280d6c2833c25867b8139490e68ddf056477917 there was a bug, introduced
|
|
||||||
in 5989acd0d3263e54429ff99769ff73a20476d863, which meant the code always
|
|
||||||
ended up using `webbrowser.open()` *anyway*.
|
|
||||||
:param url: URL to open.
|
|
||||||
"""
|
|
||||||
warnings.warn("This function is deprecated. "
|
|
||||||
"Please use `webbrowser.open() instead.", DeprecationWarning, stacklevel=2)
|
|
||||||
webbrowser.open(url)
|
|
||||||
|
33
update.py
33
update.py
@ -7,10 +7,8 @@ See LICENSE file.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
from os.path import dirname, join
|
|
||||||
from traceback import print_exc
|
from traceback import print_exc
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
@ -115,21 +113,6 @@ class Updater:
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
|
||||||
import objc
|
|
||||||
|
|
||||||
try:
|
|
||||||
objc.loadBundle(
|
|
||||||
'Sparkle', globals(), join(dirname(sys.executable), os.pardir, 'Frameworks', 'Sparkle.framework')
|
|
||||||
)
|
|
||||||
# loadBundle presumably supplies `SUUpdater`
|
|
||||||
self.updater = SUUpdater.sharedUpdater() # noqa: F821
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
# can't load framework - not frozen or not included in app bundle?
|
|
||||||
print_exc()
|
|
||||||
self.updater = None
|
|
||||||
|
|
||||||
def set_automatic_updates_check(self, onoroff: bool) -> None:
|
def set_automatic_updates_check(self, onoroff: bool) -> None:
|
||||||
"""
|
"""
|
||||||
Set (Win)Sparkle to perform automatic update checks, or not.
|
Set (Win)Sparkle to perform automatic update checks, or not.
|
||||||
@ -142,9 +125,6 @@ class Updater:
|
|||||||
if sys.platform == 'win32' and self.updater:
|
if sys.platform == 'win32' and self.updater:
|
||||||
self.updater.win_sparkle_set_automatic_check_for_updates(onoroff)
|
self.updater.win_sparkle_set_automatic_check_for_updates(onoroff)
|
||||||
|
|
||||||
if sys.platform == 'darwin' and self.updater:
|
|
||||||
self.updater.SUEnableAutomaticChecks(onoroff)
|
|
||||||
|
|
||||||
def check_for_updates(self) -> None:
|
def check_for_updates(self) -> None:
|
||||||
"""Trigger the requisite method to check for an update."""
|
"""Trigger the requisite method to check for an update."""
|
||||||
if self.use_internal():
|
if self.use_internal():
|
||||||
@ -155,9 +135,6 @@ class Updater:
|
|||||||
elif sys.platform == 'win32' and self.updater:
|
elif sys.platform == 'win32' and self.updater:
|
||||||
self.updater.win_sparkle_check_update_with_ui()
|
self.updater.win_sparkle_check_update_with_ui()
|
||||||
|
|
||||||
elif sys.platform == 'darwin' and self.updater:
|
|
||||||
self.updater.checkForUpdates_(None)
|
|
||||||
|
|
||||||
def check_appcast(self) -> EDMCVersion | None:
|
def check_appcast(self) -> EDMCVersion | None:
|
||||||
"""
|
"""
|
||||||
Manually (no Sparkle or WinSparkle) check the update_feed appcast file.
|
Manually (no Sparkle or WinSparkle) check the update_feed appcast file.
|
||||||
@ -184,13 +161,9 @@ class Updater:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
# For *these* purposes all systems are the same as 'windows', as
|
||||||
sparkle_platform = 'macos'
|
# non-win32 would be running from source.
|
||||||
|
sparkle_platform = 'windows'
|
||||||
else:
|
|
||||||
# For *these* purposes anything else is the same as 'windows', as
|
|
||||||
# non-win32 would be running from source.
|
|
||||||
sparkle_platform = 'windows'
|
|
||||||
|
|
||||||
for item in feed.findall('channel/item'):
|
for item in feed.findall('channel/item'):
|
||||||
# xml is a pain with types, hence these ignores
|
# xml is a pain with types, hence these ignores
|
||||||
|
Loading…
x
Reference in New Issue
Block a user