Skip to content

Commit

Permalink
icons, fixes, cleanups, initial testing clobber
Browse files Browse the repository at this point in the history
  • Loading branch information
danthedeckie authored Apr 5, 2019
1 parent 779e43a commit 585e99d
Show file tree
Hide file tree
Showing 15 changed files with 269 additions and 13 deletions.
37 changes: 26 additions & 11 deletions gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
########

import sys
from os.path import basename
from os.path import basename, abspath, dirname, join as joindir

import tkinter as tk
import tkinter.ttk as ttk
Expand All @@ -30,10 +30,15 @@
('Collection', 'Collections'),
('EFX', 'EFX'),
('Audio', 'Audio Files'),
('Video', 'Video Files'),
('Show', 'Shows'),
('Chaser', 'Chasers'),
('RGBMatrix', 'RGB Matrix Functions'),
('Script', 'Scripts'),
]

ICONS = {}

# Global State: (oh noes)
CLIPBOARD = set()

Expand Down Expand Up @@ -67,7 +72,8 @@ def create_widgets(self):
ttk.Label(self, textvariable=self.filenameText).pack()

self.filterText = ttk.Entry(self) # TODO
self.filterText.pack(padx=0.1)
self.filterText.bind('<Key>', self.update_filter)
self.filterText.pack(padx=0.1, fill=tk.X, expand=1)

self.functionList = ttk.Treeview(self)
self.functionList.pack(fill=tk.BOTH, expand=1)
Expand Down Expand Up @@ -104,6 +110,9 @@ def load_file(self):
self.qfile = QLCFile(self.filename)
self.filenameText.set(basename(self.filename))

def update_filter(self, event):
self.update_treeview()

def update_treeview(self):
'''
This should be able to be called multiple times without mucking up
Expand All @@ -118,7 +127,7 @@ def update_treeview(self):

# Top level tree items (Sections):
for iid, plural in FUNCTION_TYPES:
self.functionList.insert('', 'end', '_' + iid, text=plural)
self.functionList.insert('', 'end', '_' + iid, text=plural, open=True, image=ICONS[iid])

allcount = 0
orphancount = 0
Expand All @@ -129,10 +138,15 @@ def update_treeview(self):
# TODO: Show in folders mode
# TODO: Create folders and sort out functions into new folders depending on usage.
# TODO: Images for function type...
# TODO: Checking / Fixing Folders, what if something has a path that doesn't exist?

filtertext = self.filterText.get()

for f in funcs:
allcount += 1
name = "{} [{}]".format(f.attrib["Name"],f.attrib["ID"])
if not filtertext in name.lower(): continue

self.functionList.insert('_' + f.attrib["Type"],'end', 'FUNC:' + f.attrib["ID"], text=name)

#[print(x) for x in self.qfile.subfunction_ids(f)]
Expand All @@ -150,16 +164,10 @@ def copySelected(self):
if iid.startswith('FUNC:')]

CLIPBOARD.clear()

for iid in selected_ids:
func = self.qfile.function_by_id(iid)
if not func: continue


for func in self.qfile.iter_functions_for_clipboard(selected_ids):
CLIPBOARD.add(func)

for subfunc in self.qfile.subfunction_ids(func, recurse=True):
CLIPBOARD.add(subfunc)

print('%i items in clipboard' % len(CLIPBOARD))


Expand Down Expand Up @@ -221,6 +229,13 @@ def load_file(self, filename=None):

app.master.title('QLC+ Multi-file Helper Utility')

iconsdir = joindir(dirname(abspath(__file__)), 'icons')
for functype, _ in FUNCTION_TYPES:
icon = tk.PhotoImage(file=joindir(iconsdir, functype.lower() + '.png'))
icon = icon.subsample(2, 2)
ICONS[functype] = icon


for f in (sys.argv[1:]):
try:
app.load_file(f)
Expand Down
Binary file added icons/audio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/chaser.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/collection.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/cuelist.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/efx.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/rgbmatrix.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/scene.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/script.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/sequence.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/show.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/video.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 15 additions & 2 deletions reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self, filename):
def write(self, filename, *vargs, **kwargs):
new_file = ET.ElementTree(self.root)

with open(filename, 'w') as f:
with open(filename, 'w', encoding="utf8") as f:
# apparently etree cannot write doctypes :-(
# oh well. we can.
f.write('<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE Workspace>\n')
Expand Down Expand Up @@ -126,6 +126,19 @@ def subfunction_id_replace(self, func, old_id, new_id):
if f.text == old_id:
f.text = new_id

def iter_functions_for_clipboard(self, ids):
'''
given a list of ids, generate all functions which need to be copied,
including sub-functions / dependencies.
'''
for iid in ids:
func = self.function_by_id(iid)
if not func: continue

yield func

for subfunc in self.subfunction_ids(func, recurse=True):
yield subfunc

def paste_functions_here(self, clipboard):

Expand All @@ -139,7 +152,7 @@ def paste_functions_here(self, clipboard):

# print(new_functions)

fresh_id = self.highest_function_id() + 1
fresh_id = self.highest_function_id()# + 1

current_ids = set(self.used_function_ids())

Expand Down
86 changes: 86 additions & 0 deletions snippets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'''
XML Snippets for building valid QXW files for testing.
Structure:
header
engine_header
<Any Fixtures you want to use>
<Any Functions you want to use>
engine_footer
virtual_console (default is empty_virtual_console)
simple_desk (default is empty_simple_desk)
footer
'''

header = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Workspace>
<Workspace xmlns="http://www.qlcplus.org/Workspace" CurrentWindow="FixtureManager">
<Creator>
<Name>Q Light Controller Plus</Name>
<Version>4.12.0</Version>
<Author>daniel.fairhead</Author>
</Creator>
'''
engine_header = '''<Engine>
<InputOutputMap>
<Universe Name="Universe 1" ID="0">
<Output Plugin="E1.31" Line="1">
<PluginParameters UniverseChannels="363"/>
</Output>
</Universe>
<Universe Name="Universe 2" ID="1"/>
<Universe Name="Universe 3" ID="2"/>
<Universe Name="Universe 4" ID="3"/>
</InputOutputMap>
'''

engine_footer = '</Engine>'

footer = '''
</Workspace>
'''

empty_virtual_console = '''
<VirtualConsole>
<Frame Caption="">
<Appearance>
<FrameStyle>None</FrameStyle>
<ForegroundColor>Default</ForegroundColor>
<BackgroundColor>Default</BackgroundColor>
<BackgroundImage>None</BackgroundImage>
<Font>Default</Font>
</Appearance>
</Frame>
<Properties>
<Size Width="1920" Height="1080"/>
<GrandMaster ChannelMode="Intensity" ValueMode="Reduce" SliderMode="Normal"/>
</Properties>
</VirtualConsole>
'''

empty_simple_desk = '''
<SimpleDesk>
<Engine/>
</SimpleDesk>
'''

def generic_fixture(uid, address, channels=1, universe=0, name=None):
return '''
<Fixture>
<Manufacturer>Generic</Manufacturer>
<Model>Generic</Model>
<Mode>1 Channel</Mode>
<ID>{uid}</ID>
<Name>{name}</Name>
<Universe>{universe}</Universe>
<Address>{address}</Address>
<Channels>{channels}</Channels>
</Fixture>
'''.format(uid=uid, address=address, universe=universe, channels=channels,
name=name or 'Dimmer %i' % uid)

def function_scene(uid, name, path=""):
return '''<Function ID="{uid}" Type="Scene" Name="{name}" Path="{path}">
<Speed FadeIn="0" FadeOut="0" Duration="0"/>
</Function>'''.format(uid=uid, name=name, path=path)
142 changes: 142 additions & 0 deletions test_reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import xml.etree.ElementTree as ET
from io import StringIO

from unittest import TestCase

from reader import NS, QLCFile
import snippets

class BasicFileTest(TestCase):
def setUp(self):
super().setUp()
self.virtual_console = snippets.empty_virtual_console
self.simple_desk = snippets.empty_simple_desk
self.fixtures = []
self.functions = []

def get_xml(self):
return '\n'.join((
snippets.header,
snippets.engine_header,
'\n'.join(self.fixtures),
'\n'.join(self.functions),
snippets.engine_footer,
self.virtual_console,
self.simple_desk,
snippets.footer))


class SanityTest(BasicFileTest):
def test_basicfile(self):
x = StringIO(self.get_xml())
r = ET.parse(x).getroot()

# TODO: callout to actual QLC+ to load the file and check it

def test_some_fixtures(self):
self.fixtures = [
snippets.generic_fixture(0, 1),
snippets.generic_fixture(1, 2),
snippets.generic_fixture(2, 3, channels=6),
]

x = StringIO(self.get_xml())
r = ET.parse(x).getroot()

def test_some_functions(self):
self.functions = [
snippets.function_scene(0, "Basic Scene"),
snippets.function_scene(1, "Basic Scene 1"),
snippets.function_scene(2, "Basic Scene 2"),
]
x = StringIO(self.get_xml())
r = ET.parse(x).getroot()

def test_some_fixtures_and_functions(self):
self.fixtures = [
snippets.generic_fixture(0, 1),
snippets.generic_fixture(1, 2),
snippets.generic_fixture(2, 3, channels=6),
]

self.functions = [
snippets.function_scene(0, "Basic Scene"),
snippets.function_scene(1, "Basic Scene 1"),
snippets.function_scene(2, "Basic Scene 2"),
]

x = StringIO(self.get_xml())
r = ET.parse(x).getroot()

# TODO: Functions USING fixtures...

##################################

class TestCopyingSimpleScene(BasicFileTest):
def test_copy_to_blank_file(self):
self.functions = []
empty_file = QLCFile(StringIO(self.get_xml()))

self.functions = [
snippets.function_scene(0, "Basic Scene"),
snippets.function_scene(1, "Basic Scene 1"),
snippets.function_scene(2, "Basic Scene 2"),
]

full_file = QLCFile(StringIO(self.get_xml()))

clipboard = list(full_file.iter_functions_for_clipboard(['0','1','2']))
empty_file.paste_functions_here(clipboard)

new_functions = empty_file.list_functions()
self.assertEqual(new_functions[0].attrib["Name"], "Basic Scene")
self.assertEqual(new_functions[1].attrib["Name"], "Basic Scene 1")
self.assertEqual(new_functions[2].attrib["Name"], "Basic Scene 2")

self.assertEqual(new_functions[0].attrib["ID"], "0")
self.assertEqual(new_functions[1].attrib["ID"], "1")
self.assertEqual(new_functions[2].attrib["ID"], "2")

def test_copy_to_nonempty_file(self):

self.functions = [
snippets.function_scene(0, "Basic Scene"),
snippets.function_scene(1, "Basic Scene 1"),
snippets.function_scene(2, "Basic Scene 2"),
]

to_file = QLCFile(StringIO(self.get_xml()))

self.functions = [
snippets.function_scene(0, "Basic Scene A"),
snippets.function_scene(1, "Basic Scene B"),
snippets.function_scene(2, "Basic Scene C"),
]

from_file = QLCFile(StringIO(self.get_xml()))

clipboard = list(from_file.iter_functions_for_clipboard(['0','1','2']))
to_file.paste_functions_here(clipboard)

new_functions = to_file.list_functions()
self.assertEqual(len(new_functions), 6)
self.assertEqual(new_functions[0].attrib["Name"], "Basic Scene")
self.assertEqual(new_functions[1].attrib["Name"], "Basic Scene 1")
self.assertEqual(new_functions[2].attrib["Name"], "Basic Scene 2")
self.assertEqual(new_functions[3].attrib["Name"], "Basic Scene A")
self.assertEqual(new_functions[4].attrib["Name"], "Basic Scene B")
self.assertEqual(new_functions[5].attrib["Name"], "Basic Scene C")


self.assertEqual(new_functions[0].attrib["ID"], "0")
self.assertEqual(new_functions[1].attrib["ID"], "1")
self.assertEqual(new_functions[2].attrib["ID"], "2")
self.assertEqual(new_functions[3].attrib["ID"], "3")
self.assertEqual(new_functions[4].attrib["ID"], "4")
self.assertEqual(new_functions[5].attrib["ID"], "5")






0 comments on commit 585e99d

Please sign in to comment.