Compare commits

...

4 Commits

9 changed files with 291 additions and 28 deletions

View File

@ -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
View File

@ -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}

View File

@ -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")

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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">

View File

@ -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 %}

186
tooltip.py 100644
View File

@ -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)