Skip to content

Commit

Permalink
Implement multiselect in menu and canvas tool
Browse files Browse the repository at this point in the history
  • Loading branch information
ObaraEmmanuel committed Aug 26, 2023
1 parent ae62678 commit e9ed6ae
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 57 deletions.
2 changes: 1 addition & 1 deletion studio/feature/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ def render_groups(self):

def render_extern_groups(self):
for group in self._extern_groups:
if all(group.supports(w) for w in self.studio.widgets):
if self.studio.selection and all(group.supports(w) for w in self.studio.selection):
self.add_selector(group.selector)
else:
self.remove_selector(group.selector)
Expand Down
4 changes: 3 additions & 1 deletion studio/feature/design.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ def _attach(self, obj):
self._shortcut_mgr.bind_widget(obj)

def show_menu(self, event, obj=None):
if not self._selected:
if obj and obj not in self._selected:
self.studio.selection.set(obj)
MenuUtils.popup(event, self._context_menu)

Expand Down Expand Up @@ -644,6 +644,8 @@ def restore(self, widgets, restore_points, containers):
for container, widget, restore_point in zip(containers, widgets, restore_points):
container.restore_widget(widget, restore_point)
self._replace_all(widget)
if self.root_obj in widgets:
self._show_empty(False)
self.studio.on_restore(widgets)

def _replace_all(self, widget):
Expand Down
4 changes: 2 additions & 2 deletions studio/feature/stylepane.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ def __init__(self, master, pane, **cnf):
self._prev_classes = set()

def get_definition(self):
if all(isinstance(widget, PseudoWidget) for widget in self.widgets):
if self.widgets and all(isinstance(widget, PseudoWidget) for widget in self.widgets):
return get_combined_properties(self.widgets)
return {}

Expand Down Expand Up @@ -375,7 +375,7 @@ def _update_index(self):
self._index.set(self.widgets[0].grid_info()["column"])

def on_widgets_change(self):
if all(self.is_grid(widget) for widget in self.widgets):
if self.widgets and all(self.is_grid(widget) for widget in self.widgets):
super().on_widgets_change()
self._index._editor.set_def(self._get_index_def())
self._update_index()
Expand Down
1 change: 1 addition & 0 deletions studio/lib/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class Canvas(PseudoWidget, tk.Canvas):
group = Groups.container
icon = "paint"
impl = tk.Canvas
allow_direct_move = False

def __init__(self, master, id_):
super().__init__(master)
Expand Down
6 changes: 5 additions & 1 deletion studio/lib/pseudo.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class PseudoWidget:
icon = "play"
impl = None
is_toplevel = False
allow_direct_move = True
# special handlers (intercepts) for attributes that need additional processing
# to interface with the studio easily
_intercepts = {
Expand Down Expand Up @@ -137,7 +138,9 @@ def setup_widget(self):

self.bind("<ButtonPress-1>", self._on_press, add='+')
self.bind("<ButtonRelease>", self._on_release, add='+')
self.bind("<Motion>", self._on_drag, add='+')

if self.allow_direct_move:
self.bind("<Motion>", self._on_drag, add='+')

self._active = False

Expand Down Expand Up @@ -354,6 +357,7 @@ def __repr__(self):

class Container(PseudoWidget):
LAYOUTS = layouts.layouts
allow_direct_move = False

def setup_widget(self):
self.parent = self.designer = self._get_designer()
Expand Down
17 changes: 6 additions & 11 deletions studio/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def __init__(self, master=None, **cnf):
self.style_pane = self.get_feature(StylePane)

# initialize tools with everything ready
# self.tool_manager.initialize()
self.tool_manager.initialize()

self._ignore_tab_status = False
self._startup()
Expand Down Expand Up @@ -634,8 +634,7 @@ def add(self, widget, parent=None):
for feature in self.features:
feature.on_widget_add(widget, parent)

# TODO refactor tools
# self.tool_manager.on_widget_add(widget, parent)
self.tool_manager.on_widget_add(widget, parent)

def widgets_modified(self, widgets, source=None):
for feature in self.features:
Expand All @@ -644,15 +643,13 @@ def widgets_modified(self, widgets, source=None):
if self.designer and self.designer != source:
self.designer.on_widgets_change(widgets)

# TODO refactor tools
# self.tool_manager.on_widget_change(widgets)
self.tool_manager.on_widgets_change(widgets)

def widgets_layout_changed(self, widgets):
for feature in self.features:
feature.on_widgets_layout_change(widgets)

# TODO refactor tools
# self.tool_manager.on_widget_layout_change(widgets)
self.tool_manager.on_widgets_layout_change(widgets)

def make_clipboard(self, widgets):
bounds = geometry.overall_bounds([w.get_bounds() for w in widgets])
Expand Down Expand Up @@ -684,8 +681,7 @@ def delete(self, widgets=None, source=None):
for feature in self.features:
feature.on_widgets_delete(widgets)

# TODO refactor tools
# self.tool_manager.on_widget_delete(widgets)
self.tool_manager.on_widgets_delete(widgets)

def cut(self, widgets=None, source=None):
if not self.designer:
Expand All @@ -704,8 +700,7 @@ def cut(self, widgets=None, source=None):
for feature in self.features:
feature.on_widgets_delete(widgets, True)

# TODO refactor tools
# self.tool_manager.on_widget_delete(widgets)
self.tool_manager.on_widgets_delete(widgets)

def duplicate(self):
if self.designer and self.selection:
Expand Down
19 changes: 8 additions & 11 deletions studio/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

TOOLS = (
MenuTool,
CanvasTool
CanvasTool,
)


Expand Down Expand Up @@ -52,7 +52,7 @@ def get_tool_menu(self, hide_unsupported=True):
else:
template = template[0]
templates += (
manipulator(partial(tool.supports, self.studio.selected), template),
manipulator(partial(tool.supports, self.studio.selection), template),
)
# prepend a separator for context menus
if templates and hide_unsupported:
Expand Down Expand Up @@ -86,11 +86,8 @@ def dispatch(self, action, *args):
for tool in self._tools:
getattr(tool, action)(*args)

def on_select(self, widget):
self.dispatch("on_select", widget)

def on_widget_delete(self, widget):
self.dispatch("on_widget_delete", widget)
def on_widgets_delete(self, widgets):
self.dispatch("on_widgets_delete", widgets)

def on_app_close(self):
for tool in self._tools:
Expand All @@ -105,11 +102,11 @@ def on_session_clear(self):
def on_widget_add(self, widget, parent):
self.dispatch("on_widget_add", widget, parent)

def on_widget_change(self, old_widget, new_widget):
self.dispatch("on_widget_change", old_widget, new_widget)
def on_widgets_change(self, widgets):
self.dispatch("on_widgets_change", widgets)

def on_widget_layout_change(self, widget):
self.dispatch("on_widget_layout_change", widget)
def on_widgets_layout_change(self, widgets):
self.dispatch("on_widgets_layout_change", widgets)

def on_context_switch(self):
self.dispatch("on_context_switch")
Expand Down
12 changes: 5 additions & 7 deletions studio/tools/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,16 @@ def get_menu(self, studio):
# default behaviour is to return an empty template
return ()

def supports(self, widget):
def supports(self, widgets=None):
"""
Checks whether the tool can work on a given widget. This information is
useful for the studio to allow it render dropdown menus correctly
:param widget: A tk Widget to be checked
:param widgets: A list of tk Widgets to be checked, if None, current studio selection is used
:return: True if tool can work on the widget otherwise false
"""

def on_select(self, widget):
pass

def on_widget_delete(self, widget):
def on_widgets_delete(self, widgets):
pass

def on_app_close(self):
Expand All @@ -47,10 +45,10 @@ def on_session_clear(self):
def on_widget_add(self, widget, parent):
pass

def on_widget_change(self, old_widget, new_widget):
def on_widgets_change(self, widgets):
pass

def on_widget_layout_change(self, widget):
def on_widgets_layout_change(self, widgets):
pass

def on_context_switch(self):
Expand Down
36 changes: 22 additions & 14 deletions studio/tools/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,8 +580,8 @@ def cv_items(self):
# selected canvas items
return self.tool.selected_items

def supports_widget(self, widget):
return isinstance(widget, Canvas)
def supports_widgets(self):
return len(self.widgets) == 1 and isinstance(self.widgets[0], Canvas)

def can_optimize(self):
# probably needs a rethink if we consider definition overrides
Expand All @@ -604,10 +604,10 @@ def compute_prop_keys(self):
# id cannot be set for multi-selected items
self.prop_keys.remove('id')

def on_widget_change(self, widget):
def on_widgets_change(self):
self._prev_prop_keys = self.prop_keys
self.compute_prop_keys()
super(CanvasStyleGroup, self).on_widget_change(widget)
super(CanvasStyleGroup, self).on_widgets_change()
self.style_pane.remove_loading()

def _get_prop(self, prop, widget):
Expand All @@ -621,13 +621,13 @@ def _get_key(self, widget, prop):
def _get_action_data(self, widget, prop):
return {item: {prop: item.cget(prop)} for item in self.cv_items}

def _apply_action(self, prop, value, widget, data):
def _apply_action(self, prop, value, widgets, data):
for item in data:
item.configure(data[item])
if item._controller:
item._controller.update()
if self.tool.canvas == widget:
self.on_widget_change(widget)
if self.tool.canvas == widgets[0]:
self.on_widgets_change()
self.tool.on_items_modified(data.keys())

def _set_prop(self, prop, value, widget):
Expand Down Expand Up @@ -875,6 +875,8 @@ def __init__(self, studio, manager):
),
), self.studio, self.studio.style)

self.studio.bind("<<SelectionChanged>>", self.on_select, "+")

@property
def _ids(self):
return [item.name for item_set in self.items for item in item_set._cv_items]
Expand Down Expand Up @@ -1154,12 +1156,12 @@ def remove_controller(self, item):

def selection_changed(self):
# called when canvas item selection changes
self.style_group.on_widget_change(self.canvas)
self.style_group.on_widgets_change()

def _update_selection(self, canvas):
# update selections from the canvas tree
if canvas != self.canvas:
self.studio.select(canvas)
self.studio.selection.set(canvas)
# call to studio should cause canvas to be selected
assert self.canvas == canvas
selected = set(self.selected_items)
Expand Down Expand Up @@ -1226,7 +1228,12 @@ def select_item(self, item, multi=False):

self.selection_changed()

def on_select(self, widget):
def on_select(self, _):
if len(self.studio.selection) == 1:
widget = self.studio.selection[0]
else:
widget = None

if self.canvas == widget:
return
if self.canvas is not None:
Expand Down Expand Up @@ -1271,10 +1278,11 @@ def on_items_modified(self, items):
for item in items:
item.node.widget_modified(item)

def on_widget_delete(self, widget):
if isinstance(widget, Canvas):
if widget in self.items:
self.items.remove(widget)
def on_widgets_delete(self, widgets):
for widget in widgets:
if isinstance(widget, Canvas):
if widget in self.items:
self.items.remove(widget)

def propagate_move(self, delta_x, delta_y, source=None):
for item in self.selected_items:
Expand Down
19 changes: 10 additions & 9 deletions studio/tools/menus.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,21 +387,22 @@ def restore(self, widget):
widget.configure(menu=self._deleted.get(widget))
self._deleted.pop(widget)

def supports(self, widget):
if widget is None:
return widget
return 'menu' in widget.keys()
def supports(self, widgets=None):
widgets = widgets or self.studio.selection
if not widgets:
return False
return all('menu' in w.keys() for w in widgets)

def get_menu(self, studio):
icon = get_icon_image
return (
('command', 'Edit', icon('edit', 14, 14), lambda: self.edit(studio.selected), {}),
('command', 'Edit', icon('edit', 14, 14), lambda: self.edit(studio.selection[0]), {}),
EnableIf(
lambda: studio.selected and studio.selected['menu'] != '',
('command', 'Remove', icon('delete', 14, 14), lambda: self.remove(studio.selected), {})),
lambda: studio.selection and studio.selection[0]['menu'] != '',
('command', 'Remove', icon('delete', 14, 14), lambda: self.remove(studio.selection[0]), {})),
EnableIf(
lambda: studio.selected and studio.selected in self._deleted,
('command', 'Restore', icon('undo', 14, 14), lambda: self.restore(studio.selected), {})),
lambda: studio.selection and studio.selection[0] in self._deleted,
('command', 'Restore', icon('undo', 14, 14), lambda: self.restore(studio.selection[0]), {})),
EnableIf(
lambda: MenuEditor._tool_map,
('command', 'Close all editors', icon('close', 14, 14), self.close_editors, {}))
Expand Down

0 comments on commit e9ed6ae

Please sign in to comment.