add label class, move empty buton to empty label
This commit is contained in:
160
controldeck.py
160
controldeck.py
@@ -52,6 +52,7 @@ from justpy import (
|
|||||||
QCard,
|
QCard,
|
||||||
QCardSection,
|
QCardSection,
|
||||||
QDialog,
|
QDialog,
|
||||||
|
QDiv,
|
||||||
QEditor,
|
QEditor,
|
||||||
QHeader,
|
QHeader,
|
||||||
QIcon,
|
QIcon,
|
||||||
@@ -126,12 +127,50 @@ def config_load(conf=''):
|
|||||||
#print(config.sections())
|
#print(config.sections())
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
# TODO: colors definable in config
|
||||||
|
class Label(QDiv):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
**kwargs:
|
||||||
|
- wtype: if 'empty' then: empty slot, for horizontal arrangement, text is
|
||||||
|
ignored, no bg color etc.
|
||||||
|
"""
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.style = "width: 90px;"
|
||||||
|
self.style += "min-height: 70px;"
|
||||||
|
if self.wtype != 'empty':
|
||||||
|
self.classes = f"q-pa-sm text-blue-grey-4 text-bold text-center bg-light-blue-10"
|
||||||
|
#self.style += "font-size: 14px;"
|
||||||
|
self.style += "line-height: 1em;"
|
||||||
|
self.style += "border-radius: 7px;"
|
||||||
|
self.text = self.text.upper()
|
||||||
|
#print()
|
||||||
|
#print(self)
|
||||||
|
|
||||||
|
# TODO: distinguish between non-command and comand inactive stage?
|
||||||
class Button(QBtn):
|
class Button(QBtn):
|
||||||
"""
|
"""
|
||||||
usage
|
Args:
|
||||||
Button(text, text_alt, btype, command, command_alt,
|
**kwargs:
|
||||||
|
- text: button text in normal state (unpressed)
|
||||||
|
- text_alt: button text in active state (pressed)
|
||||||
|
- wtype: 'button' (any string atm) for a button
|
||||||
|
- command: command to execute on click
|
||||||
|
- command_alt: command to execute on click in active state
|
||||||
|
- color_bg: background color
|
||||||
|
- color_fg: foreground color
|
||||||
|
- state_pattern: string defining the normal state (unpressed) [NEDDED?]
|
||||||
|
- state_pattern_alt: string defining the alternative state
|
||||||
|
(active, pressed)
|
||||||
|
- state_command: command to execute to compare with state_pattern*
|
||||||
|
- icon: icon in normal state (unpressed)
|
||||||
|
- icon_alt: icon in active state
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
Button(text, text_alt, wtype, command, command_alt,
|
||||||
color_bg=, color_fg=, state_pattern, state_pattern_alt,
|
color_bg=, color_fg=, state_pattern, state_pattern_alt,
|
||||||
state_command, state_command_alt,
|
state_command,
|
||||||
icon, icon_alt, image, image_alt,
|
icon, icon_alt, image, image_alt,
|
||||||
a)
|
a)
|
||||||
"""
|
"""
|
||||||
@@ -140,55 +179,72 @@ class Button(QBtn):
|
|||||||
# self.outline = True # is set via style, see below
|
# self.outline = True # is set via style, see below
|
||||||
# self.color = "blue-grey-8" # is overwritten via text_color
|
# self.color = "blue-grey-8" # is overwritten via text_color
|
||||||
self.text_color = COLOR_PRIME_TEXT
|
self.text_color = COLOR_PRIME_TEXT
|
||||||
self.size = 'md'
|
self.size = 'md' # button / font size
|
||||||
self.push = True
|
self.push = True
|
||||||
# self.padding = 'none' # not working, also not inside init
|
# self.padding = 'none' # not working, also not inside init
|
||||||
self.dense = True
|
self.dense = True
|
||||||
|
|
||||||
# default **kwargs
|
# default **kwargs
|
||||||
self.btype = None # button or empty
|
self.wtype = None # button or empty
|
||||||
self.image = '' # used for files like svg and png
|
self.image = '' # used for files like svg and png
|
||||||
self.command = '' # command to run on click
|
self.command = '' # command to run on click
|
||||||
self.state = '' # output of the state check command
|
self.state = '' # output of the state check command
|
||||||
self.state_command = '' # command to check the unclicked state
|
self.state_command = '' # command to check the unclicked state
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.style = "width: 90px;"
|
self.style = "width: 90px;"
|
||||||
self.style += "min-height: 70px;"
|
self.style += "min-height: 77px;" # image + 2 text lines
|
||||||
|
self.style += "border: 1px solid #455a64;" # #455a64 blue-grey-8
|
||||||
|
self.style += "line-height: 1em;"
|
||||||
|
|
||||||
if self.btype == 'empty':
|
if self.image and path.exists(self.image):
|
||||||
self.classes = 'invisible' # invisible; not shown but still stakes up space
|
# copy image files into the static folder
|
||||||
else:
|
basename = path.basename(self.image)
|
||||||
self.style += "border: 1px solid #455a64;" # #455a64 blue-grey-8
|
staticfile = path.join(STATIC_DIR, basename)
|
||||||
|
if not path.exists(staticfile):
|
||||||
|
shutil.copy2(self.image, staticfile)
|
||||||
|
if DEBUG:
|
||||||
|
print(f'[DEBUG] copy {self.image} to {staticfile}')
|
||||||
|
self.icon = f"img:/static/{basename}"
|
||||||
|
# <q-icon name="img:data:image/svg+xml;charset=utf8,<svg xmlns='http://www.w3.org/2000/svg' height='140' width='500'><ellipse cx='200' cy='80' rx='100' ry='50' style='fill:yellow;stroke:purple;stroke-width:2' /></svg>" />
|
||||||
|
# <q-btn icon="img:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" ... />
|
||||||
|
|
||||||
self.style += "width: 90px;"
|
if self.command != '':
|
||||||
self.style += "min-height: 77px;" # image + 2 text lines
|
self.update_state()
|
||||||
self.style += "line-height: 1em;"
|
tt = f"command: {self.command.strip()}"
|
||||||
if self.image and path.exists(self.image):
|
if self.state_command:
|
||||||
# copy image files into the static folder
|
tt += f"\nstate: {self.state}"
|
||||||
basename = path.basename(self.image)
|
QTooltip(a=self, text=tt, delay=500) # setting style white-space:pre does not work, see wp.css below
|
||||||
staticfile = path.join(STATIC_DIR, basename)
|
def click(self, msg):
|
||||||
if not path.exists(staticfile):
|
if self.command != '':
|
||||||
shutil.copy2(self.image, staticfile)
|
|
||||||
if DEBUG:
|
|
||||||
print(f'[DEBUG] copy {self.image} to {staticfile}')
|
|
||||||
self.icon = f"img:/static/{basename}"
|
|
||||||
# <q-icon name="img:data:image/svg+xml;charset=utf8,<svg xmlns='http://www.w3.org/2000/svg' height='140' width='500'><ellipse cx='200' cy='80' rx='100' ry='50' style='fill:yellow;stroke:purple;stroke-width:2' /></svg>" />
|
|
||||||
# <q-btn icon="img:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" ... />
|
|
||||||
|
|
||||||
if self.command != '':
|
|
||||||
QTooltip(a=self, text=self.command.strip(), delay=500) # setting style white-space:pre does not work, see wp.css below
|
|
||||||
def click(self, msg):
|
|
||||||
self.update_state()
|
self.update_state()
|
||||||
if self.command != '':
|
if DEBUG:
|
||||||
print()
|
print()
|
||||||
print(datetime.datetime.now())
|
print(datetime.datetime.now())
|
||||||
print(self.command)
|
print(f"command: {self.command}")
|
||||||
process(self.command, shell=True, output=False) # output=True freezes controldeck until process finished (until e.g. an emacs button is closed)
|
process(self.command, shell=True, output=False) # output=True freezes controldeck until process finished (until e.g. an emacs button is closed)
|
||||||
self.on('click', click)
|
self.on('click', click)
|
||||||
|
|
||||||
|
def is_state_alt(self):
|
||||||
|
return self.state == self.state_pattern_alt
|
||||||
|
|
||||||
def update_state(self):
|
def update_state(self):
|
||||||
if self.state_command != '':
|
if self.state_command != '':
|
||||||
self.state = process(self.state_command, shell=True)
|
self.state = process(self.state_command, shell=True)
|
||||||
|
if DEBUG:
|
||||||
|
print()
|
||||||
|
print(datetime.datetime.now())
|
||||||
|
print("update btn state")
|
||||||
|
print(f"text: {self.text}")
|
||||||
|
print(f"state (before click): {self.state}")
|
||||||
|
print(f"state_command: {self.state_command}")
|
||||||
|
print(f"state_pattern: {self.state_pattern}")
|
||||||
|
print(f"state_pattern_alt: {self.state_pattern_alt}")
|
||||||
|
print(f"is_state_alt: {self.is_state_alt()}")
|
||||||
|
if self.is_state_alt():
|
||||||
|
# self.style += "border: 1px solid green;"
|
||||||
|
self.style += "border-bottom: 1px solid green;"
|
||||||
|
|
||||||
|
|
||||||
class Volume(Div):
|
class Volume(Div):
|
||||||
# class variables
|
# class variables
|
||||||
@@ -202,7 +258,7 @@ class Volume(Div):
|
|||||||
self.slider = None # for handle methods to access slider
|
self.slider = None # for handle methods to access slider
|
||||||
|
|
||||||
# default **kwargs
|
# default **kwargs
|
||||||
self.vtype = 'sink' # sink (loudspeaker) or sink-input (app output)
|
self.wtype = 'sink' # sink (loudspeaker) or sink-input (app output)
|
||||||
self.name = '' # pulseaudio sink name
|
self.name = '' # pulseaudio sink name
|
||||||
self.description = '' # badge name
|
self.description = '' # badge name
|
||||||
|
|
||||||
@@ -211,10 +267,10 @@ class Volume(Div):
|
|||||||
self.update_state() # get self.pa_state
|
self.update_state() # get self.pa_state
|
||||||
|
|
||||||
if self.pa_state:
|
if self.pa_state:
|
||||||
if self.vtype == 'sink':
|
if self.wtype == 'sink':
|
||||||
cmdl_toggle = 'pactl set-sink-mute {name} toggle'
|
cmdl_toggle = 'pactl set-sink-mute {name} toggle'
|
||||||
cmdl_value = 'pactl set-sink-volume {name} {value}%'
|
cmdl_value = 'pactl set-sink-volume {name} {value}%'
|
||||||
elif self.vtype == 'sink-input':
|
elif self.wtype == 'sink-input':
|
||||||
cmdl_toggle = 'pactl set-sink-input-mute {name} toggle'
|
cmdl_toggle = 'pactl set-sink-input-mute {name} toggle'
|
||||||
cmdl_value = 'pactl set-sink-input-volume {name} {value}%'
|
cmdl_value = 'pactl set-sink-input-volume {name} {value}%'
|
||||||
app_name = self.pa_state['properties']['application.process.binary'] if 'application.process.binary' in self.pa_state['properties'] else ''
|
app_name = self.pa_state['properties']['application.process.binary'] if 'application.process.binary' in self.pa_state['properties'] else ''
|
||||||
@@ -333,11 +389,11 @@ class Volume(Div):
|
|||||||
self.update_states()
|
self.update_states()
|
||||||
tmp = []
|
tmp = []
|
||||||
# filter for the given pa name, empty list if not found
|
# filter for the given pa name, empty list if not found
|
||||||
if self.vtype == 'sink':
|
if self.wtype == 'sink':
|
||||||
# match pa name with self.name
|
# match pa name with self.name
|
||||||
tmp = list(filter(lambda item: item['name'] == self.name,
|
tmp = list(filter(lambda item: item['name'] == self.name,
|
||||||
Volume.data['sinks']))
|
Volume.data['sinks']))
|
||||||
elif self.vtype == 'sink-input':
|
elif self.wtype == 'sink-input':
|
||||||
# match pa index with self.name
|
# match pa index with self.name
|
||||||
try: # for int casting
|
try: # for int casting
|
||||||
tmp = list(filter(lambda item: item['index'] == int(self.name),
|
tmp = list(filter(lambda item: item['index'] == int(self.name),
|
||||||
@@ -354,7 +410,7 @@ class VolumeGroup():
|
|||||||
a = kwargs.pop('a', None) # add Volume widgets to the specified component
|
a = kwargs.pop('a', None) # add Volume widgets to the specified component
|
||||||
Volume.update_states()
|
Volume.update_states()
|
||||||
for i in Volume.data['sink-inputs']:
|
for i in Volume.data['sink-inputs']:
|
||||||
Volume(a=a, name=i['index'], vtype='sink-input')
|
Volume(a=a, name=i['index'], wtype='sink-input')
|
||||||
|
|
||||||
async def reload(self, msg):
|
async def reload(self, msg):
|
||||||
await msg.page.reload()
|
await msg.page.reload()
|
||||||
@@ -390,7 +446,7 @@ def widget_load(config) -> dict:
|
|||||||
iname = None
|
iname = None
|
||||||
#iname = re.search(r"^([0-9]*:?)([0-9]*\.?)(button|empty)", i, flags=re.IGNORECASE)
|
#iname = re.search(r"^([0-9]*:?)([0-9]*\.?)(button|empty)", i, flags=re.IGNORECASE)
|
||||||
iname = re.search(
|
iname = re.search(
|
||||||
r"^([0-9a-z]*:)?([0-9]*\.)?(button|empty|sink-inputs|sink|source)", # sink-inputs BEFORE sink
|
r"^([0-9a-z]*:)?([0-9]*\.)?(empty|label|button|sink-inputs|sink|source)", # sink-inputs BEFORE sink!
|
||||||
i, flags=re.IGNORECASE)
|
i, flags=re.IGNORECASE)
|
||||||
if iname is not None:
|
if iname is not None:
|
||||||
tab_name = iname.group(1)[:-1] if iname.group(1) is not None else '' # remove collon, id is '' if nothing is given
|
tab_name = iname.group(1)[:-1] if iname.group(1) is not None else '' # remove collon, id is '' if nothing is given
|
||||||
@@ -403,7 +459,14 @@ def widget_load(config) -> dict:
|
|||||||
# print('wid_type', wid_type)
|
# print('wid_type', wid_type)
|
||||||
# print('wid_name', wid_name)
|
# print('wid_name', wid_name)
|
||||||
# print('')
|
# print('')
|
||||||
if wid_type in ['button', 'empty']:
|
if wid_type == 'empty':
|
||||||
|
args = [{'widget-class': 'Empty',
|
||||||
|
'type': wid_type}]
|
||||||
|
if wid_type == 'label':
|
||||||
|
args = [{'widget-class': 'Label',
|
||||||
|
'type': wid_type,
|
||||||
|
'text': wid_name}]
|
||||||
|
elif wid_type == 'button':
|
||||||
# button or empty
|
# button or empty
|
||||||
args = [{'widget-class': 'Button',
|
args = [{'widget-class': 'Button',
|
||||||
'type': wid_type,
|
'type': wid_type,
|
||||||
@@ -416,7 +479,6 @@ def widget_load(config) -> dict:
|
|||||||
'state': config.get(i, 'state', fallback=''),
|
'state': config.get(i, 'state', fallback=''),
|
||||||
'state-alt': config.get(i, 'state-alt', fallback=''),
|
'state-alt': config.get(i, 'state-alt', fallback=''),
|
||||||
'state-command': config.get(i, 'state-command', fallback=''),
|
'state-command': config.get(i, 'state-command', fallback=''),
|
||||||
'state-command-alt': config.get(i, 'state-command-alt', fallback=''),
|
|
||||||
'icon': config.get(i, 'icon', fallback=''),
|
'icon': config.get(i, 'icon', fallback=''),
|
||||||
'icon-alt': config.get(i, 'icon-alt', fallback=''),
|
'icon-alt': config.get(i, 'icon-alt', fallback=''),
|
||||||
'image': config.get(i, 'image', fallback=''),
|
'image': config.get(i, 'image', fallback=''),
|
||||||
@@ -776,22 +838,30 @@ async def application(request):
|
|||||||
name=var,
|
name=var,
|
||||||
classes="row q-pa-sm q-gutter-sm",
|
classes="row q-pa-sm q-gutter-sm",
|
||||||
a=tab_panel[tab_name])
|
a=tab_panel[tab_name])
|
||||||
|
if j['widget-class'] == 'Empty':
|
||||||
|
Label(text='',
|
||||||
|
wtype=j['type'],
|
||||||
|
a=eval(var))
|
||||||
|
if j['widget-class'] == 'Label':
|
||||||
|
Label(text=j['text'],
|
||||||
|
wtype=j['type'],
|
||||||
|
a=eval(var))
|
||||||
if j['widget-class'] == 'Button':
|
if j['widget-class'] == 'Button':
|
||||||
Button(text=j['text'], text_alt=j['text-alt'],
|
Button(text=j['text'], text_alt=j['text-alt'],
|
||||||
btype=j['type'],
|
wtype=j['type'],
|
||||||
command=j['command'], command_alt=j['command-alt'],
|
command=j['command'], command_alt=j['command-alt'],
|
||||||
color_bg=j['color-bg'], color_fg=j['color-fg'],
|
color_bg=j['color-bg'], color_fg=j['color-fg'],
|
||||||
state_pattern=j['state'], state_pattern_alt=j['state-alt'],
|
state_pattern=j['state'], state_pattern_alt=j['state-alt'],
|
||||||
state_command=j['state-command'], state_command_alt=j['state-command-alt'],
|
state_command=j['state-command'],
|
||||||
icon=j['icon'], icon_alt=j['icon-alt'],
|
icon=j['icon'], icon_alt=j['icon-alt'],
|
||||||
image=j['image'], image_alt=j['image-alt'],
|
image=j['image'], image_alt=j['image-alt'],
|
||||||
a=eval(var))
|
a=eval(var))
|
||||||
elif j['widget-class'] == 'Volume':
|
elif j['widget-class'] == 'Volume':
|
||||||
Volume(name=j['name'], description=j['description'],
|
Volume(name=j['name'], description=j['description'],
|
||||||
vtype=j['type'],
|
wtype=j['type'],
|
||||||
a=eval(var))
|
a=eval(var))
|
||||||
elif j['widget-class'] == 'VolumeGroup':
|
elif j['widget-class'] == 'VolumeGroup':
|
||||||
VolumeGroup(vtype=j['type'], a=eval(var))
|
VolumeGroup(wtype=j['type'], a=eval(var))
|
||||||
|
|
||||||
# TODO: change reference wp.components to ...
|
# TODO: change reference wp.components to ...
|
||||||
if not wp.components:
|
if not wp.components:
|
||||||
|
|||||||
Reference in New Issue
Block a user