# ******************************************************
# * Copyright © 2016-2023 - Jordan Irwin (AntumDeluge) *
# ******************************************************
# * This software is licensed under the MIT license. *
# * See: LICENSE.txt for details. *
# ******************************************************
## Custom buttons for application.
#
# @module ui.buttons
import os
import wx
from dbr.containers import Contains
from fields.cmdfield import CommandField
from globals import tooltips
from globals.paths import getBitmapsDir
from globals.strings import IsString
from libdbr import strings
from libdbr.logger import Logger
from libdebreate.ident import btnid
from ui.layout import BoxSizer
from ui.style import layout as lyt
__logger = Logger(__name__)
## Standard button that inherits CommandField
class Button(wx.Button, CommandField):
def __init__(self, parent, btnId=wx.ID_ANY, label=wx.EmptyString, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=0, name=wx.ButtonNameStr, commands=None, requireAll=False):
wx.Button.__init__(self, parent, btnId, label, pos, size, style, name=name)
CommandField.__init__(self, commands, requireAll)
## The same as wx.BitmapButton but defaults to style=wx.NO_BORDER
class CustomButton(wx.BitmapButton, CommandField):
def __init__(self, parent, bitmap, btnId=wx.ID_ANY, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.NO_BORDER, validator=wx.DefaultValidator,
name=wx.ButtonNameStr, commands=None, requireAll=False):
if not isinstance(bitmap, wx.Bitmap):
bitmap = wx.Bitmap(bitmap)
wx.BitmapButton.__init__(self, parent, btnId, bitmap, pos, size, style|wx.NO_BORDER,
validator, name)
CommandField.__init__(self, commands, requireAll)
## @todo Doxygen
class LayoutButton(BoxSizer):
def __init__(self, button, label, parent=None, btnId=wx.ID_ANY, size=32,
tooltip=None, name=None, showLabel=True):
BoxSizer.__init__(self, wx.VERTICAL)
if IsString(button):
self.Button = CreateButton(parent, btnId, label, button, size, tooltip, name)
else:
self.Button = button
self.Add(self.Button, 1, wx.ALIGN_CENTER)
if isinstance(self.Button, CustomButton):
if not label:
label = self.Button.Name
self.Label = wx.StaticText(self.Button.GetParent(), label=label)
self.Add(self.Label, 0, wx.ALIGN_CENTER)
self.Show(self.Label, showLabel)
## @todo Doxygen
def Bind(self, eventType, eventHandler):
self.Button.Bind(eventType, eventHandler)
## @todo Doxygen
def GetLabel(self):
if isinstance(self.Button, CustomButton):
return self.Label.GetLabel()
return self.Button.GetLabel()
## @todo Doxygen
def LabelIsShown(self):
if not isinstance(self.Button, CustomButton):
# Instance is a wx.Button
return True
return self.IsShown(self.Label)
## @todo Doxygen
def ShowLabel(self, show=True):
self.Show(self.Label, show)
## BoxSizer class to distinguish between other sizers
class ButtonSizer(BoxSizer):
def __init__(self, orient):
BoxSizer.__init__(self, orient)
## @todo Doxygen
def Add(self, button, proportion=0, flag=0, border=0, label=None, userData=None):
# FIXME: Create method to call from Add & Insert methods & reduce code
if isinstance(button, LayoutButton):
add_object = button
else:
if isinstance(button, CustomButton):
if label == None:
label = button.GetToolTipText()
add_object = BoxSizer(wx.VERTICAL)
add_object.Add(button, 0, wx.ALIGN_CENTER)
add_object.Add(wx.StaticText(button.Parent, label=label), 0, wx.ALIGN_CENTER_HORIZONTAL)
else:
add_object = button
return BoxSizer.Add(self, add_object, proportion, flag, border, userData)
## @todo Doxygen
def HideLabels(self):
self.ShowLabels(False)
## @todo Doxygen
def Insert(self, index, button, proportion=0, flag=0, border=0, label=None, userData=None):
if isinstance(button, CustomButton):
if label == None:
label = button.GetToolTipText()
add_object = BoxSizer(wx.VERTICAL)
add_object.Add(button, 0, wx.ALIGN_CENTER)
add_object.Add(wx.StaticText(button.Parent, label=label), 0, wx.ALIGN_CENTER_HORIZONTAL)
else:
add_object = button
return BoxSizer.Insert(self, index, add_object, proportion, flag, border, userData)
## Changes label for all buttons with specified ID
#
# @param btnId
# \b \e Integer ID of buttons to change labels
# @param newLabel
# New \b \e string label for button
# @return
# \b \e True if matching button found
def SetLabel(self, btnId, newLabel):
label_set = False
for SI in self.GetChildren():
btn_objects = SI.GetSizer()
if btn_objects:
btn_objects = btn_objects.GetChildren()
button = btn_objects[0].GetWindow()
if button and button.GetId() == btnId:
if isinstance(button, CustomButton):
static_text = btn_objects[1].GetWindow()
static_text.SetLabel(newLabel)
tooltips.register(button, newLabel)
label_set = True
else:
button.SetLabel(newLabel)
tooltips.register(button, newLabel)
label_set = True
return label_set
## Show or hide text labels
def ShowLabels(self, show=True):
buttons = self.GetChildren()
if buttons:
for SIZER in self.GetChildren():
SIZER = SIZER.GetSizer()
if SIZER:
label = SIZER.GetChildren()[1]
label.Show(show)
window = self.GetContainingWindow()
if window:
window.Layout()
## @todo Doxygen
#
# @param button_ids
# \b \e List of IDs of buttons to be added
# @param parent_sizer
# The \b \e wx.Sizer instance that the buttons sizer should be added to
# @param show_labels:
# If True, button labels will be shown on custom button instances
# @param reverse
# Reverse order of buttons added
# @return
# \b \e ui.button.ButtonSizer instance containing the buttons
def AddCustomButtons(window, button_ids, parent_sizer=None, show_labels=True, reverse=True,
flag=wx.ALIGN_RIGHT|lyt.PAD_RB, border=5):
lyt_buttons = ButtonSizer(wx.HORIZONTAL)
if reverse:
button_ids = reversed(button_ids)
for ID in button_ids:
new_button = CreateButton(window, ID)
lyt_buttons.Add(new_button, 0, wx.ALIGN_CENTER)
lyt_buttons.ShowLabels(show_labels)
if isinstance(parent_sizer, wx.BoxSizer):
parent_sizer.Add(lyt_buttons, 0, flag, border)
return None
# parent_sizer is boolean
elif parent_sizer:
window.GetSizer().Add(lyt_buttons, 0, flag, border)
return None
return lyt_buttons
## Find sizer instance that contains buttons
#
# Helper function for ReplaceStandardButtons
#
# @param sizer
# \b \e wx.Sizer or \b \e wx.Window instance to search for child button sizer
def GetButtonSizer(sizer):
if isinstance(sizer, wx.Window):
sizer = sizer.GetSizer()
if isinstance(sizer, (ButtonSizer, wx.StdDialogButtonSizer)):
return sizer
for S in sizer.GetChildren():
S = S.GetSizer()
if S:
S = GetButtonSizer(S)
if S:
return S
## @todo Doxygen
def _get_containing_sizer(parent, sizer):
if isinstance(parent, wx.Window):
parent = parent.GetSizer()
if not parent or parent == sizer:
return None
if Contains(parent, sizer):
return parent
for S in parent.GetChildren():
S = S.GetSizer()
if S:
S = _get_containing_sizer(S, sizer)
if S:
return S
## Replaces standard dialog buttons with custom ones
#
# @param dialog
# Dialog instance containing the buttons
def ReplaceStandardButtons(dialog):
if isinstance(dialog, (wx.FileDialog, wx.MessageDialog)):
__logger.warn("FIXME: Cannot replace buttons on object type: {}".format(type(dialog)))
return
lyt_buttons = GetButtonSizer(dialog.GetSizer())
removed_button_ids = []
if lyt_buttons:
for FIELD in lyt_buttons.GetChildren():
FIELD = FIELD.GetWindow()
if isinstance(FIELD, wx.Button):
lyt_buttons.Detach(FIELD)
removed_button_ids.append(FIELD.GetId())
FIELD.Destroy()
# Replace sizer with ButtonSizer
if not isinstance(lyt_buttons, ButtonSizer):
container_sizer = _get_containing_sizer(dialog, lyt_buttons)
index = 0
for S in container_sizer.GetChildren():
S = S.GetSizer()
if S and S == lyt_buttons:
break
index += 1
container_sizer.Remove(lyt_buttons)
lyt_buttons = ButtonSizer(wx.HORIZONTAL)
container_sizer.Insert(index, lyt_buttons, 0, wx.ALIGN_RIGHT)
# Don't add padding to first item
FLAGS = 0
for ID in removed_button_ids:
lyt_buttons.Add(CreateButton(dialog, ID), 0, FLAGS, 5)
if not FLAGS:
FLAGS = wx.LEFT
dialog.Fit()
dialog.Layout()
## Creates a new custom button
#
# @param parent
# \b \e wx.Window parent instance
# @param label
# Text to be shown on button or tooltip
# @param btnId
# <b><i>Integer identifier
# @param image
# Base name of image file to use for custom buttons (uses standard image if set to 'None')
# @param size
# Image size to use for button
# @param tooltip
# Text to display when cursor hovers over button
# @param name
# Name attribute
# @return
# ui.button.CustomButton instance or wx.Button instance if image file not found
def CreateButton(parent, btnId=wx.ID_ANY, label=wx.EmptyString, image=None, size=32, tooltip=None, name=None,
commands=None, requireAll=False):
if not image:
image = btnid.GetImage(btnId)
# Use titleized version of the image name for the label
if not label and image:
label = image.title()
if not name:
name = label
button = None
if image:
image = os.path.join(getBitmapsDir(), "button", strings.toString(size), "{}.png".format(image))
if not os.path.isfile(image):
__logger.warn(
"CreateButton: Attempted to set not-existent image for button (ID {}):".format(btnId),
details=image)
else:
button = CustomButton(parent, image, btnId, name=name, commands=commands,
requireAll=requireAll)
if not tooltip:
tooltip = label
tooltips.register(button, tooltip)
# Use a standard button
if not button:
button = Button(parent, btnId, label, name=name, commands=commands,
requireAll=requireAll)
return button