Compare commits
4 Commits
8bca8b4f5e
...
4d163ad0f1
Author | SHA1 | Date |
---|---|---|
Jaka Perovšek | 4d163ad0f1 | |
Jaka Perovšek | dab2144e7f | |
Jaka Perovšek | b9d4b63a4f | |
Jaka Perovšek | 78f911ab41 |
22
deploy.py
22
deploy.py
|
@ -22,6 +22,7 @@ def rmfulldir(dirpath):
|
|||
|
||||
|
||||
def update():
|
||||
logging.info(f"checking for updates via git pull.")
|
||||
pull = ["utils/git/bin/git.exe", "pull"]
|
||||
subprocess.call(pull)
|
||||
|
||||
|
@ -186,9 +187,6 @@ def prepare_images(data, config):
|
|||
|
||||
|
||||
def build_xml_code(data, config):
|
||||
logging.info(f"copying filelist.xml")
|
||||
shutil.copy("./source/filelist.xml", "./build/filelist.xml")
|
||||
|
||||
logging.info(f"calculate the value that lets you use the songs n-times")
|
||||
song_lengths = [OggVorbis(f"./build/music/{tape['identifier']}.ogg").info.length
|
||||
for tape in data]
|
||||
|
@ -205,6 +203,7 @@ def build_xml_code(data, config):
|
|||
j2env.globals.update(zip=zip)
|
||||
|
||||
# load the template file
|
||||
template0 = j2env.get_template("./source/filelist_template.xml")
|
||||
template1 = j2env.get_template("./source/sunken_tapes_template.xml")
|
||||
|
||||
if config["use_ita"]:
|
||||
|
@ -213,6 +212,10 @@ def build_xml_code(data, config):
|
|||
template2 = j2env.get_template("./source/sunken_tapes_style_template.xml")
|
||||
|
||||
logging.info(f"rendering the xml files")
|
||||
with open("./build/filelist.xml", "w+", encoding="utf8") as output_file:
|
||||
# render the template
|
||||
output_file.write(template0.render(config=config))
|
||||
|
||||
with open("./build/sunken_tapes.xml", "w+", encoding="utf8") as output_file:
|
||||
# render the template
|
||||
output_file.write(template1.render(tapes=data, config=config,
|
||||
|
@ -221,7 +224,7 @@ def build_xml_code(data, config):
|
|||
|
||||
with open("./build/sunken_tapes_style.xml", "w+", encoding="utf8") as output_file:
|
||||
# render the template
|
||||
output_file.write(template2.render(tapes=data))
|
||||
output_file.write(template2.render(tapes=data, config=config))
|
||||
|
||||
|
||||
def deploy(config):
|
||||
|
@ -241,12 +244,17 @@ def deploy(config):
|
|||
prepare_images(data, config)
|
||||
build_xml_code(data, config)
|
||||
|
||||
logging.info(f"removing the old installed mod directory {config['installdir'] + '/Mods/Sunken Tapes/'}")
|
||||
rmfulldir(config["installdir"] + "/Mods/Sunken Tapes/")
|
||||
if config["override_workshop"]:
|
||||
mod_directory = "/Mods/Sunken Tapes/"
|
||||
else:
|
||||
mod_directory = "/Mods/Sunken Tapez/"
|
||||
|
||||
logging.info(f"removing the old installed mod directory {config['installdir'] + mod_directory}")
|
||||
rmfulldir(config["installdir"] + mod_directory)
|
||||
|
||||
logging.info(f"copying the new build")
|
||||
if Path(config["installdir"]).is_dir():
|
||||
copy_tree("./build/", config["installdir"] + "/Mods/Sunken Tapes/")
|
||||
copy_tree("./build/", config["installdir"] + mod_directory)
|
||||
else:
|
||||
raise FileNotFoundError(
|
||||
f"{config['installdir']} does not exist. Set up the correct Barotrauma installation directory")
|
||||
|
|
45
main.py
45
main.py
|
@ -3,6 +3,7 @@ import tkinter as tk
|
|||
from tkinter import filedialog as fd
|
||||
from tkinter.messagebox import showinfo, askyesno, askokcancel
|
||||
from tkinter import ttk
|
||||
from tooltip import Hovertip
|
||||
from pathlib import Path
|
||||
from deploy import download_ffmpeg, download_git, deploy, get_ffmpeg_version, update
|
||||
import logging
|
||||
|
@ -66,7 +67,7 @@ def create_options_frame(container):
|
|||
|
||||
# Use ITA checkbox
|
||||
use_ita = tk.StringVar()
|
||||
use_ita.set("1")
|
||||
use_ita.set("0")
|
||||
use_ita_check = ttk.Checkbutton(
|
||||
frame,
|
||||
variable=use_ita,
|
||||
|
@ -82,10 +83,31 @@ def create_options_frame(container):
|
|||
text='Buffs',
|
||||
command=lambda: print(buffs.get()))
|
||||
|
||||
# Override Workshop checkbox
|
||||
override_workshop = tk.StringVar()
|
||||
override_workshop.set("0")
|
||||
override_workshop_check = ttk.Checkbutton(
|
||||
frame,
|
||||
variable=override_workshop,
|
||||
text='Override Workshop',
|
||||
command=lambda: print(override_workshop.get()))
|
||||
|
||||
Hovertip(use_ita_check,
|
||||
'Check this box if you use Into The Abyss mod.'
|
||||
'\nITA and Sunken Tapes override the same style file.')
|
||||
|
||||
Hovertip(buffs_check,
|
||||
'Check this box to enable some songs causing strange effects'
|
||||
'\nThis is the intended default behaviour.')
|
||||
|
||||
Hovertip(override_workshop_check,
|
||||
'Keep this unchecked to prevent Steam Workshop'
|
||||
'\noverriding your custom installation.')
|
||||
|
||||
for widget in frame.winfo_children():
|
||||
widget.pack(side="top", padx=3, pady=3, fill="x")
|
||||
|
||||
return frame, use_ita, buffs
|
||||
return frame, use_ita, buffs, override_workshop
|
||||
|
||||
|
||||
def create_resolution_frame(container):
|
||||
|
@ -119,16 +141,26 @@ def create_deploy_frame(container, config):
|
|||
config_values = {"installdir": config["installdir"].get(),
|
||||
"use_ita": config["use_ita"].get() == "1",
|
||||
"buffs": config["buffs"].get() == "1",
|
||||
"override_workshop": config["override_workshop"].get() == "1",
|
||||
"resolution_x": int(config["resolution_x"].get()),
|
||||
"resolution_y": int(config["resolution_x"].get())}
|
||||
"resolution_y": int(config["resolution_y"].get())}
|
||||
|
||||
logging.info(f"deploying with config: {config_values}")
|
||||
deploy(config_values)
|
||||
|
||||
if config_values["override_workshop"]:
|
||||
mod_name = "Sunken Tapes"
|
||||
note = ""
|
||||
else:
|
||||
mod_name = "Sunken Tapez"
|
||||
note = '\n\nThis is a custom version and is named with Z (Sunken Tapez) to differentiate it from ' \
|
||||
+ 'the Steam Workshop version that would overwrite it otherwise.'
|
||||
|
||||
showinfo(title='Success!',
|
||||
message=f'Sunken Tapes was successfully installed to:'
|
||||
message=f'{mod_name} was successfully installed to:'
|
||||
f'\n{config["installdir"].get()}'
|
||||
f'\n\nThis installer will now close.')
|
||||
f'\n\nThis installer will now close.'
|
||||
f'{note}')
|
||||
|
||||
container.destroy()
|
||||
|
||||
|
@ -215,12 +247,13 @@ def create_main_window():
|
|||
middle_frame = ttk.Frame(root)
|
||||
|
||||
install_frame, install_dir = create_install_frame(root)
|
||||
options_frame, use_ita, buffs = create_options_frame(middle_frame)
|
||||
options_frame, use_ita, buffs, override_workshop = create_options_frame(middle_frame)
|
||||
resolution_frame, width, height = create_resolution_frame(middle_frame)
|
||||
|
||||
config = {"installdir": install_dir,
|
||||
"use_ita": use_ita,
|
||||
"buffs": buffs,
|
||||
"override_workshop": override_workshop,
|
||||
"resolution_x": width,
|
||||
"resolution_y": height}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ def main():
|
|||
shutil.copy("./deploy.py", "./barotrauma-sunken-tapes/deploy.py")
|
||||
shutil.copy("./main.py", "./barotrauma-sunken-tapes/main.py")
|
||||
shutil.copy("./publish.py", "./barotrauma-sunken-tapes/publish.py")
|
||||
shutil.copy("./tooltip.py", "./barotrauma-sunken-tapes/tooltip.py")
|
||||
shutil.copy("./fetch_song.py", "./barotrauma-sunken-tapes/fetch_song.py")
|
||||
shutil.copy("./install.bat", "./barotrauma-sunken-tapes/install.bat")
|
||||
shutil.copy("./README.md", "./barotrauma-sunken-tapes/README.md")
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<contentpackage name="Sunken Tapes" path="Mods/Sunken Tapes/filelist.xml" gameversion="0.9.1.0" corepackage="false">
|
||||
<Item file="Mods/Sunken Tapes/sunken_tapes.xml" />
|
||||
<UIStyle file="Mods/Sunken Tapes/sunken_tapes_style.xml" />
|
||||
</contentpackage>
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<contentpackage name="Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}" path="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/filelist.xml" gameversion="0.14.9.1" {% if config['override_workshop'] %}steamworkshopid="2616577901" {% endif %}corepackage="false">
|
||||
<Item file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/sunken_tapes.xml" />
|
||||
<UIStyle file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/sunken_tapes_style.xml" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/icons.png" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/covers.png" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/boombox.png" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/sunken_tapes_titletext_ita.png" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/sunken_tapes_titletext_vanilla.png" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/08080_canyons_joyride.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/08080_minor_threat.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/bedouin.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/biosphere.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/ddt.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/dm.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/dmx.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/dmxcut.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/hardbass.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/ira.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/immigrantsong.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/java.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/nazare.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/rainbowstalin.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/redoctober.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/rum.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/rusija.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/schritte.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/schritte_08080.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/shanty1.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/shanty2.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/shoegazeprincessa.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/sigma.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/swgd.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/tha.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/urfaust.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/wacky_tape.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/sound_effects/boombox_insert_cassette.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/sound_effects/boombox_play_cassette.ogg" />
|
||||
<None file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/sound_effects/cassette_drop.ogg" />
|
||||
</contentpackage>
|
|
@ -180,7 +180,7 @@
|
|||
</TitleText>
|
||||
{% for tape in tapes %}
|
||||
<sunken_tapes_cover_{{ tape.identifier }} color="255,255,255,255" textcolor="0,0,0,255">
|
||||
<Sprite name="sunken_tapes_cover_{{ tape.identifier }}" texture="Mods/Sunken Tapes/covers.png" size="0.0, 0.0" sourcerect="0,{{ loop.index0*328 }},512,328" origin="0.5,0.5" compress="false" tile="false"/>
|
||||
<Sprite name="sunken_tapes_cover_{{ tape.identifier }}" texture="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/covers.png" size="0.0, 0.0" sourcerect="0,{{ loop.index0*328 }},512,328" origin="0.5,0.5" compress="false" tile="false"/>
|
||||
</sunken_tapes_cover_{{ tape.identifier }}>{% endfor %}
|
||||
|
||||
<ita_document1 color="255,255,255,255" textcolor="0,0,0,255">
|
||||
|
|
|
@ -180,7 +180,7 @@
|
|||
</TitleText>
|
||||
{% for tape in tapes %}
|
||||
<sunken_tapes_cover_{{ tape.identifier }} color="255,255,255,255" textcolor="0,0,0,255">
|
||||
<Sprite name="sunken_tapes_cover_{{ tape.identifier }}" texture="Mods/Sunken Tapes/covers.png" size="0.0, 0.0" sourcerect="0,{{ loop.index0*328 }},512,328" origin="0.5,0.5" compress="false" tile="false"/>
|
||||
<Sprite name="sunken_tapes_cover_{{ tape.identifier }}" texture="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/covers.png" size="0.0, 0.0" sourcerect="0,{{ loop.index0*328 }},512,328" origin="0.5,0.5" compress="false" tile="false"/>
|
||||
</sunken_tapes_cover_{{ tape.identifier }}>{% endfor %}
|
||||
|
||||
<InnerGlow color="255,255,255,204" hovercolor="255,255,255,204" selectedcolor="255,255,255,204">
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
<Price locationtype="mine" multiplier="1.5" sold="false"/>
|
||||
</Price>
|
||||
<Upgrade gameversion="0.9.2.0" scale="0.5" />
|
||||
<InventoryIcon texture="Mods/Sunken Tapes/icons.png" sourcerect="0,0,64,64" origin="0.5,0.5" />
|
||||
<Sprite texture="Mods/Sunken Tapes/boombox.png" sourcerect="0,0,100,60" depth="0.55" origin="0.5,0.5" />
|
||||
<InventoryIcon texture="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/icons.png" sourcerect="0,0,64,64" origin="0.5,0.5" />
|
||||
<Sprite texture="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/boombox.png" sourcerect="0,0,100,60" depth="0.55" origin="0.5,0.5" />
|
||||
<Body width="100" height="60" />
|
||||
<LightComponent LightColor="0.78,0.04,0.235,0.59" range="10" powerconsumption="0" blinkfrequency="1" IsOn="false" canbeselected="false">
|
||||
</LightComponent>
|
||||
|
@ -22,7 +22,7 @@
|
|||
<TickBox text="Play">
|
||||
<StatusEffect type="OnUse" targettype="This" IsOn="true">
|
||||
<Conditional IsOn="false" />
|
||||
<sound file="Mods/Sunken Tapes/sound_effects/boombox_play_cassette.ogg" type="OnUse" range="500" volume="1.0" />
|
||||
<sound file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/sound_effects/boombox_play_cassette.ogg" type="OnUse" range="500" volume="1.0" />
|
||||
</StatusEffect>
|
||||
<!--StatusEffect type="OnUse" targettype="Contained" comparison="And">
|
||||
<Conditional condition="lte 50.0" />
|
||||
|
@ -49,7 +49,7 @@
|
|||
|
||||
<StatusEffect type="OnSecondaryUse" targettype="This" IsOn="false" >
|
||||
<Conditional IsOn="true" />
|
||||
<sound file="Mods/Sunken Tapes/sound_effects/boombox_play_cassette.ogg" type="OnUse" range="500" volume="1.0" />
|
||||
<sound file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/sound_effects/boombox_play_cassette.ogg" type="OnUse" range="500" volume="1.0" />
|
||||
<Use />
|
||||
</StatusEffect>
|
||||
</TickBox>
|
||||
|
@ -109,12 +109,12 @@
|
|||
<Price baseprice="{{ tape.price }}" soldeverywhere="false">{% for location in ["outpost", "city", "research", "military", "mine"] %}
|
||||
<Price locationtype="{{ location }}" multiplier="{{ tape.multipliers[loop.index0] }}" sold="{{ tape.sold[loop.index0] }}" minavailable="1" />{% endfor %}
|
||||
</Price>
|
||||
<InventoryIcon texture="Mods/Sunken Tapes/icons.png" sourcerect="0,{{ loop.index0*41 + 64 }},64,41" origin="0.5,0.5" />
|
||||
<Sprite texture="Mods/Sunken Tapes/icons.png" sourcerect="0,{{ loop.index0*41 + 64 }},64,41" depth="0.6" origin="0.5,0.5" />
|
||||
<InventoryIcon texture="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/icons.png" sourcerect="0,{{ loop.index0*41 + 64 }},64,41" origin="0.5,0.5" />
|
||||
<Sprite texture="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/icons.png" sourcerect="0,{{ loop.index0*41 + 64 }},64,41" depth="0.6" origin="0.5,0.5" />
|
||||
<Body width="48" height="48" />
|
||||
<Throwable slots="Any,RightHand,LeftHand" holdpos="0,0" handle1="0,0" throwforce="4.0" aimpos="35,-10" msg="ItemMsgPickUpSelect">
|
||||
<StatusEffect type="OnImpact" target="This" Condition="-5.0" disabledeltatime="true">
|
||||
<sound file="Mods/Sunken Tapes/sound_effects/cassette_drop.ogg" range="500" volume="1.0" />
|
||||
<sound file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/sound_effects/cassette_drop.ogg" range="500" volume="1.0" />
|
||||
</StatusEffect>
|
||||
</Throwable>
|
||||
<CustomInterface canbeselected="false" drawhudwhenequipped="true" allowuioverlap="true">
|
||||
|
@ -151,7 +151,7 @@
|
|||
<InventoryIcon texture="Content/Items/Electricity/signalcomp.png" sourcerect="0,160,4,4" origin="0.5,0.5" />
|
||||
<ItemComponent>
|
||||
<StatusEffect type="Always" target="This">
|
||||
<sound file="Mods/Sunken Tapes/music/{{ tape.identifier }}.ogg" type="OnUse" range="1000" loop="true" volume="1.0" />
|
||||
<sound file="Mods/Sunken Tape{% if config['override_workshop'] %}s{% else %}z{% endif %}/music/{{ tape.identifier }}.ogg" type="OnUse" range="1000" loop="true" volume="1.0" />
|
||||
</StatusEffect>{% if tape.buffs %}
|
||||
<StatusEffect type="Always" target="NearbyCharacters" range="1000">{% for buff in tape.buffs %}
|
||||
{% if buff == "psychosis" %}<Affliction identifier="{{ buff }}" strength= "{{ '%0.4f' % (delta + 0.1) }}" />{% else %}<Affliction identifier="{{ buff }}" strength= "{{ '%0.4f' % (delta*4 + 1) }}" />{% endif %}{% endfor %}
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
"""Tools for displaying tool-tips.
|
||||
|
||||
This includes:
|
||||
* an abstract base-class for different kinds of tooltips
|
||||
* a simple text-only Tooltip class
|
||||
"""
|
||||
from tkinter import *
|
||||
|
||||
|
||||
class TooltipBase:
|
||||
"""abstract base class for tooltips"""
|
||||
|
||||
def __init__(self, anchor_widget):
|
||||
"""Create a tooltip.
|
||||
|
||||
anchor_widget: the widget next to which the tooltip will be shown
|
||||
|
||||
Note that a widget will only be shown when showtip() is called.
|
||||
"""
|
||||
self.anchor_widget = anchor_widget
|
||||
self.tipwindow = None
|
||||
|
||||
def __del__(self):
|
||||
self.hidetip()
|
||||
|
||||
def showtip(self):
|
||||
"""display the tooltip"""
|
||||
if self.tipwindow:
|
||||
return
|
||||
self.tipwindow = tw = Toplevel(self.anchor_widget)
|
||||
# show no border on the top level window
|
||||
tw.wm_overrideredirect(1)
|
||||
try:
|
||||
# This command is only needed and available on Tk >= 8.4.0 for OSX.
|
||||
# Without it, call tips intrude on the typing process by grabbing
|
||||
# the focus.
|
||||
tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w,
|
||||
"help", "noActivates")
|
||||
except TclError:
|
||||
pass
|
||||
|
||||
self.position_window()
|
||||
self.showcontents()
|
||||
self.tipwindow.update_idletasks() # Needed on MacOS -- see #34275.
|
||||
self.tipwindow.lift() # work around bug in Tk 8.5.18+ (issue #24570)
|
||||
|
||||
def position_window(self):
|
||||
"""(re)-set the tooltip's screen position"""
|
||||
x, y = self.get_position()
|
||||
root_x = self.anchor_widget.winfo_rootx() + x
|
||||
root_y = self.anchor_widget.winfo_rooty() + y
|
||||
self.tipwindow.wm_geometry("+%d+%d" % (root_x, root_y))
|
||||
|
||||
def get_position(self):
|
||||
"""choose a screen position for the tooltip"""
|
||||
# The tip window must be completely outside the anchor widget;
|
||||
# otherwise when the mouse enters the tip window we get
|
||||
# a leave event and it disappears, and then we get an enter
|
||||
# event and it reappears, and so on forever :-(
|
||||
#
|
||||
# Note: This is a simplistic implementation; sub-classes will likely
|
||||
# want to override this.
|
||||
return 20, self.anchor_widget.winfo_height() + 1
|
||||
|
||||
def showcontents(self):
|
||||
"""content display hook for sub-classes"""
|
||||
# See ToolTip for an example
|
||||
raise NotImplementedError
|
||||
|
||||
def hidetip(self):
|
||||
"""hide the tooltip"""
|
||||
# Note: This is called by __del__, so careful when overriding/extending
|
||||
tw = self.tipwindow
|
||||
self.tipwindow = None
|
||||
if tw:
|
||||
try:
|
||||
tw.destroy()
|
||||
except TclError: # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
class OnHoverTooltipBase(TooltipBase):
|
||||
"""abstract base class for tooltips, with delayed on-hover display"""
|
||||
|
||||
def __init__(self, anchor_widget, hover_delay=1000):
|
||||
"""Create a tooltip with a mouse hover delay.
|
||||
|
||||
anchor_widget: the widget next to which the tooltip will be shown
|
||||
hover_delay: time to delay before showing the tooltip, in milliseconds
|
||||
|
||||
Note that a widget will only be shown when showtip() is called,
|
||||
e.g. after hovering over the anchor widget with the mouse for enough
|
||||
time.
|
||||
"""
|
||||
super(OnHoverTooltipBase, self).__init__(anchor_widget)
|
||||
self.hover_delay = hover_delay
|
||||
|
||||
self._after_id = None
|
||||
self._id1 = self.anchor_widget.bind("<Enter>", self._show_event)
|
||||
self._id2 = self.anchor_widget.bind("<Leave>", self._hide_event)
|
||||
self._id3 = self.anchor_widget.bind("<Button>", self._hide_event)
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.anchor_widget.unbind("<Enter>", self._id1)
|
||||
self.anchor_widget.unbind("<Leave>", self._id2) # pragma: no cover
|
||||
self.anchor_widget.unbind("<Button>", self._id3) # pragma: no cover
|
||||
except TclError:
|
||||
pass
|
||||
super(OnHoverTooltipBase, self).__del__()
|
||||
|
||||
def _show_event(self, event=None):
|
||||
"""event handler to display the tooltip"""
|
||||
if self.hover_delay:
|
||||
self.schedule()
|
||||
else:
|
||||
self.showtip()
|
||||
|
||||
def _hide_event(self, event=None):
|
||||
"""event handler to hide the tooltip"""
|
||||
self.hidetip()
|
||||
|
||||
def schedule(self):
|
||||
"""schedule the future display of the tooltip"""
|
||||
self.unschedule()
|
||||
self._after_id = self.anchor_widget.after(self.hover_delay,
|
||||
self.showtip)
|
||||
|
||||
def unschedule(self):
|
||||
"""cancel the future display of the tooltip"""
|
||||
after_id = self._after_id
|
||||
self._after_id = None
|
||||
if after_id:
|
||||
self.anchor_widget.after_cancel(after_id)
|
||||
|
||||
def hidetip(self):
|
||||
"""hide the tooltip"""
|
||||
try:
|
||||
self.unschedule()
|
||||
except TclError: # pragma: no cover
|
||||
pass
|
||||
super(OnHoverTooltipBase, self).hidetip()
|
||||
|
||||
|
||||
class Hovertip(OnHoverTooltipBase):
|
||||
"A tooltip that pops up when a mouse hovers over an anchor widget."
|
||||
def __init__(self, anchor_widget, text, hover_delay=1000):
|
||||
"""Create a text tooltip with a mouse hover delay.
|
||||
|
||||
anchor_widget: the widget next to which the tooltip will be shown
|
||||
hover_delay: time to delay before showing the tooltip, in milliseconds
|
||||
|
||||
Note that a widget will only be shown when showtip() is called,
|
||||
e.g. after hovering over the anchor widget with the mouse for enough
|
||||
time.
|
||||
"""
|
||||
super(Hovertip, self).__init__(anchor_widget, hover_delay=hover_delay)
|
||||
self.text = text
|
||||
|
||||
def showcontents(self):
|
||||
label = Label(self.tipwindow, text=self.text, justify=LEFT,
|
||||
background="#ffffe0", relief=SOLID, borderwidth=1)
|
||||
label.pack()
|
||||
|
||||
|
||||
def _tooltip(parent): # htest #
|
||||
top = Toplevel(parent)
|
||||
top.title("Test tooltip")
|
||||
x, y = map(int, parent.geometry().split('+')[1:])
|
||||
top.geometry("+%d+%d" % (x, y + 150))
|
||||
label = Label(top, text="Place your mouse over buttons")
|
||||
label.pack()
|
||||
button1 = Button(top, text="Button 1 -- 1/2 second hover delay")
|
||||
button1.pack()
|
||||
Hovertip(button1, "This is tooltip text for button1.", hover_delay=500)
|
||||
button2 = Button(top, text="Button 2 -- no hover delay")
|
||||
button2.pack()
|
||||
Hovertip(button2, "This is tooltip\ntext for button2.", hover_delay=None)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from unittest import main
|
||||
main('idlelib.idle_test.test_tooltip', verbosity=2, exit=False)
|
||||
|
||||
from idlelib.idle_test.htest import run
|
||||
run(_tooltip)
|
Loading…
Reference in New Issue