-
Notifications
You must be signed in to change notification settings - Fork 23
/
actions.py
1902 lines (1718 loc) · 93.4 KB
/
actions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Built-in
import logging
import os
import webbrowser
from functools import partial
# External
from Qt import QtCore, QtGui, QtWidgets
# Internal
from . import DIRECTIONS
from nxt_editor.constants import NXT_WEBSITE
from nxt_editor import user_dir
from nxt import nxt_layer, DATA_STATE, nxt_path
from nxt_editor import colors, finder, file_search
logger = logging.getLogger('nxt')
class NxtAction(QtWidgets.QAction):
def __init__(self, text, parent=None):
"""Creates action that can have user defined shortcuts
:param parent: Main window instance that will own the action.
:param text: Name for the action that will be saved as the key
in the hotkeys.json
"""
super(NxtAction, self).__init__(text, parent)
self.cached_user_shortcuts = self.cache_shortcuts()
self.setShortcutContext(QtCore.Qt.WidgetShortcut)
self.default_shortcut = None
if parent:
parent.addAction(self)
if text in self.cached_user_shortcuts.keys():
self.setShortcut(self.cached_user_shortcuts[text],
user_override=True)
def cache_shortcuts(self):
"""Populates `self.cached_user_shortcuts` with a copy of the on-disk
preference dictionary of shortcuts. Returns said copy.
"""
self.cached_user_shortcuts = user_dir.hotkeys.copy()
return self.cached_user_shortcuts
def setShortcut(self, shortcut, user_override=False):
self.cache_shortcuts()
text = self.text()
if not user_override:
self.default_shortcut = shortcut
if not user_override and text in self.cached_user_shortcuts.keys():
shortcut = self.cached_user_shortcuts.get(text)
elif user_override:
self.save_shortcut(name=text, shortcut=shortcut)
super(NxtAction, self).setShortcut(shortcut)
def text(self):
"""Makes sure we get a string back at some point unicode messed us up"""
text = super(NxtAction, self).text()
return str(text)
def setText(self, new_text):
old_text = self.text()
if not new_text:
raise Exception('Sorry Nxt Actions must have valid text!')
if old_text and old_text != new_text:
# This code is only reached if somehow you change the name of
# the action in the code, this does not retroactively update the
# user's pref file!
self.update_shortcut(old_text, new_text, self.shortcut())
super(NxtAction, self).setText(new_text)
def save_shortcut(self, name, shortcut):
if isinstance(shortcut, QtGui.QKeySequence):
shortcut = str(shortcut.toString())
default_shortcut = self.default_shortcut
if isinstance(default_shortcut, QtGui.QKeySequence):
default_shortcut = default_shortcut.toString()
if shortcut == default_shortcut:
self.remove_shortcut(name)
else:
self.cached_user_shortcuts[name] = shortcut or None
user_dir.hotkeys[name] = shortcut or None
def update_shortcut(self, old_name, new_name, shortcut):
self.cache_shortcuts()
if isinstance(shortcut, QtGui.QKeySequence):
shortcut = str(shortcut.toString())
self.remove_shortcut(old_name)
self.save_shortcut(new_name, shortcut)
def remove_shortcut(self, name):
if name in self.cached_user_shortcuts.keys():
self.cached_user_shortcuts.pop(name)
class NxtActionContainer(QtWidgets.QWidget):
def __init__(self, main_window):
"""QObject used to hold pointers to actions and organize them
logically. In general actions in a container object should be
ones that logically would NOT have conflicting hotkeys. For example
you wouldn't want actions controlling the mute state of a layer in
the same container as actions controlling the disabled state of a node.
Often action's triggered signal will need to be connected somewhere
outside of the container object. Carefully consider when and where
action is needed/used to determine if it should be connected to a
when it is created in the container init.
:param main_window: nxt MainWindow object
"""
super(NxtActionContainer, self).__init__(parent=main_window)
self.main_window = main_window
self.main_window.tab_changed.connect(self.handle_model_change)
self.action_display_order = []
self.available_without_model = []
self.prev_enabled_state = {}
def get_action_data(self):
"""Parses the container for actions and assembles a sorted multi
dimensional list. Each sub-list contains the following: Name, What's
This, Tooltip, Shortcut (string), NxtAction. This data is intended to
be used by the hotkey dockwidet.
:return: list
"""
action_list = []
for action in self.actions():
name = action.text()
what = action.whatsThis()
tool_tip = action.toolTip()
shortcut = action.shortcut().toString()
action_list += [[name, what, tool_tip, shortcut, action]]
info = (len(action_list), self.objectName())
logger.debug('{} action(s) found in {}'.format(*info))
return action_list
def actions(self):
"""Attempts to return the display order of the actions if it is
different than the declaration order.
:return: list
"""
ordered_actions = self.action_display_order[:]
for action in super(NxtActionContainer, self).actions():
if action not in ordered_actions:
ordered_actions += [action]
return ordered_actions
def handle_model_change(self):
if not self.main_window.model:
for action in self.actions():
if action in self.available_without_model:
continue
self.prev_enabled_state[action] = action.isEnabled()
action.setEnabled(False)
else:
for action in self.actions():
if action in self.available_without_model:
continue
prev_state = self.prev_enabled_state.get(action, None)
if prev_state is not None:
action.setEnabled(prev_state)
self.prev_enabled_state = {}
class BoolUserPrefAction(NxtAction):
"""NxtAction that saves state between sessions via preference key."""
def __init__(self, text, pref_key, default=False, parent=None):
"""NxtAction that saves state between sessions via preference key
Note that default checked state is set during initialization, before
signals have been hooked up. Meaning any later-connected function is not
automatically called to "kick start" the application state. Either
build that into the start of the UI, or call your action once manually
to kick start.
:param text: Action description.
:type text: str
:param pref_key: Preference key to save at
:type pref_key: str
:param default: Deafult value, loads default state from user pref, only
falling back to this default when no save exists, defaults to False
:type default: bool, optional
:param parent: Action parent, defaults to None
:type parent: QObject, optional
"""
super(BoolUserPrefAction, self).__init__(text, parent)
self.setCheckable(True)
self.pref_key = pref_key
self.setChecked(user_dir.user_prefs.get(self.pref_key, default))
self.triggered.connect(self.on_triggered)
def on_triggered(self):
user_dir.user_prefs[self.pref_key] = bool(self.isChecked())
class AppActions(NxtActionContainer):
def __init__(self, main_window):
super(AppActions, self).__init__(main_window)
self.setObjectName('Application Actions')
# GENERAL ACTIONS
context = QtCore.Qt.WindowShortcut
# User docs
def open_user_docs():
webbrowser.open_new(NXT_WEBSITE)
self.docs_action = NxtAction(text='User Docs', parent=self)
self.docs_action.setShortcut('F1')
self.docs_action.triggered.connect(open_user_docs)
self.docs_action.setWhatsThis('Open user docs.')
self.docs_action.setShortcutContext(context)
# Undo
self.undo_action = NxtAction(text='Undo', parent=self)
self.undo_action.setShortcut('Ctrl+Z')
self.undo_action.triggered.connect(self.main_window.undo)
self.undo_action.setWhatsThis('Undo last command.')
self.undo_action.setShortcutContext(context)
# Redo
self.redo_action = NxtAction(text='Redo', parent=self)
self.redo_action.setShortcut('Ctrl+Shift+Z')
self.redo_action.triggered.connect(self.main_window.redo)
self.redo_action.setWhatsThis('Redo last command.')
self.redo_action.setShortcutContext(context)
# Close
self.close_action = NxtAction(text='Exit', parent=self)
self.close_action.setShortcut('Alt+F4')
self.close_action.triggered.connect(self.main_window.close)
self.close_action.setWhatsThis('Close nxt.')
self.available_without_model.append(self.close_action)
self.close_action.setShortcutContext(context)
# New graph
self.new_graph_action = NxtAction(text='New Graph',
parent=self)
self.new_graph_action.triggered.connect(self.main_window.new_tab)
self.new_graph_action.setShortcut('Ctrl+N')
self.new_graph_action.setShortcutContext(context)
self.new_graph_action.setWhatsThis('New empty graph.')
self.available_without_model.append(self.new_graph_action)
# Open graph
self.open_file_action = NxtAction(text='Open Graph',
parent=self)
self.open_file_action.triggered.connect(self.main_window.load_file)
self.open_file_action.setShortcut('Ctrl+O')
self.open_file_action.setShortcutContext(context)
self.open_file_action.setWhatsThis('Open nxt file.')
self.available_without_model.append(self.open_file_action)
# Open Previous
def open_previous():
pref_key = user_dir.EDITOR_CACHE.LAST_CLOSED
last_sessions = user_dir.editor_cache[pref_key]
prev_session = []
try:
prev_session = last_sessions.pop(-1)
except IndexError:
logger.info("No more sessions to re-open")
for path in prev_session:
self.main_window.load_file(filepath=path)
user_dir.editor_cache[pref_key] = last_sessions
self.open_previous_action = NxtAction(text='Open Last Closed',
parent=self)
self.open_previous_action.setShortcut('Ctrl+Shift+T')
self.open_previous_action.setShortcutContext(context)
self.open_previous_action.setWhatsThis('Open previously closed file(s)')
self.open_previous_action.triggered.connect(open_previous)
self.available_without_model.append(self.open_previous_action)
# Close Tab
def close_tab():
current_idx = self.main_window.open_files_tab_widget.currentIndex()
self.main_window.open_files_tab_widget.close_tab(current_idx)
self.close_tab_action = NxtAction(text='Close Tab',
parent=self)
self.close_tab_action.setShortcut('Ctrl+W')
self.close_tab_action.setShortcutContext(context)
self.close_tab_action.setWhatsThis('Close active tab')
self.close_tab_action.triggered.connect(close_tab)
# Open Find and Replace
def open_find_rep():
find_rep_widget = self.main_window.find_rep
if not find_rep_widget.isVisible():
find_rep_widget.show()
find_rep_widget.raise_()
self.find_rep_action = NxtAction(text='Find and Replace', parent=self)
self.find_rep_action.setShortcut('Ctrl+F')
self.find_rep_action.setShortcutContext(context)
self.find_rep_action.setWhatsThis('Open find and replace dialog')
self.find_rep_action.triggered.connect(open_find_rep)
# Dock Widget Actions
# Layer Manager
self.layer_manager_action = NxtAction(text='Layer Manager',
parent=self)
self.layer_manager_action.setCheckable(True)
self.layer_manager_action.setWhatsThis('Toggle the layer manager '
'widget.')
self.layer_manager_action.setShortcutContext(context)
self.available_without_model.append(self.layer_manager_action)
# Property editor
self.property_editor_action = NxtAction(text='Property Editor',
parent=self)
self.property_editor_action.setCheckable(True)
self.property_editor_action.setShortcutContext(context)
self.property_editor_action.setWhatsThis('Toggle the property editor '
'widget.')
self.available_without_model.append(self.property_editor_action)
# Code editor
self.code_editor_action = NxtAction(text='Code Editor',
parent=self)
self.code_editor_action.setCheckable(True)
self.code_editor_action.setShortcutContext(context)
self.code_editor_action.setWhatsThis('Toggle the code editor widget.')
self.available_without_model.append(self.code_editor_action)
# History view
self.history_view_action = NxtAction(text='History View',
parent=self)
self.history_view_action.setCheckable(True)
self.history_view_action.setShortcutContext(context)
self.history_view_action.setWhatsThis('Toggle the history view widget.')
self.available_without_model.append(self.history_view_action)
# Output log
self.output_log_action = NxtAction(text='Output Log',
parent=self)
self.output_log_action.setCheckable(True)
self.output_log_action.setShortcutContext(context)
self.output_log_action.setWhatsThis('Toggle the output log widget.')
self.available_without_model.append(self.output_log_action)
# Hotkey editor
self.hotkey_editor_action = NxtAction(text='Hotkey Editor',
parent=self)
self.hotkey_editor_action.setCheckable(True)
self.hotkey_editor_action.setShortcutContext(context)
self.hotkey_editor_action.setWhatsThis('Toggle the hotkey editor '
'widget.')
self.available_without_model.append(self.hotkey_editor_action)
# Build View
self.build_view_action = NxtAction(text='Build View',
parent=self)
self.build_view_action.setCheckable(True)
self.build_view_action.setShortcutContext(context)
self.build_view_action.setWhatsThis('Toggle the build view '
'widget.')
self.available_without_model.append(self.build_view_action)
# Workflow tools
self.workflow_tools_action = NxtAction(text='Workflow Tools',
parent=self)
self.workflow_tools_action.setCheckable(True)
self.workflow_tools_action.setShortcutContext(context)
self.workflow_tools_action.setWhatsThis('Workflow tools dock widget.')
self.available_without_model.append(self.workflow_tools_action)
def find_node():
if not self.main_window.model:
return
finder.FinderWidget(self.main_window, self.main_window.model).show()
self.find_node_action = NxtAction(text='Find Node',
parent=self)
self.find_node_action.setWhatsThis('Search for, select, and focus '
'nodes in the view')
self.find_node_action.setShortcut('/')
self.find_node_action.triggered.connect(find_node)
self.find_node_action.setShortcutContext(context)
def find_and_open():
parent = self.main_window
width = parent.width()*.75
window_path = self.main_window.get_current_tab_file_path()
path = None
if window_path:
path = os.path.dirname(window_path)
if not path:
path = os.getcwd()
filepath = file_search.Searcher.get_open_file_path(parent, width,
default=path)
filepath = nxt_path.full_file_expand(filepath)
if os.path.isfile(filepath):
self.main_window.load_file(filepath)
self.find_and_open_action = NxtAction(text='Find and Open Graph',
parent=self)
self.find_and_open_action.setShortcut('Ctrl+P')
self.find_and_open_action.triggered.connect(find_and_open)
self.find_and_open_action.setShortcutContext(context)
self.available_without_model.append(self.find_and_open_action)
def clear_logs():
rich = self.main_window.output_log.rich_output_textedit
raw = self.main_window.output_log.raw_output_textedit
rich.clear()
raw.clear()
self.clear_logs_action = NxtAction(text='Clear All Logs',
parent=self)
self.clear_logs_action.setWhatsThis('Clear all the text from all of the output logs (raw and rich).')
self.clear_logs_action.triggered.connect(clear_logs)
self.clear_logs_action.setShortcutContext(context)
self.available_without_model.append(self.clear_logs_action)
# Toggle error ding sound
def toggle_ding():
pref_key = user_dir.USER_PREF.DING
ding_state = self.toggle_ding_action.isChecked()
user_dir.user_prefs[pref_key] = ding_state
self.toggle_ding_action = NxtAction('Error sound', parent=self)
self.toggle_ding_action.setWhatsThis('When enabled a "ding" sound will be played when NXT is given bad input '
'or encounters and error')
self.toggle_ding_action.setCheckable(True)
_ding_state = user_dir.user_prefs.get(user_dir.USER_PREF.DING, True)
self.toggle_ding_action.setChecked(_ding_state)
self.toggle_ding_action.triggered.connect(toggle_ding)
self.toggle_ding_action.setShortcutContext(context)
self.available_without_model.append(self.toggle_ding_action)
self.action_display_order = [self.find_node_action,
self.new_graph_action,
self.open_file_action, self.undo_action,
self.redo_action,
self.layer_manager_action,
self.property_editor_action,
self.code_editor_action,
self.history_view_action,
self.build_view_action,
self.output_log_action,
self.hotkey_editor_action,
self.workflow_tools_action,
self.toggle_ding_action,
self.clear_logs_action,
self.close_action]
class LayerActions(NxtActionContainer):
def __init__(self, main_window):
super(LayerActions, self).__init__(main_window)
self.setObjectName('Layers')
app_context = QtCore.Qt.WindowShortcut
widget_context = QtCore.Qt.WidgetWithChildrenShortcut
# Save
self.save_layer_action = NxtAction(text='Save Layer',
parent=self)
self.save_layer_action.setAutoRepeat(False)
self.save_layer_action.setData(None)
self.save_layer_action.setShortcut('Ctrl+S')
self.save_layer_action.setShortcutContext(app_context)
self.save_layer_action.setToolTip('Save Layer')
self.save_layer_action.setWhatsThis('Saves the current layer to disc')
def save_layer():
layer = self.save_layer_action.data()
clear_action_data(self.actions())
self.main_window.save_layer(layer)
self.save_layer_action.triggered.connect(save_layer)
# Save as
self.save_layer_as_action = NxtAction(text='Save Layer As',
parent=self)
self.save_layer_as_action.setAutoRepeat(False)
self.save_layer_as_action.setData(None)
self.save_layer_as_action.setShortcut('Ctrl+Shift+S')
self.save_layer_as_action.setShortcutContext(app_context)
def save_layer_as():
layer = self.save_layer_as_action.data()
clear_action_data(self.actions())
self.main_window.save_layer_as(layer)
self.save_layer_as_action.triggered.connect(save_layer_as)
# Save all layers
self.save_all_layers_action = NxtAction(text='Save All Layers',
parent=self)
self.save_all_layers_action.setShortcutContext(app_context)
save_all = self.main_window.save_all_layers
self.save_all_layers_action.triggered.connect(save_all)
# Open source
self.open_source_action = NxtAction(text='Open Source',
parent=self)
self.open_source_action.setWhatsThis('Open target layer in new tab.')
self.open_source_action.setAutoRepeat(False)
self.open_source_action.setData(None)
self.open_source_action.setShortcut('Ctrl+Shift+O')
self.open_source_action.setShortcutContext(app_context)
def open_source():
layer = self.open_source_action.data()
clear_action_data(self.actions())
self.main_window.open_source(layer)
self.open_source_action.triggered.connect(open_source)
# Change color
self.change_color_action = NxtAction(text='Change Color',
parent=self)
self.change_color_action.setAutoRepeat(False)
self.change_color_action.setData(None)
def change_color():
layer = self.change_color_action.data()
self.change_color_action.setData(None)
if not layer:
layer = self.main_window.model.target_layer
if not layer:
return
cd = QtWidgets.QColorDialog()
cd.setOption(QtWidgets.QColorDialog.DontUseNativeDialog)
for i, c in enumerate(colors.LAYER_COLORS):
cd.setCustomColor(i, c)
cd.setCurrentColor(QtGui.QColor(layer.color))
cd.exec_()
color = cd.currentColor()
if color.isValid():
color_name = color.name()
model = self.main_window.model
model.set_layer_color(layer_path=layer.real_path,
color=color_name)
self.change_color_action.triggered.connect(change_color)
# Change Alias
self.change_alias_action = NxtAction(text='Change Alias',
parent=self)
self.change_alias_action.setAutoRepeat(False)
self.change_alias_action.setData(None)
# Remove layer
self.remove_layer_action = NxtAction(text='Remove Layer',
parent=self)
self.remove_layer_action.setAutoRepeat(False)
self.remove_layer_action.setData(None)
def remove_layer():
layer = self.remove_layer_action.data()
clear_action_data(self.actions())
if not layer:
layer = self.main_window.model.display_layer
if not layer:
return
safe = self.main_window.validate_layers_saved(single_layer=layer)
if not safe:
return
self.main_window.model.remove_sublayer(layer)
self.remove_layer_action.triggered.connect(remove_layer)
# Mute layer
self.mute_layer_action = NxtAction(text='Toggle Layer Mute',
parent=self)
self.mute_layer_action.setAutoRepeat(False)
self.mute_layer_action.setData(None)
self.mute_layer_action.setShortcut('M')
self.mute_layer_action.setShortcutContext(widget_context)
def mute_layer():
layer = self.mute_layer_action.data()
clear_action_data(self.actions())
if not layer:
layer = self.main_window.model.target_layer
if not layer:
return
self.main_window.model.mute_toggle_layer(layer)
self.mute_layer_action.triggered.connect(mute_layer)
# Solo layer
self.solo_layer_action = NxtAction(text='Toggle Layer Solo',
parent=self)
self.solo_layer_action.setAutoRepeat(False)
self.solo_layer_action.setData(None)
self.solo_layer_action.setShortcut('S')
self.solo_layer_action.setShortcutContext(widget_context)
def solo_layer():
layer = self.solo_layer_action.data()
clear_action_data(self.actions())
if not layer:
layer = self.main_window.model.target_layer
if not layer:
return
self.main_window.model.solo_toggle_layer(layer)
self.solo_layer_action.triggered.connect(solo_layer)
# Create Above
self.new_layer_above_action = NxtAction(text='Create Layer Above',
parent=self)
self.new_layer_above_action.setData(None)
self.new_layer_above_action.setShortcutContext(widget_context)
def new_above():
layer = self.new_layer_above_action.data()
clear_action_data(self.actions())
if not layer:
layer = self.main_window.model.target_layer
if not layer:
return
self.main_window.model.new_layer(layer, nxt_layer.AUTHORING.CREATE,
nxt_layer.AUTHORING.ABOVE)
self.new_layer_above_action.triggered.connect(new_above)
# Create Below
self.new_layer_below_action = NxtAction(text='Create Layer Below',
parent=self)
self.new_layer_below_action.setData(None)
self.new_layer_below_action.setShortcutContext(widget_context)
def new_below():
layer = self.new_layer_below_action.data()
clear_action_data(self.actions())
if not layer:
layer = self.main_window.model.target_layer
if not layer:
return
self.main_window.model.new_layer(layer, nxt_layer.AUTHORING.CREATE,
nxt_layer.AUTHORING.BELOW)
self.new_layer_below_action.triggered.connect(new_below)
# Ref Above
self.ref_layer_above_action = NxtAction(text='Reference Layer Above',
parent=self)
self.ref_layer_above_action.setData(None)
self.ref_layer_above_action.setShortcutContext(widget_context)
def ref_above():
layer = self.ref_layer_above_action.data()
clear_action_data(self.actions())
if not layer:
layer = self.main_window.model.target_layer
if not layer:
return
reference = nxt_layer.AUTHORING.REFERENCE
self.main_window.model.new_layer(layer, reference,
nxt_layer.AUTHORING.ABOVE)
self.ref_layer_above_action.triggered.connect(ref_above)
# Ref Below
self.ref_layer_below_action = NxtAction(text='Reference Layer Below',
parent=self)
self.ref_layer_below_action.setData(None)
self.ref_layer_below_action.setShortcutContext(widget_context)
def ref_below():
layer = self.ref_layer_below_action.data()
clear_action_data(self.actions())
if not layer:
layer = self.main_window.model.target_layer
if not layer:
return
reference = nxt_layer.AUTHORING.REFERENCE
self.main_window.model.new_layer(layer, reference,
nxt_layer.AUTHORING.BELOW)
self.ref_layer_below_action.triggered.connect(ref_below)
# Layer Manager Table View
pref_key = user_dir.USER_PREF.LAYER_TABLE
text = 'Layer Manager as Table'
self.lay_manger_table_action = BoolUserPrefAction(text, pref_key,
parent=self)
self.lay_manger_table_action.setShortcut('V')
self.lay_manger_table_action.setShortcutContext(widget_context)
self.action_display_order = [self.save_layer_action,
self.save_layer_as_action,
self.save_all_layers_action,
self.open_source_action,
self.mute_layer_action,
self.solo_layer_action,
self.change_color_action,
self.change_alias_action,
self.new_layer_above_action,
self.new_layer_below_action,
self.ref_layer_above_action,
self.ref_layer_below_action,
self.remove_layer_action]
class NodeActions(NxtActionContainer):
def __init__(self, main_window):
super(NodeActions, self).__init__(main_window)
self.setObjectName('Nodes')
# add node
self.add_node_action = NxtAction(text='Add Node',
parent=self)
self.add_node_action.setShortcut('N')
self.add_node_action.setAutoRepeat(False)
self.add_node_action.setToolTip('Add Node')
self.add_node_action.setWhatsThis('Add a new node to the current '
'graph')
add_icon = QtGui.QIcon()
add_pixmap_on = QtGui.QPixmap(':icons/icons/add_node.png')
add_pixmap_hov = QtGui.QPixmap(':icons/icons/add_node_hover.png')
add_icon.addPixmap(add_pixmap_hov, QtGui.QIcon.Active, QtGui.QIcon.On)
add_icon.addPixmap(add_pixmap_on, QtGui.QIcon.Normal, QtGui.QIcon.On)
self.add_node_action.setIcon(add_icon)
def add_node():
self.main_window.view.add_node()
self.add_node_action.triggered.connect(add_node)
# delete nodes
def del_nodes(recursive=False):
self.main_window.model.delete_nodes(recursive=recursive)
self.delete_node_action = NxtAction(text='Delete Node', parent=self)
self.delete_node_action.setShortcut('Del')
self.delete_node_action.setWhatsThis('Delete the selected node(s).')
self.delete_node_action.triggered.connect(partial(del_nodes, False))
del_icon = QtGui.QIcon()
del_pixmap_on = QtGui.QPixmap(':icons/icons/delete_node.png')
del_pixmap_hov = QtGui.QPixmap(':icons/icons/delete_node_hover.png')
del_icon.addPixmap(del_pixmap_hov, QtGui.QIcon.Active, QtGui.QIcon.On)
del_icon.addPixmap(del_pixmap_on, QtGui.QIcon.Normal, QtGui.QIcon.On)
self.delete_node_action.setIcon(del_icon)
# Recursive delete node
self.recursive_delete_node_action = NxtAction(text='Recursive Delete '
'Node', parent=self)
self.recursive_delete_node_action.setWhatsThis('Delete selected node '
'and all of its '
'descendants '
'recursively.')
self.recursive_delete_node_action.setShortcut('Shift+Del')
self.recursive_delete_node_action.triggered.connect(partial(del_nodes,
True))
# duplicate node
self.duplicate_node_action = NxtAction(text='Duplicate Node',
parent=self)
self.duplicate_node_action.setShortcut('Ctrl+D')
self.duplicate_node_action.setAutoRepeat(False)
def dupe_nodes():
self.main_window.model.duplicate_nodes()
self.duplicate_node_action.triggered.connect(dupe_nodes)
dupe_icon = QtGui.QIcon()
dupe_pixmap_on = QtGui.QPixmap(':icons/icons/duplicate_node.png')
dupe_pixmap_hov = QtGui.QPixmap(':icons/icons/duplicate_node_hover.png')
dupe_icon.addPixmap(dupe_pixmap_hov, QtGui.QIcon.Active, QtGui.QIcon.On)
dupe_icon.addPixmap(dupe_pixmap_on, QtGui.QIcon.Normal, QtGui.QIcon.On)
self.duplicate_node_action.setIcon(dupe_icon)
# instance node
self.instance_node_action = NxtAction(text='Instance Node',
parent=self)
self.instance_node_action.setShortcut('Ctrl+I')
self.instance_node_action.setAutoRepeat(False)
def inst_nodes():
self.main_window.model.instance_nodes()
self.instance_node_action.triggered.connect(inst_nodes)
inst_icon = QtGui.QIcon()
inst_pixmap_on = QtGui.QPixmap(':icons/icons/instance_node.png')
inst_pixmap_hov = QtGui.QPixmap(':icons/icons/instance_node_hover.png')
inst_icon.addPixmap(inst_pixmap_hov, QtGui.QIcon.Active,
QtGui.QIcon.On)
inst_icon.addPixmap(inst_pixmap_on, QtGui.QIcon.Normal, QtGui.QIcon.On)
self.instance_node_action.setIcon(inst_icon)
# remove instance node
self.remove_instance_action = NxtAction(text='Remove Instance',
parent=self)
self.remove_instance_action.setShortcut('Ctrl+Shift+I')
self.remove_instance_action.setAutoRepeat(False)
self.remove_instance_action.setToolTip('Removes Instance')
self.remove_instance_action.setWhatsThis('Removes instance path from '
'node overloading weaker '
'layer instance opinions.')
rm_inst_icon = QtGui.QIcon()
rm_inst_pixmap_on = QtGui.QPixmap(':icons/icons/uninstance_node.png')
rm_inst_pixmap_hov = QtGui.QPixmap(':icons/icons/uninstance_node_hover.png')
rm_inst_icon.addPixmap(rm_inst_pixmap_hov, QtGui.QIcon.Active,
QtGui.QIcon.On)
rm_inst_icon.addPixmap(rm_inst_pixmap_on, QtGui.QIcon.Normal,
QtGui.QIcon.On)
self.remove_instance_action.setIcon(rm_inst_icon)
def rm_inst():
model = self.main_window.model
for node_path in model.selection:
self.main_window.model.set_node_instance(node_path, '',
model.target_layer)
self.remove_instance_action.triggered.connect(rm_inst)
# self.addSeparator()
# cut node
self.cut_node_action = NxtAction(text='Cut', parent=self)
self.cut_node_action.setShortcut('Ctrl+X')
def cut_nodes():
self.main_window.model.cut_nodes()
self.cut_node_action.triggered.connect(cut_nodes)
cut_icon = QtGui.QIcon()
cut_pixmap_on = QtGui.QPixmap(':icons/icons/cut_node.png')
cut_pixmap_hov = QtGui.QPixmap(':icons/icons/cut_node_hover.png')
cut_icon.addPixmap(cut_pixmap_hov, QtGui.QIcon.Active, QtGui.QIcon.On)
cut_icon.addPixmap(cut_pixmap_on, QtGui.QIcon.Normal, QtGui.QIcon.On)
self.cut_node_action.setIcon(cut_icon)
# copy node
self.copy_node_action = NxtAction(text='Copy', parent=self)
self.copy_node_action.setShortcut('Ctrl+C')
self.copy_node_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
def copy_nodes():
self.main_window.model.copy_nodes()
self.copy_node_action.triggered.connect(copy_nodes)
copy_icon = QtGui.QIcon()
copy_pixmap_on = QtGui.QPixmap(':icons/icons/copy_node.png')
copy_pixmap_hov = QtGui.QPixmap(':icons/icons/copy_node_hover.png')
copy_icon.addPixmap(copy_pixmap_hov, QtGui.QIcon.Active, QtGui.QIcon.On)
copy_icon.addPixmap(copy_pixmap_on, QtGui.QIcon.Normal, QtGui.QIcon.On)
self.copy_node_action.setIcon(copy_icon)
# paste node
self.paste_node_action = NxtAction(text='Paste', parent=self)
self.paste_node_action.setShortcut('Ctrl+V')
def paste_nodes():
self.main_window.view.paste_nodes()
self.paste_node_action.triggered.connect(paste_nodes)
paste_icon = QtGui.QIcon()
paste_pixmap_on = QtGui.QPixmap(':icons/icons/paste_node.png')
paste_pixmap_hov = QtGui.QPixmap(':icons/icons/paste_node_hover.png')
paste_icon.addPixmap(paste_pixmap_hov, QtGui.QIcon.Active,
QtGui.QIcon.On)
paste_icon.addPixmap(paste_pixmap_on, QtGui.QIcon.Normal,
QtGui.QIcon.On)
self.paste_node_action.setIcon(paste_icon)
# self.addSeparator()
# localize node
self.localize_node_action = NxtAction(text='Localize Node',
parent=self)
def localize_nodes():
self.main_window.model.localize_nodes()
self.localize_node_action.triggered.connect(localize_nodes)
localize_icon = QtGui.QIcon()
localize_pixmap_on = QtGui.QPixmap(':icons/icons/localize_node.png')
localize_pixmap_hov = QtGui.QPixmap(':icons/icons/localize_node_hover.png')
localize_icon.addPixmap(localize_pixmap_hov, QtGui.QIcon.Active,
QtGui.QIcon.On)
localize_icon.addPixmap(localize_pixmap_on, QtGui.QIcon.Normal,
QtGui.QIcon.On)
self.localize_node_action.setIcon(localize_icon)
# revert node
self.revert_node_action = NxtAction(text='Revert Node', parent=self)
def revert_nodes():
tgt = self.main_window.model.target_layer
self.main_window.model.revert_nodes(layer=tgt)
self.revert_node_action.triggered.connect(revert_nodes)
revert_icon = QtGui.QIcon()
revert_pixmap_on = QtGui.QPixmap(':icons/icons/clean_node.png')
revert_pixmap_hov = QtGui.QPixmap(':icons/icons/clean_node_hover.png')
revert_icon.addPixmap(revert_pixmap_hov, QtGui.QIcon.Active,
QtGui.QIcon.On)
revert_icon.addPixmap(revert_pixmap_on, QtGui.QIcon.Normal,
QtGui.QIcon.On)
self.revert_node_action.setIcon(revert_icon)
# Select all
def select_all():
self.main_window.view.select_all()
self.select_all_action = NxtAction(text='Select All Nodes',
parent=self)
self.select_all_action.setShortcut('Ctrl+A')
self.select_all_action.triggered.connect(select_all)
self.select_all_action.setIconText('Select All')
# Rename node
def rename_node():
self.main_window.view.rename_node()
self.rename_node_action = NxtAction(text='Rename Node', parent=self)
self.rename_node_action.triggered.connect(rename_node)
# Disable node
def toggle_disable():
self.main_window.model.toggle_nodes_enabled()
self.disable_node_action = NxtAction(text='Enable/Disable Node',
parent=self)
self.disable_node_action.setShortcut('D')
self.disable_node_action.setAutoRepeat(False)
self.disable_node_action.triggered.connect(toggle_disable)
# Revert child order
def revert_child_order():
self.main_window.model.revert_child_order()
self.revert_child_order_action = NxtAction(text='Revert child order',
parent=self)
self.revert_child_order_action.triggered.connect(revert_child_order)
# move up in child order
def up_child_order():
model = self.main_window.model
if model:
model.reorder_child_nodes(model.get_selected_nodes(), -1)
self.up_child_order_action = NxtAction(text='Move up in child '
'order', parent=self)
self.up_child_order_action.triggered.connect(up_child_order)
self.up_child_order_action.setShortcut('Ctrl+Up')
# move down in child order
self.down_child_order_action = NxtAction(text='Move down in child '
'order', parent=self)
def down_child_order():
model = self.main_window.model
if model:
model.reorder_child_nodes(model.get_selected_nodes(), 1)
self.down_child_order_action.triggered.connect(down_child_order)
self.down_child_order_action.setShortcut('Ctrl+Down')
# Parent node action
def parent_nodes():
sel_node_paths = self.main_window.model.get_selected_nodes()
if len(sel_node_paths) > 1:
node_paths = sel_node_paths[:-1]
parent_path = sel_node_paths[-1]
else:
logger.info("Not enough nodes selected for parent command.")
return
self.main_window.model.parent_nodes(node_paths=node_paths,
parent_path=parent_path)
self.parent_nodes_action = NxtAction(text='Parent', parent=self)
self.parent_nodes_action.setShortcut('P')
self.parent_nodes_action.setAutoRepeat(False)
self.parent_nodes_action.triggered.connect(parent_nodes)
self.parent_nodes_action.setWhatsThis('Parent node(s).')
# Un-Parent node action
def unparent_nodes():
model = self.main_window.model
model.parent_nodes(node_paths=model.get_selected_nodes(),
parent_path=nxt_path.WORLD)
self.unparent_nodes_action = NxtAction(text='Un-Parent', parent=self)
self.unparent_nodes_action.setShortcut('Shift+P')
self.unparent_nodes_action.setAutoRepeat(False)
self.unparent_nodes_action.triggered.connect(unparent_nodes)
self.unparent_nodes_action.setWhatsThis('Un-parent node(s).')
# Toggle Collapse
def toggle_collapse(recursive):
model = self.main_window.model
model.toggle_node_collapse(model.get_selected_nodes(),
recursive_down=recursive)
self.toggle_collapse_action = NxtAction(text='Toggle Node Collapse',
parent=self)
self.toggle_collapse_action.setShortcut('Alt+C')
self.toggle_collapse_action.setAutoRepeat(False)
self.toggle_collapse_action.setWhatsThis('Toggle the collapse state '
'for the selected node(s).')
self.toggle_collapse_action.triggered.connect(partial(toggle_collapse,
False))
self.toggle_recursive_collapse_action = NxtAction(text='Recursive '
'Toggle Node '
'Collapse',
parent=self)
self.toggle_recursive_collapse_action.setShortcut('Shift+Alt+C')
self.toggle_recursive_collapse_action.setAutoRepeat(False)
self.toggle_recursive_collapse_action.setWhatsThis('Recursively toggle '
'the collapse '
'state for the '
'selected node(s).')
func = partial(toggle_collapse, True)
self.toggle_recursive_collapse_action.triggered.connect(func)
self.action_display_order = [self.select_all_action,
self.add_node_action,
self.delete_node_action,
self.recursive_delete_node_action,
self.cut_node_action,
self.copy_node_action,
self.paste_node_action,
self.duplicate_node_action,
self.instance_node_action,
self.remove_instance_action,
self.localize_node_action,
self.revert_node_action,
self.disable_node_action,
self.parent_nodes_action,
self.unparent_nodes_action,
self.up_child_order_action,
self.down_child_order_action,
self.toggle_collapse_action,
self.toggle_recursive_collapse_action]
class PropertyEditorActions(NxtActionContainer):
def __init__(self, main_window):
super(PropertyEditorActions, self).__init__(main_window)
self.setObjectName('Property Editor')
context = QtCore.Qt.WidgetWithChildrenShortcut
# localize instance path action
self.localize_inst_path_action = NxtAction(text='Localize '
'Instance Path',
parent=self)
self.localize_inst_path_action.setShortcutContext(context)
self.localize_inst_path_action.setAutoRepeat(False)
# revert instance path action
self.revert_inst_path_action = NxtAction(text='Revert Instance Path',
parent=self)
self.revert_inst_path_action.setShortcutContext(context)