Add Tooltips for sprites
This commit is contained in:
parent
5dbc21ce3a
commit
911737b84f
17
Gui.py
17
Gui.py
|
@ -1,15 +1,17 @@
|
||||||
from Main import main, __version__ as ESVersion
|
|
||||||
from Utils import is_bundled, local_path, output_path, open_file
|
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from Rom import Sprite
|
|
||||||
from glob import glob
|
from glob import glob
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
from tkinter import Checkbutton, OptionMenu, Toplevel, LabelFrame, PhotoImage, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, Y, Entry, Spinbox, Button, filedialog, messagebox
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
from tkinter import Checkbutton, OptionMenu, Toplevel, LabelFrame, PhotoImage, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, Y, Entry, Spinbox, Button, filedialog, messagebox
|
|
||||||
|
from GuiUtils import ToolTips
|
||||||
|
from Main import main, __version__ as ESVersion
|
||||||
|
from Rom import Sprite
|
||||||
|
from Utils import is_bundled, local_path, output_path, open_file
|
||||||
|
|
||||||
|
|
||||||
def guiMain(args=None):
|
def guiMain(args=None):
|
||||||
|
@ -318,9 +320,11 @@ class SpriteSelector(object):
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
for file in glob(output_path(path)):
|
for file in glob(output_path(path)):
|
||||||
image = get_image_for_sprite(file)
|
sprite = Sprite(file)
|
||||||
|
image = get_image_for_sprite(sprite)
|
||||||
if image is None: continue
|
if image is None: continue
|
||||||
button = Button(frame, image=image, command=lambda file=file: self.select_sprite(file))
|
button = Button(frame, image=image, command=lambda file=file: self.select_sprite(file))
|
||||||
|
ToolTips.register(button, sprite.name + ("\nBy: %s" % sprite.author_name if sprite.author_name is not None else ""))
|
||||||
button.image = image
|
button.image = image
|
||||||
button.grid(row=i // 16, column=i % 16)
|
button.grid(row=i // 16, column=i % 16)
|
||||||
i += 1
|
i += 1
|
||||||
|
@ -394,8 +398,7 @@ class SpriteSelector(object):
|
||||||
return local_path("data/sprites/unofficial")
|
return local_path("data/sprites/unofficial")
|
||||||
|
|
||||||
|
|
||||||
def get_image_for_sprite(filename):
|
def get_image_for_sprite(sprite):
|
||||||
sprite = Sprite(filename)
|
|
||||||
if not sprite.valid:
|
if not sprite.valid:
|
||||||
return None
|
return None
|
||||||
height = 24
|
height = 24
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
|
||||||
|
class ToolTips(object):
|
||||||
|
# This class derived from wckToolTips which is available under the following license:
|
||||||
|
|
||||||
|
# Copyright (c) 1998-2007 by Secret Labs AB
|
||||||
|
# Copyright (c) 1998-2007 by Fredrik Lundh
|
||||||
|
#
|
||||||
|
# By obtaining, using, and/or copying this software and/or its
|
||||||
|
# associated documentation, you agree that you have read, understood,
|
||||||
|
# and will comply with the following terms and conditions:
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and distribute this software and its
|
||||||
|
# associated documentation for any purpose and without fee is hereby
|
||||||
|
# granted, provided that the above copyright notice appears in all
|
||||||
|
# copies, and that both that copyright notice and this permission notice
|
||||||
|
# appear in supporting documentation, and that the name of Secret Labs
|
||||||
|
# AB or the author not be used in advertising or publicity pertaining to
|
||||||
|
# distribution of the software without specific, written prior
|
||||||
|
# permission.
|
||||||
|
#
|
||||||
|
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||||
|
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
# FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR
|
||||||
|
# ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||||
|
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
label = None
|
||||||
|
window = None
|
||||||
|
active = 0
|
||||||
|
tag = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getcontroller(cls, widget):
|
||||||
|
if cls.tag is None:
|
||||||
|
|
||||||
|
cls.tag = "ui_tooltip_%d" % id(cls)
|
||||||
|
widget.bind_class(cls.tag, "<Enter>", cls.enter)
|
||||||
|
widget.bind_class(cls.tag, "<Leave>", cls.leave)
|
||||||
|
widget.bind_class(cls.tag, "<Motion>", cls.motion)
|
||||||
|
|
||||||
|
# pick suitable colors for tooltips
|
||||||
|
try:
|
||||||
|
cls.bg = "systeminfobackground"
|
||||||
|
cls.fg = "systeminfotext"
|
||||||
|
widget.winfo_rgb(cls.fg) # make sure system colors exist
|
||||||
|
widget.winfo_rgb(cls.bg)
|
||||||
|
except:
|
||||||
|
cls.bg = "#ffffe0"
|
||||||
|
cls.fg = "black"
|
||||||
|
|
||||||
|
return cls.tag
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register(cls, widget, text):
|
||||||
|
widget.ui_tooltip_text = text
|
||||||
|
tags = list(widget.bindtags())
|
||||||
|
tags.append(cls.getcontroller(widget))
|
||||||
|
widget.bindtags(tuple(tags))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def unregister(cls, widget):
|
||||||
|
tags = list(widget.bindtags())
|
||||||
|
tags.remove(cls.getcontroller(widget))
|
||||||
|
widget.bindtags(tuple(tags))
|
||||||
|
|
||||||
|
# event handlers
|
||||||
|
@classmethod
|
||||||
|
def enter(cls, event):
|
||||||
|
widget = event.widget
|
||||||
|
if not cls.label:
|
||||||
|
# create and hide balloon help window
|
||||||
|
cls.popup = tk.Toplevel(bg=cls.fg, bd=1)
|
||||||
|
cls.popup.overrideredirect(1)
|
||||||
|
cls.popup.withdraw()
|
||||||
|
cls.label = tk.Label(
|
||||||
|
cls.popup, fg=cls.fg, bg=cls.bg, bd=0, padx=2, justify=tk.LEFT
|
||||||
|
)
|
||||||
|
cls.label.pack()
|
||||||
|
cls.active = 0
|
||||||
|
cls.xy = event.x_root + 16, event.y_root + 10
|
||||||
|
cls.event_xy = event.x, event.y
|
||||||
|
cls.after_id = widget.after(200, cls.display, widget)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def motion(cls, event):
|
||||||
|
widget = event.widget
|
||||||
|
cls.xy = event.x_root + 16, event.y_root + 10
|
||||||
|
cls.event_xy = event.x, event.y
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def display(cls, widget):
|
||||||
|
if not cls.active:
|
||||||
|
# display balloon help window
|
||||||
|
text = widget.ui_tooltip_text
|
||||||
|
if callable(text):
|
||||||
|
text = text(widget, cls.event_xy)
|
||||||
|
cls.label.config(text=text)
|
||||||
|
cls.popup.deiconify()
|
||||||
|
cls.popup.lift()
|
||||||
|
cls.popup.geometry("+%d+%d" % cls.xy)
|
||||||
|
cls.active = 1
|
||||||
|
cls.after_id = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def leave(cls, event):
|
||||||
|
widget = event.widget
|
||||||
|
if cls.active:
|
||||||
|
cls.popup.withdraw()
|
||||||
|
cls.active = 0
|
||||||
|
if cls.after_id:
|
||||||
|
widget.after_cancel(cls.after_id)
|
||||||
|
cls.after_id = None
|
37
Rom.py
37
Rom.py
|
@ -4,9 +4,11 @@ from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts,
|
||||||
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts
|
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts
|
||||||
from Utils import local_path
|
from Utils import local_path
|
||||||
import random
|
import random
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
|
@ -100,6 +102,8 @@ class Sprite(object):
|
||||||
def __init__(self, filename):
|
def __init__(self, filename):
|
||||||
with open(filename, 'rb') as file:
|
with open(filename, 'rb') as file:
|
||||||
filedata = bytearray(file.read())
|
filedata = bytearray(file.read())
|
||||||
|
self.name = os.path.basename(filename)
|
||||||
|
self.author_name = None
|
||||||
self.valid = True
|
self.valid = True
|
||||||
if len(filedata) == 0x7000:
|
if len(filedata) == 0x7000:
|
||||||
# sprite file with graphics and without palette data
|
# sprite file with graphics and without palette data
|
||||||
|
@ -123,11 +127,10 @@ class Sprite(object):
|
||||||
self.glove_palette = filedata[0xDEDF5:0xDEDF9]
|
self.glove_palette = filedata[0xDEDF5:0xDEDF9]
|
||||||
elif filedata.startswith(b'ZSPR'):
|
elif filedata.startswith(b'ZSPR'):
|
||||||
result = self.parse_zspr(filedata,1)
|
result = self.parse_zspr(filedata,1)
|
||||||
print(result)
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.valid = False
|
self.valid = False
|
||||||
return
|
return
|
||||||
(sprite, palette) = result
|
(sprite, palette, self.name, self.author_name) = result
|
||||||
if len(sprite) != 0x7000:
|
if len(sprite) != 0x7000:
|
||||||
self.valid = False
|
self.valid = False
|
||||||
return
|
return
|
||||||
|
@ -174,26 +177,48 @@ class Sprite(object):
|
||||||
return arr
|
return arr
|
||||||
|
|
||||||
def parse_zspr(self, filedata, expected_kind):
|
def parse_zspr(self, filedata, expected_kind):
|
||||||
|
logger = logging.getLogger('')
|
||||||
headerstr = "<4xBHHIHIHH6x"
|
headerstr = "<4xBHHIHIHH6x"
|
||||||
headersize = struct.calcsize(headerstr)
|
headersize = struct.calcsize(headerstr)
|
||||||
if len(filedata) < headersize:
|
if len(filedata) < headersize:
|
||||||
return None
|
return None
|
||||||
(version, csum, icsum, sprite_offset, sprite_size, palette_offset, palette_size, kind) = struct.unpack_from(headerstr,filedata)
|
(version, csum, icsum, sprite_offset, sprite_size, palette_offset, palette_size, kind) = struct.unpack_from(headerstr,filedata)
|
||||||
if version not in [1]:
|
if version not in [1]:
|
||||||
#ZSPR Version not supported
|
logger.error('Error parsing ZSPR file: Version %g not supported', version)
|
||||||
return None
|
return None
|
||||||
if kind != expected_kind:
|
if kind != expected_kind:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
stream = io.BytesIO(filedata)
|
||||||
|
stream.seek(headersize)
|
||||||
|
|
||||||
|
def read_utf16le(stream):
|
||||||
|
"Decodes a null-terminated UTF-16_LE string of unknown size from a stream"
|
||||||
|
raw = bytearray()
|
||||||
|
while True:
|
||||||
|
char = stream.read(2)
|
||||||
|
if char in [b'', b'\x00\x00']:
|
||||||
|
break
|
||||||
|
raw += char
|
||||||
|
return raw.decode('utf-16_le')
|
||||||
|
|
||||||
|
sprite_name = read_utf16le(stream)
|
||||||
|
author_name = read_utf16le(stream)
|
||||||
|
|
||||||
|
# Ignoring the Author Rom name for the time being.
|
||||||
|
|
||||||
real_csum = sum(filedata) % 0x10000
|
real_csum = sum(filedata) % 0x10000
|
||||||
if real_csum != csum or real_csum ^ 0xFFFF != icsum:
|
if real_csum != csum or real_csum ^ 0xFFFF != icsum:
|
||||||
#invalid checksum
|
logger.warning('ZSPR file has incorrect checksum. It may be corrupted.')
|
||||||
pass
|
pass
|
||||||
sprite = filedata[sprite_offset:sprite_offset + sprite_size]
|
sprite = filedata[sprite_offset:sprite_offset + sprite_size]
|
||||||
palette = filedata[palette_offset:palette_offset + palette_size]
|
palette = filedata[palette_offset:palette_offset + palette_size]
|
||||||
#FIXME: Check lengths of those byte arrays against the _size values
|
|
||||||
|
|
||||||
return (sprite, palette)
|
if len(sprite) != sprite_size or len(palette) != palette_size:
|
||||||
|
logger.error('Error parsing ZSPR file: Unexpected end of file')
|
||||||
|
return None
|
||||||
|
|
||||||
|
return (sprite, palette, sprite_name, author_name)
|
||||||
|
|
||||||
def decode_palette(self):
|
def decode_palette(self):
|
||||||
"Returns the palettes as an array of arrays of 15 colors"
|
"Returns the palettes as an array of arrays of 15 colors"
|
||||||
|
|
Loading…
Reference in New Issue