diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 62cdb2d7c..8b574fc8d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -82,13 +82,31 @@ ObjectList::ObjectList(wxWindow* parent) : init_icons(); // describe control behavior - Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxEvent& event) { + Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent& event) { #ifndef __APPLE__ // On Windows and Linux, forces a kill focus emulation on the object manipulator fields because this event handler is called // before the kill focus event handler on the object manipulator when changing selection in the list, invalidating the object // manipulator cache with the following call to selection_changed() wxGetApp().obj_manipul()->emulate_kill_focus(); #endif // __APPLE__ + + /* For multiple selection with pressed SHIFT, + * event.GetItem() returns value of a first item in selection list + * instead of real last clicked item. + * So, let check last selected item in such strange way + */ + if (wxGetKeyState(WXK_SHIFT)) + { + wxDataViewItemArray sels; + GetSelections(sels); + if (sels.front() == m_last_selected_item) + m_last_selected_item = sels.back(); + else + m_last_selected_item = event.GetItem(); + } + else + m_last_selected_item = event.GetItem(); + selection_changed(); #ifndef __WXMSW__ set_tooltip_for_item(get_mouse_position_in_control()); @@ -509,7 +527,7 @@ void ObjectList::key_event(wxKeyEvent& event) ) { remove(); } - else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_SHIFT)) + else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/)) select_item_all_children(); else event.Skip(); @@ -1005,8 +1023,7 @@ wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu) void ObjectList::append_menu_items_osx(wxMenu* menu) { - append_menu_item(menu, wxID_ANY, _(L("Delete item")), "", - [this](wxCommandEvent&) { remove(); }, "", menu); + append_menu_item_delete(menu); append_menu_item(menu, wxID_ANY, _(L("Rename")), "", [this](wxCommandEvent&) { rename_item(); }, "", menu); @@ -1061,6 +1078,12 @@ void ObjectList::append_menu_item_change_extruder(wxMenu* menu) const } } +void ObjectList::append_menu_item_delete(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _(L("Delete")), "", + [this](wxCommandEvent&) { remove(); }, "", menu); +} + void ObjectList::create_object_popupmenu(wxMenu *menu) { #ifdef __WXOSX__ @@ -1118,6 +1141,9 @@ void ObjectList::create_part_popupmenu(wxMenu *menu) void ObjectList::create_instance_popupmenu(wxMenu*menu) { +#ifdef __WXOSX__ + append_menu_item_delete(menu); +#endif // __WXOSX__ m_menu_item_split_instances = append_menu_item_instance_to_object(menu); /* New behavior logic: @@ -1560,7 +1586,7 @@ void ObjectList::split() auto opt_keys = model_object->volumes[id]->config.keys(); if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) { select_item(m_objects_model->AddSettingsChild(vol_item)); - Collapse(vol_item); + /*Collapse*/Expand(vol_item); } } @@ -1754,7 +1780,7 @@ void ObjectList::add_object_to_list(size_t obj_idx) auto opt_keys = model_object->volumes[id]->config.keys(); if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { select_item(m_objects_model->AddSettingsChild(vol_item)); - Collapse(vol_item); + /*Collapse*/Expand(vol_item); } } Expand(item); @@ -1768,7 +1794,7 @@ void ObjectList::add_object_to_list(size_t obj_idx) auto opt_keys = model_object->config.keys(); if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { select_item(m_objects_model->AddSettingsChild(item)); - Collapse(item); + /*Collapse*/Expand(item); } #ifndef __WXOSX__ @@ -1948,6 +1974,8 @@ void ObjectList::update_selections() const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); wxDataViewItemArray sels; + m_selection_mode = smInstance; + // We doesn't update selection if SettingsItem for the current object/part is selected if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) { @@ -1961,24 +1989,6 @@ void ObjectList::update_selections() return; } } -// if (selection.is_single_full_object() && selection.get_instance_idx() != -1) -// { -// sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); -// } -// else if (selection.is_single_volume() || selection.is_modifier() || -// selection.is_multiple_volume() || selection.is_multiple_full_object()) -// { -// for (auto idx : selection.get_volume_idxs()) { -// const auto gl_vol = selection.get_volume(idx); -// if (selection.is_multiple_full_object()) -// sels.Add(m_objects_model->GetItemById(gl_vol->object_idx())); -// else if (gl_vol->volume_idx() >= 0) -// // Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids -// // are not associated with ModelVolumes, but they are temporarily generated by the backend -// // (for example, SLA supports or SLA pad). -// sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); -// } -// } else if (selection.is_single_full_object() || selection.is_multiple_full_object()) { const Selection::ObjectIdxsToInstanceIdxsMap& objects_content = selection.get_content(); @@ -1993,8 +2003,10 @@ void ObjectList::update_selections() bool root_is_selected = false; for (const auto& item:current_sels) - if (item == m_objects_model->GetParent(frst_inst_item)) { + if (item == m_objects_model->GetParent(frst_inst_item) || + item == m_objects_model->GetTopParent(frst_inst_item)) { root_is_selected = true; + sels.Add(item); break; } if (root_is_selected) @@ -2016,6 +2028,7 @@ void ObjectList::update_selections() // (for example, SLA supports or SLA pad). sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); } + m_selection_mode = smVolume; } else if (selection.is_single_full_instance() || selection.is_multiple_full_instance()) { @@ -2025,7 +2038,7 @@ void ObjectList::update_selections() } else if (selection.is_mixed()) { - auto& objects_content_list = selection.get_content(); + const Selection::ObjectIdxsToInstanceIdxsMap& objects_content_list = selection.get_content(); for (auto idx : selection.get_volume_idxs()) { const auto gl_vol = selection.get_volume(idx); @@ -2058,6 +2071,9 @@ void ObjectList::update_selections() sels.Add(m_objects_model->GetItemByVolumeId(glv_obj_idx, glv_vol_idx)); } } + + if (sels.size() == 0) + m_selection_mode = smUndef; select_items(sels); @@ -2093,17 +2109,19 @@ void ObjectList::update_selections_on_canvas() auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, int instance_idx, bool as_single_selection) { - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { - selection.add_object(m_objects_model->GetIdByItem(item), as_single_selection); + const ItemType& type = m_objects_model->GetItemType(item); + if ( type == itInstanceRoot || m_objects_model->GetParent(item) == wxDataViewItem(0) ) { + wxDataViewItem obj_item = type == itInstanceRoot ? m_objects_model->GetParent(item) : item; + selection.add_object(m_objects_model->GetIdByItem(obj_item), as_single_selection); return; } - if (m_objects_model->GetItemType(item) == itVolume) { + if (type == itVolume) { const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); const int vol_idx = m_objects_model->GetVolumeIdByItem(item); selection.add_volume(obj_idx, vol_idx, std::max(instance_idx, 0), as_single_selection); } - else if (m_objects_model->GetItemType(item) == itInstance) { + else if (type == itInstance) { const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); const int inst_idx = m_objects_model->GetInstanceIdByItem(item); selection.add_instance(obj_idx, inst_idx, as_single_selection); @@ -2116,7 +2134,7 @@ void ObjectList::update_selections_on_canvas() if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot)) - add_to_selection(m_objects_model->GetParent(item), selection, -1, true); + add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, true); else add_to_selection(item, selection, instance_idx, true); @@ -2172,22 +2190,101 @@ void ObjectList::select_item_all_children() if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject) { for (int i = 0; i < m_objects->size(); i++) sels.Add(m_objects_model->GetItemById(i)); + m_selection_mode = smInstance; } else { const auto item = GetSelection(); // Some volume(instance) is selected => select all volumes(instances) inside the current object - if (m_objects_model->GetItemType(item) & (itVolume | itInstance)) { + if (m_objects_model->GetItemType(item) & (itVolume | itInstance)) m_objects_model->GetChildren(m_objects_model->GetParent(item), sels); - } + + m_selection_mode = m_objects_model->GetItemType(item)&itVolume ? smVolume : smInstance; } SetSelections(sels); selection_changed(); } +// update selection mode for non-multiple selection +void ObjectList::update_selection_mode() +{ + // All items are unselected + if (!GetSelection()) + { + m_last_selected_item = wxDataViewItem(0); + m_selection_mode = smUndef; + return; + } + + const ItemType type = m_objects_model->GetItemType(GetSelection()); + m_selection_mode = type&itSettings ? smUndef : + type&itVolume ? smVolume : smInstance; +} + +// check last selected item. If is it possible to select it +bool ObjectList::check_last_selection(wxString& msg_str) +{ + if (!m_last_selected_item) + return true; + + const bool is_shift_pressed = wxGetKeyState(WXK_SHIFT); + + /* We can't mix Parts and Objects/Instances. + * So, show information about it + */ + const ItemType type = m_objects_model->GetItemType(m_last_selected_item); + + // check a case of a selection of the Parts from different Objects + bool impossible_multipart_selection = false; + if (type & itVolume && m_selection_mode == smVolume) + { + wxDataViewItemArray sels; + GetSelections(sels); + for (const auto& sel: sels) + if (sel != m_last_selected_item && + m_objects_model->GetParent(sel) != m_objects_model->GetParent(m_last_selected_item)) + { + impossible_multipart_selection = true; + break; + } + } + + if (impossible_multipart_selection || + type & itSettings || + type & itVolume && m_selection_mode == smInstance || + !(type & itVolume) && m_selection_mode == smVolume) + { + // Inform user why selection isn't complited + const wxString item_type = m_selection_mode == smInstance ? _(L("Object or Instance")) : _(L("Part")); + + msg_str = wxString::Format( _(L("Unsupported selection")) + "\n\n" + + _(L("You started your selection with %s Item.")) + "\n" + + _(L("In this mode you can select only other %s Items%s")), + item_type, item_type, + m_selection_mode == smInstance ? "." : + " " + _(L("of a current Object"))); + + // Unselect last selected item, if selection is without SHIFT + if (!is_shift_pressed) { + Unselect(m_last_selected_item); + show_info(this, msg_str, _(L("Info"))); + } + + return is_shift_pressed; + } + + return true; +} + void ObjectList::fix_multiselection_conflicts() { - if (GetSelectedItemsCount() <= 1) + if (GetSelectedItemsCount() <= 1) { + update_selection_mode(); + return; + } + + wxString msg_string; + if (!check_last_selection(msg_string)) return; m_prevent_list_events = true; @@ -2195,27 +2292,59 @@ void ObjectList::fix_multiselection_conflicts() wxDataViewItemArray sels; GetSelections(sels); - for (const auto item : sels) { - if (!IsSelected(item)) // if this item is unselected now (from previous actions) - continue; + if (m_selection_mode == smVolume) + { + // identify correct parent of the initial selected item + const wxDataViewItem& parent = m_objects_model->GetParent(m_last_selected_item == sels.front() ? sels.back() : sels.front()); - if (m_objects_model->GetItemType(item) & itSettings) { - Unselect(item); - continue; - } + sels.clear(); + wxDataViewItemArray children; // selected volumes from current parent + m_objects_model->GetChildren(parent, children); - const wxDataViewItem& parent = m_objects_model->GetParent(item); - if (parent != wxDataViewItem(0) && IsSelected(parent)) - Unselect(parent); - else + for (const auto child : children) + if (IsSelected(child) && m_objects_model->GetItemType(child)&itVolume) + sels.Add(child); + + // If some part is selected, unselect all items except of selected parts of the current object + UnselectAll(); + SetSelections(sels); + } + else + { + for (const auto item : sels) { - wxDataViewItemArray unsels; - m_objects_model->GetAllChildren(item, unsels); - for (const auto unsel_item : unsels) - Unselect(unsel_item); + if (!IsSelected(item)) // if this item is unselected now (from previous actions) + continue; + + if (m_objects_model->GetItemType(item) & itSettings) { + Unselect(item); + continue; + } + + const wxDataViewItem& parent = m_objects_model->GetParent(item); + if (parent != wxDataViewItem(0) && IsSelected(parent)) + Unselect(parent); + else + { + wxDataViewItemArray unsels; + m_objects_model->GetAllChildren(item, unsels); + for (const auto unsel_item : unsels) + Unselect(unsel_item); + } + + if (m_objects_model->GetItemType(item) & itVolume) + Unselect(item); + + m_selection_mode = smInstance; } } + if (!msg_string.IsEmpty()) + show_info(this, msg_string, _(L("Info"))); + + if (!IsSelected(m_last_selected_item)) + m_last_selected_item = wxDataViewItem(0); + m_prevent_list_events = false; } @@ -2535,8 +2664,7 @@ void ObjectList::show_multi_selection_menu() wxMenu* menu = new wxMenu(); #ifdef __WXOSX__ - append_menu_item(menu, wxID_ANY, _(L("Delete items")), "", - [this](wxCommandEvent&) { remove(); }, "", menu); + append_menu_item_delete(menu); #endif //__WXOSX__ if (extruders_count() > 1) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 48d87db3c..2f465a4ab 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -60,6 +60,12 @@ struct ItemForDelete class ObjectList : public wxDataViewCtrl { + enum SELECTION_MODE + { + smUndef, + smVolume, + smInstance + } m_selection_mode {smUndef}; struct dragged_item_data { @@ -135,6 +141,7 @@ class ObjectList : public wxDataViewCtrl bool m_part_settings_changed = false; int m_selected_row = 0; + wxDataViewItem m_last_selected_item {nullptr}; #if 0 FreqSettingsBundle m_freq_settings_fff; @@ -188,6 +195,7 @@ public: void append_menu_item_fix_through_netfabb(wxMenu* menu); void append_menu_item_export_stl(wxMenu* menu) const ; void append_menu_item_change_extruder(wxMenu* menu) const; + void append_menu_item_delete(wxMenu* menu); void create_object_popupmenu(wxMenu *menu); void create_sla_object_popupmenu(wxMenu*menu); void create_part_popupmenu(wxMenu*menu); @@ -258,6 +266,8 @@ public: void select_items(const wxDataViewItemArray& sels); void select_all(); void select_item_all_children(); + void update_selection_mode(); + bool check_last_selection(wxString& msg_str); // correct current selections to avoid of the possible conflicts void fix_multiselection_conflicts();