#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2022 , Nils Hilbricht , Germany ( https : / / www . hilbricht . net )
This file is part of the Laborejo Software Suite ( https : / / www . laborejo . org ) ,
Laborejo2 is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : / / www . gnu . org / licenses / > .
"""
import logging ; logger = logging . getLogger ( __name__ ) ; logger . info ( " import " )
from typing import Iterable , Callable , Tuple
from PyQt5 import QtCore , QtGui , QtWidgets
import engine . api as api
from qtgui . constantsAndConfigs import constantsAndConfigs #Client constantsAndConfigs!
"""
There are two types of submenus in this file . The majority is created in menu . py during start up .
Like Clef , KeySig etc . These don ' t need to ask for any dynamic values.
The other is like SecondaryTempoChangeMenu . In menu . py this is bound with a lambda construct so a
new instance gets created each time the action is called by the user . Thats why this function has
self . __call__ in its init .
"""
class Submenu ( QtWidgets . QDialog ) :
def __init__ ( self , mainWindow , labelString , hasOkCancelButtons = False ) :
super ( ) . __init__ ( mainWindow ) #if you don't set the parent to the main window the whole screen will be the root and the dialog pops up in the middle of it.
#self.setModal(True) #we don't need this when called with self.exec() instead of self.show()
self . layout = QtWidgets . QFormLayout ( )
#self.layout = QtWidgets.QVBoxLayout()
self . setLayout ( self . layout )
label = QtWidgets . QLabel ( labelString ) #"Choose a clef" or so.
self . layout . addWidget ( label )
#self.setFocus(); #self.grabKeyboard(); #redundant for a proper modal dialog. Leave here for documentation reasons.
if hasOkCancelButtons == 1 : #or true
self . buttonBox = QtWidgets . QDialogButtonBox ( QtWidgets . QDialogButtonBox . Ok | QtWidgets . QDialogButtonBox . Cancel )
self . buttonBox . accepted . connect ( self . process )
self . buttonBox . rejected . connect ( self . reject )
elif hasOkCancelButtons == 2 : #only cancel. #TODO: unpythonic.
self . buttonBox = QtWidgets . QDialogButtonBox ( QtWidgets . QDialogButtonBox . Cancel )
self . buttonBox . rejected . connect ( self . reject )
else :
self . buttonBox = None
def keyPressEvent ( self , event ) :
""" Escape closes the dialog by default.
We want Enter as " accept value "
All other methods of mixing editing , window focus and signals
results in strange qt behaviour , triggering the api function twice or more .
Especially unitbox . editingFinished is too easy to trigger .
The key - event method turned out to be the most straightforward way . """
try :
getattr ( self , " process " )
k = event . key ( ) #49=1, 50=2 etc.
if k == 0x01000004 or k == 0x01000005 : #normal enter or keypad enter
event . ignore ( )
self . process ( )
else : #Pressed Esc
self . abortHandler ( )
super ( ) . keyPressEvent ( event )
except AttributeError :
super ( ) . keyPressEvent ( event )
def showEvent ( self , event ) :
#TODO: not optimal but better than nothing.
super ( ) . showEvent ( event )
#self.resize(self.layout.geometry().width(), self.layout.geometry().height())
self . resize ( self . childrenRect ( ) . height ( ) , self . childrenRect ( ) . width ( ) )
self . updateGeometry ( )
def abortHandler ( self ) :
pass
def process ( self ) :
""" Careful! Calling this eats python errors without notice. Make sure your objects exists
and your syntax is correct """
raise NotImplementedError ( )
self . done ( True )
def __call__ ( self ) :
""" This instance can be called like a function """
if self . buttonBox :
self . layout . addWidget ( self . buttonBox )
self . setFixedSize ( self . layout . geometry ( ) . size ( ) )
self . exec ( ) #blocks until the dialog gets closed
"""
Most submenus have the line " lambda, r, value=value " . . .
the r is the return value we get automatically from the Qt buttons which need to be handled .
"""
class ChooseOne ( Submenu ) :
""" A generic submenu that presents a list of options to the users.
Only supports up to ten entries , for number shortcuts """
def __init__ ( self , mainWindow , title : str , lst : Iterable [ Tuple [ str , Callable ] ] ) :
if len ( lst ) > 9 :
raise ValueError ( f " ChooseOne submenu supports up to nine entries. You have { len ( lst ) } " )
super ( ) . __init__ ( mainWindow , title )
for number , ( prettyname , function ) in enumerate ( lst ) :
button = QtWidgets . QPushButton ( f " [ { number + 1 } ] { prettyname } " )
button . setShortcut ( QtGui . QKeySequence ( str ( number + 1 ) ) )
button . setStyleSheet ( " Text-align:left; padding: 5px; " ) ;
self . layout . addWidget ( button )
button . clicked . connect ( function )
button . clicked . connect ( self . done )