diff options
Diffstat (limited to 'src/slic3r/GUI/wxExtensions.cpp')
-rw-r--r-- | src/slic3r/GUI/wxExtensions.cpp | 1662 |
1 files changed, 1662 insertions, 0 deletions
diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp new file mode 100644 index 000000000..13730a497 --- /dev/null +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -0,0 +1,1662 @@ +#include "wxExtensions.hpp" + +#include "GUI.hpp" +#include "../../libslic3r/Utils.hpp" +#include "BitmapCache.hpp" + +#include <wx/sizer.h> +#include <wx/statline.h> +#include <wx/dcclient.h> +#include <wx/numformatter.h> + +const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200; +const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200; +const unsigned int wxCheckListBoxComboPopup::DefaultItemHeight = 18; + +bool wxCheckListBoxComboPopup::Create(wxWindow* parent) +{ + return wxCheckListBox::Create(parent, wxID_HIGHEST + 1, wxPoint(0, 0)); +} + +wxWindow* wxCheckListBoxComboPopup::GetControl() +{ + return this; +} + +void wxCheckListBoxComboPopup::SetStringValue(const wxString& value) +{ + m_text = value; +} + +wxString wxCheckListBoxComboPopup::GetStringValue() const +{ + return m_text; +} + +wxSize wxCheckListBoxComboPopup::GetAdjustedSize(int minWidth, int prefHeight, int maxHeight) +{ + // matches owner wxComboCtrl's width + // and sets height dinamically in dependence of contained items count + + wxComboCtrl* cmb = GetComboCtrl(); + if (cmb != nullptr) + { + wxSize size = GetComboCtrl()->GetSize(); + + unsigned int count = GetCount(); + if (count > 0) + size.SetHeight(count * DefaultItemHeight); + else + size.SetHeight(DefaultHeight); + + return size; + } + else + return wxSize(DefaultWidth, DefaultHeight); +} + +void wxCheckListBoxComboPopup::OnKeyEvent(wxKeyEvent& evt) +{ + // filters out all the keys which are not working properly + switch (evt.GetKeyCode()) + { + case WXK_LEFT: + case WXK_UP: + case WXK_RIGHT: + case WXK_DOWN: + case WXK_PAGEUP: + case WXK_PAGEDOWN: + case WXK_END: + case WXK_HOME: + case WXK_NUMPAD_LEFT: + case WXK_NUMPAD_UP: + case WXK_NUMPAD_RIGHT: + case WXK_NUMPAD_DOWN: + case WXK_NUMPAD_PAGEUP: + case WXK_NUMPAD_PAGEDOWN: + case WXK_NUMPAD_END: + case WXK_NUMPAD_HOME: + { + break; + } + default: + { + evt.Skip(); + break; + } + } +} + +void wxCheckListBoxComboPopup::OnCheckListBox(wxCommandEvent& evt) +{ + // forwards the checklistbox event to the owner wxComboCtrl + + if (m_check_box_events_status == OnCheckListBoxFunction::FreeToProceed ) + { + wxComboCtrl* cmb = GetComboCtrl(); + if (cmb != nullptr) { + wxCommandEvent event(wxEVT_CHECKLISTBOX, cmb->GetId()); + event.SetEventObject(cmb); + cmb->ProcessWindowEvent(event); + } + } + + evt.Skip(); + + #ifndef _WIN32 // events are sent differently on OSX+Linux vs Win (more description in header file) + if ( m_check_box_events_status == OnCheckListBoxFunction::RefuseToProceed ) + // this happens if the event was resent by OnListBoxSelection - next call to OnListBoxSelection is due to user clicking the text, so the function should + // explicitly change the state on the checkbox + m_check_box_events_status = OnCheckListBoxFunction::WasRefusedLastTime; + else + // if the user clicked the checkbox square, this event was sent before OnListBoxSelection was called, so we don't want it to resend it + m_check_box_events_status = OnCheckListBoxFunction::RefuseToProceed; + #endif +} + +void wxCheckListBoxComboPopup::OnListBoxSelection(wxCommandEvent& evt) +{ + // transforms list box item selection event into checklistbox item toggle event + + int selId = GetSelection(); + if (selId != wxNOT_FOUND) + { + #ifndef _WIN32 + if (m_check_box_events_status == OnCheckListBoxFunction::RefuseToProceed) + #endif + Check((unsigned int)selId, !IsChecked((unsigned int)selId)); + + m_check_box_events_status = OnCheckListBoxFunction::FreeToProceed; // so the checkbox reacts to square-click the next time + + SetSelection(wxNOT_FOUND); + wxCommandEvent event(wxEVT_CHECKLISTBOX, GetId()); + event.SetInt(selId); + event.SetEventObject(this); + ProcessEvent(event); + } +} + + +// *** wxDataViewTreeCtrlComboPopup *** + +const unsigned int wxDataViewTreeCtrlComboPopup::DefaultWidth = 270; +const unsigned int wxDataViewTreeCtrlComboPopup::DefaultHeight = 200; +const unsigned int wxDataViewTreeCtrlComboPopup::DefaultItemHeight = 22; + +bool wxDataViewTreeCtrlComboPopup::Create(wxWindow* parent) +{ + return wxDataViewTreeCtrl::Create(parent, wxID_ANY/*HIGHEST + 1*/, wxPoint(0, 0), wxDefaultSize/*wxSize(270, -1)*/, wxDV_NO_HEADER); +} +/* +wxSize wxDataViewTreeCtrlComboPopup::GetAdjustedSize(int minWidth, int prefHeight, int maxHeight) +{ + // matches owner wxComboCtrl's width + // and sets height dinamically in dependence of contained items count + wxComboCtrl* cmb = GetComboCtrl(); + if (cmb != nullptr) + { + wxSize size = GetComboCtrl()->GetSize(); + if (m_cnt_open_items > 0) + size.SetHeight(m_cnt_open_items * DefaultItemHeight); + else + size.SetHeight(DefaultHeight); + + return size; + } + else + return wxSize(DefaultWidth, DefaultHeight); +} +*/ +void wxDataViewTreeCtrlComboPopup::OnKeyEvent(wxKeyEvent& evt) +{ + // filters out all the keys which are not working properly + if (evt.GetKeyCode() == WXK_UP) + { + return; + } + else if (evt.GetKeyCode() == WXK_DOWN) + { + return; + } + else + { + evt.Skip(); + return; + } +} + +void wxDataViewTreeCtrlComboPopup::OnDataViewTreeCtrlSelection(wxCommandEvent& evt) +{ + wxComboCtrl* cmb = GetComboCtrl(); + auto selected = GetItemText(GetSelection()); + cmb->SetText(selected); +} + +// ---------------------------------------------------------------------------- +// *** PrusaCollapsiblePane *** +// ---------------------------------------------------------------------------- +void PrusaCollapsiblePane::OnStateChange(const wxSize& sz) +{ +#ifdef __WXOSX__ + wxCollapsiblePane::OnStateChange(sz); +#else + SetSize(sz); + + if (this->HasFlag(wxCP_NO_TLW_RESIZE)) + { + // the user asked to explicitly handle the resizing itself... + return; + } + + auto top = GetParent(); //right_panel + if (!top) + return; + + wxSizer *sizer = top->GetSizer(); + if (!sizer) + return; + + const wxSize newBestSize = sizer->ComputeFittingClientSize(top); + top->SetMinClientSize(newBestSize); + + wxWindowUpdateLocker noUpdates_p(top->GetParent()); + // we shouldn't attempt to resize a maximized window, whatever happens + // if (!top->IsMaximized()) + // top->SetClientSize(newBestSize); + top->GetParent()->Layout(); + top->Refresh(); +#endif //__WXOSX__ +} + +// ---------------------------------------------------------------------------- +// *** PrusaCollapsiblePaneMSW *** used only #ifdef __WXMSW__ +// ---------------------------------------------------------------------------- +#ifdef __WXMSW__ +bool PrusaCollapsiblePaneMSW::Create(wxWindow *parent, wxWindowID id, const wxString& label, + const wxPoint& pos, const wxSize& size, long style, const wxValidator& val, const wxString& name) +{ + if (!wxControl::Create(parent, id, pos, size, style, val, name)) + return false; + m_pStaticLine = NULL; + m_strLabel = label; + + // sizer containing the expand button and possibly a static line + m_sz = new wxBoxSizer(wxHORIZONTAL); + + m_bmp_close.LoadFile(Slic3r::GUI::from_u8(Slic3r::var("disclosure_triangle_close.png")), wxBITMAP_TYPE_PNG); + m_bmp_open.LoadFile(Slic3r::GUI::from_u8(Slic3r::var("disclosure_triangle_open.png")), wxBITMAP_TYPE_PNG); + + m_pDisclosureTriangleButton = new wxButton(this, wxID_ANY, m_strLabel, wxPoint(0, 0), + wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + UpdateBtnBmp(); + m_pDisclosureTriangleButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) + { + if (event.GetEventObject() != m_pDisclosureTriangleButton) + { + event.Skip(); + return; + } + + Collapse(!IsCollapsed()); + + // this change was generated by the user - send the event + wxCollapsiblePaneEvent ev(this, GetId(), IsCollapsed()); + GetEventHandler()->ProcessEvent(ev); + }); + + m_sz->Add(m_pDisclosureTriangleButton, 0, wxLEFT | wxTOP | wxBOTTOM, GetBorder()); + + // do not set sz as our sizers since we handle the pane window without using sizers + m_pPane = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, + wxTAB_TRAVERSAL | wxNO_BORDER, wxT("wxCollapsiblePanePane")); + + wxColour& clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + m_pDisclosureTriangleButton->SetBackgroundColour(clr); + this->SetBackgroundColour(clr); + m_pPane->SetBackgroundColour(clr); + + // start as collapsed: + m_pPane->Hide(); + + return true; +} + +void PrusaCollapsiblePaneMSW::UpdateBtnBmp() +{ + if (IsCollapsed()) + m_pDisclosureTriangleButton->SetBitmap(m_bmp_close); + else{ + m_pDisclosureTriangleButton->SetBitmap(m_bmp_open); + // To updating button bitmap it's needed to lost focus on this button, so + // we set focus to mainframe + //GetParent()->GetParent()->GetParent()->SetFocus(); + //or to pane + GetPane()->SetFocus(); + } + Layout(); +} + +void PrusaCollapsiblePaneMSW::SetLabel(const wxString &label) +{ + m_strLabel = label; + m_pDisclosureTriangleButton->SetLabel(m_strLabel); + Layout(); +} + +bool PrusaCollapsiblePaneMSW::Layout() +{ + if (!m_pDisclosureTriangleButton || !m_pPane || !m_sz) + return false; // we need to complete the creation first! + + wxSize oursz(GetSize()); + + // move & resize the button and the static line + m_sz->SetDimension(0, 0, oursz.GetWidth(), m_sz->GetMinSize().GetHeight()); + m_sz->Layout(); + + if (IsExpanded()) + { + // move & resize the container window + int yoffset = m_sz->GetSize().GetHeight() + GetBorder(); + m_pPane->SetSize(0, yoffset, + oursz.x, oursz.y - yoffset); + + // this is very important to make the pane window layout show correctly + m_pPane->Layout(); + } + + return true; +} + +void PrusaCollapsiblePaneMSW::Collapse(bool collapse) +{ + // optimization + if (IsCollapsed() == collapse) + return; + + InvalidateBestSize(); + + // update our state + m_pPane->Show(!collapse); + + // update button bitmap + UpdateBtnBmp(); + + OnStateChange(GetBestSize()); +} +#endif //__WXMSW__ + +// ***************************************************************************** +// ---------------------------------------------------------------------------- +// PrusaObjectDataViewModelNode +// ---------------------------------------------------------------------------- + +void PrusaObjectDataViewModelNode::set_object_action_icon() { + m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("add_object.png")), wxBITMAP_TYPE_PNG); +} +void PrusaObjectDataViewModelNode::set_part_action_icon() { + m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG); +} + +Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr; +bool PrusaObjectDataViewModelNode::update_settings_digest(const std::vector<std::string>& categories) +{ + if (m_type != "settings" || m_opt_categories == categories) + return false; + + m_opt_categories = categories; + m_name = wxEmptyString; + m_icon = m_empty_icon; + + auto categories_icon = Slic3r::GUI::get_category_icon(); + + for (auto& cat : m_opt_categories) + m_name += cat + "; "; + + wxBitmap *bmp = m_bitmap_cache->find(m_name.ToStdString()); + if (bmp == nullptr) { + std::vector<wxBitmap> bmps; + for (auto& cat : m_opt_categories) + bmps.emplace_back(categories_icon.find(cat) == categories_icon.end() ? + wxNullBitmap : categories_icon.at(cat)); + bmp = m_bitmap_cache->insert(m_name.ToStdString(), bmps); + } + + m_bmp = *bmp; + + return true; +} + +// ***************************************************************************** +// ---------------------------------------------------------------------------- +// PrusaObjectDataViewModel +// ---------------------------------------------------------------------------- + +PrusaObjectDataViewModel::PrusaObjectDataViewModel() +{ + m_bitmap_cache = new Slic3r::GUI::BitmapCache; +} + +PrusaObjectDataViewModel::~PrusaObjectDataViewModel() +{ + for (auto object : m_objects) + delete object; + delete m_bitmap_cache; + m_bitmap_cache = nullptr; +} + +wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name) +{ + auto root = new PrusaObjectDataViewModelNode(name); + m_objects.push_back(root); + // notify control + wxDataViewItem child((void*)root); + wxDataViewItem parent((void*)NULL); + ItemAdded(parent, child); + return child; +} + +wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name, const int instances_count/*, int scale*/) +{ + auto root = new PrusaObjectDataViewModelNode(name, instances_count); + m_objects.push_back(root); + // notify control + wxDataViewItem child((void*)root); + wxDataViewItem parent((void*)NULL); + ItemAdded(parent, child); + return child; +} + +wxDataViewItem PrusaObjectDataViewModel::AddChild( const wxDataViewItem &parent_item, + const wxString &name, + const wxBitmap& icon, + const int extruder/* = 0*/, + const bool create_frst_child/* = true*/) +{ + PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID(); + if (!root) return wxDataViewItem(0); + + const wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder); + + if (create_frst_child && (root->GetChildren().Count() == 0 || + (root->GetChildren().Count() == 1 && root->GetNthChild(0)->m_type == "settings"))) + { + const auto icon_solid_mesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG); + const auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh, extruder_str, 0); + root->Append(node); + // notify control + const wxDataViewItem child((void*)node); + ItemAdded(parent_item, child); + } + + const auto volume_id = root->GetChildCount() > 0 && root->GetNthChild(0)->m_type == "settings" ? + root->GetChildCount() - 1 : root->GetChildCount(); + + const auto node = new PrusaObjectDataViewModelNode(root, name, icon, extruder_str, volume_id); + root->Append(node); + // notify control + const wxDataViewItem child((void*)node); + ItemAdded(parent_item, child); + return child; +} + +wxDataViewItem PrusaObjectDataViewModel::AddSettingsChild(const wxDataViewItem &parent_item) +{ + PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID(); + if (!root) return wxDataViewItem(0); + + const auto node = new PrusaObjectDataViewModelNode(root); + root->Insert(node, 0); + // notify control + const wxDataViewItem child((void*)node); + ItemAdded(parent_item, child); + return child; +} + +wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) +{ + auto ret_item = wxDataViewItem(0); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return ret_item; + + auto node_parent = node->GetParent(); + wxDataViewItem parent(node_parent); + + // first remove the node from the parent's array of children; + // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ + // thus removing the node from it doesn't result in freeing it + if (node_parent){ + auto id = node_parent->GetChildren().Index(node); + auto v_id = node->GetVolumeId(); + node_parent->GetChildren().Remove(node); + if (id > 0){ + if(id == node_parent->GetChildCount()) id--; + ret_item = wxDataViewItem(node_parent->GetChildren().Item(id)); + } + + //update volume_id value for remaining child-nodes + auto children = node_parent->GetChildren(); + for (size_t i = 0; i < node_parent->GetChildCount() && v_id>=0; i++) + { + auto volume_id = children[i]->GetVolumeId(); + if (volume_id > v_id) + children[i]->SetVolumeId(volume_id-1); + } + } + else + { + auto it = find(m_objects.begin(), m_objects.end(), node); + auto id = it - m_objects.begin(); + if (it != m_objects.end()) + m_objects.erase(it); + if (id > 0){ + if(id == m_objects.size()) id--; + ret_item = wxDataViewItem(m_objects[id]); + } + } + // free the node + delete node; + + // set m_containet to FALSE if parent has no child + if (node_parent) { +#ifndef __WXGTK__ + if (node_parent->GetChildCount() == 0) + node_parent->m_container = false; +#endif //__WXGTK__ + ret_item = parent; + } + + // notify control + ItemDeleted(parent, item); + return ret_item; +} + +void PrusaObjectDataViewModel::DeleteAll() +{ + while (!m_objects.empty()) + { + auto object = m_objects.back(); +// object->RemoveAllChildren(); + Delete(wxDataViewItem(object)); + } +} + +void PrusaObjectDataViewModel::DeleteChildren(wxDataViewItem& parent) +{ + PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent.GetID(); + if (!root) // happens if item.IsOk()==false + return; + + // first remove the node from the parent's array of children; + // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ + // thus removing the node from it doesn't result in freeing it + auto& children = root->GetChildren(); + for (int id = root->GetChildCount() - 1; id >= 0; --id) + { + auto node = children[id]; + auto item = wxDataViewItem(node); + children.RemoveAt(id); + + // free the node + delete node; + + // notify control + ItemDeleted(parent, item); + } + + // set m_containet to FALSE if parent has no child +#ifndef __WXGTK__ + root->m_container = false; +#endif //__WXGTK__ +} + +wxDataViewItem PrusaObjectDataViewModel::GetItemById(int obj_idx) +{ + if (obj_idx >= m_objects.size()) + { + printf("Error! Out of objects range.\n"); + return wxDataViewItem(0); + } + return wxDataViewItem(m_objects[obj_idx]); +} + + +wxDataViewItem PrusaObjectDataViewModel::GetItemByVolumeId(int obj_idx, int volume_idx) +{ + if (obj_idx >= m_objects.size()) { + printf("Error! Out of objects range.\n"); + return wxDataViewItem(0); + } + + auto parent = m_objects[obj_idx]; + if (parent->GetChildCount() == 0) { + printf("Error! Object has no one volume.\n"); + return wxDataViewItem(0); + } + + for (size_t i = 0; i < parent->GetChildCount(); i++) + if (parent->GetNthChild(i)->m_volume_id == volume_idx) + return wxDataViewItem(parent->GetNthChild(i)); + + return wxDataViewItem(0); +} + +int PrusaObjectDataViewModel::GetIdByItem(wxDataViewItem& item) +{ + wxASSERT(item.IsOk()); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + auto it = find(m_objects.begin(), m_objects.end(), node); + if (it == m_objects.end()) + return -1; + + return it - m_objects.begin(); +} + +int PrusaObjectDataViewModel::GetVolumeIdByItem(const wxDataViewItem& item) +{ + wxASSERT(item.IsOk()); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return -1; + return node->GetVolumeId(); +} + +wxString PrusaObjectDataViewModel::GetName(const wxDataViewItem &item) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_name; +} + +wxString PrusaObjectDataViewModel::GetCopy(const wxDataViewItem &item) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_copy; +} + +wxIcon& PrusaObjectDataViewModel::GetIcon(const wxDataViewItem &item) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + return node->m_icon; +} + +wxBitmap& PrusaObjectDataViewModel::GetBitmap(const wxDataViewItem &item) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + return node->m_bmp; +} + +void PrusaObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const +{ + wxASSERT(item.IsOk()); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + switch (col) + { + case 0:{ + const PrusaDataViewBitmapText data(node->m_name, node->m_bmp); + variant << data; + break;} + case 1: + variant = node->m_copy; + break; + case 2: + variant = node->m_extruder; + break; + case 3: + variant << node->m_action_icon; + break; + default: + ; + } +} + +bool PrusaObjectDataViewModel::SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned int col) +{ + wxASSERT(item.IsOk()); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + return node->SetValue(variant, col); +} + +bool PrusaObjectDataViewModel::SetValue(const wxVariant &variant, const int item_idx, unsigned int col) +{ + if (item_idx < 0 || item_idx >= m_objects.size()) + return false; + + return m_objects[item_idx]->SetValue(variant, col); +} + +wxDataViewItem PrusaObjectDataViewModel::MoveChildUp(const wxDataViewItem &item) +{ + auto ret_item = wxDataViewItem(0); + wxASSERT(item.IsOk()); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return ret_item; + + auto node_parent = node->GetParent(); + if (!node_parent) // If isn't part, but object + return ret_item; + + auto volume_id = node->GetVolumeId(); + if (0 < volume_id && volume_id < node_parent->GetChildCount()){ + node_parent->SwapChildrens(volume_id - 1, volume_id); + ret_item = wxDataViewItem(node_parent->GetNthChild(volume_id - 1)); + ItemChanged(item); + ItemChanged(ret_item); + } + else + ret_item = wxDataViewItem(node_parent->GetNthChild(0)); + return ret_item; +} + +wxDataViewItem PrusaObjectDataViewModel::MoveChildDown(const wxDataViewItem &item) +{ + auto ret_item = wxDataViewItem(0); + wxASSERT(item.IsOk()); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return ret_item; + + auto node_parent = node->GetParent(); + if (!node_parent) // If isn't part, but object + return ret_item; + + auto volume_id = node->GetVolumeId(); + if (0 <= volume_id && volume_id+1 < node_parent->GetChildCount()){ + node_parent->SwapChildrens(volume_id + 1, volume_id); + ret_item = wxDataViewItem(node_parent->GetNthChild(volume_id + 1)); + ItemChanged(item); + ItemChanged(ret_item); + } + else + ret_item = wxDataViewItem(node_parent->GetNthChild(node_parent->GetChildCount()-1)); + return ret_item; +} + +wxDataViewItem PrusaObjectDataViewModel::ReorganizeChildren(int current_volume_id, int new_volume_id, const wxDataViewItem &parent) +{ + auto ret_item = wxDataViewItem(0); + if (current_volume_id == new_volume_id) + return ret_item; + wxASSERT(parent.IsOk()); + PrusaObjectDataViewModelNode *node_parent = (PrusaObjectDataViewModelNode*)parent.GetID(); + if (!node_parent) // happens if item.IsOk()==false + return ret_item; + + const size_t shift = node_parent->GetChildren().Item(0)->m_type == "settings" ? 1 : 0; + + PrusaObjectDataViewModelNode *deleted_node = node_parent->GetNthChild(current_volume_id+shift); + node_parent->GetChildren().Remove(deleted_node); + ItemDeleted(parent, wxDataViewItem(deleted_node)); + node_parent->Insert(deleted_node, new_volume_id+shift); + ItemAdded(parent, wxDataViewItem(deleted_node)); + const auto settings_item = HasSettings(wxDataViewItem(deleted_node)); + if (settings_item) + ItemAdded(wxDataViewItem(deleted_node), settings_item); + + //update volume_id value for child-nodes + auto children = node_parent->GetChildren(); + int id_frst = current_volume_id < new_volume_id ? current_volume_id : new_volume_id; + int id_last = current_volume_id > new_volume_id ? current_volume_id : new_volume_id; + for (int id = id_frst; id <= id_last; ++id) + children[id+shift]->SetVolumeId(id); + + return wxDataViewItem(node_parent->GetNthChild(new_volume_id+shift)); +} + +bool PrusaObjectDataViewModel::IsEnabled(const wxDataViewItem &item, unsigned int col) const +{ + wxASSERT(item.IsOk()); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + + // disable extruder selection for the "Settings" item + return !(col == 2 && node->m_extruder.IsEmpty()); +} + +wxDataViewItem PrusaObjectDataViewModel::GetParent(const wxDataViewItem &item) const +{ + // the invisible root node has no parent + if (!item.IsOk()) + return wxDataViewItem(0); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + + // objects nodes has no parent too + if (find(m_objects.begin(), m_objects.end(),node) != m_objects.end()) + return wxDataViewItem(0); + + return wxDataViewItem((void*)node->GetParent()); +} + +bool PrusaObjectDataViewModel::IsContainer(const wxDataViewItem &item) const +{ + // the invisible root node can have children + if (!item.IsOk()) + return true; + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + return node->IsContainer(); +} + +unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)parent.GetID(); + if (!node) + { + for (auto object : m_objects) + array.Add(wxDataViewItem((void*)object)); + return m_objects.size(); + } + + if (node->GetChildCount() == 0) + { + return 0; + } + + unsigned int count = node->GetChildren().GetCount(); + for (unsigned int pos = 0; pos < count; pos++) + { + PrusaObjectDataViewModelNode *child = node->GetChildren().Item(pos); + array.Add(wxDataViewItem((void*)child)); + } + + return count; +} + +wxDataViewItem PrusaObjectDataViewModel::HasSettings(const wxDataViewItem &item) const +{ + if (!item.IsOk()) + return wxDataViewItem(0); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (node->GetChildCount() == 0) + return wxDataViewItem(0); + + auto& children = node->GetChildren(); + if (children[0]->m_type == "settings") + return wxDataViewItem((void*)children[0]);; + + return wxDataViewItem(0); +} + +bool PrusaObjectDataViewModel::IsSettingsItem(const wxDataViewItem &item) const +{ + if (!item.IsOk()) + return false; + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + return node->m_type == "settings"; +} + + + +void PrusaObjectDataViewModel::UpdateSettingsDigest(const wxDataViewItem &item, + const std::vector<std::string>& categories) +{ + if (!item.IsOk()) return; + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node->update_settings_digest(categories)) + return; + ItemChanged(item); +} + +IMPLEMENT_VARIANT_OBJECT(PrusaDataViewBitmapText) +// --------------------------------------------------------- +// PrusaIconTextRenderer +// --------------------------------------------------------- + +bool PrusaBitmapTextRenderer::SetValue(const wxVariant &value) +{ + m_value << value; + return true; +} + +bool PrusaBitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const +{ + return false; +} + +bool PrusaBitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) +{ + int xoffset = 0; + + const wxBitmap& icon = m_value.GetBitmap(); + if (icon.IsOk()) + { + dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); + xoffset = icon.GetWidth() + 4; + } + + RenderText(m_value.GetText(), xoffset, rect, dc, state); + + return true; +} + +wxSize PrusaBitmapTextRenderer::GetSize() const +{ + if (!m_value.GetText().empty()) + { + wxSize size = GetTextExtent(m_value.GetText()); + + if (m_value.GetBitmap().IsOk()) + size.x += m_value.GetBitmap().GetWidth() + 4; + return size; + } + return wxSize(80, 20); +} + + +// ---------------------------------------------------------------------------- +// PrusaDoubleSlider +// ---------------------------------------------------------------------------- + +PrusaDoubleSlider::PrusaDoubleSlider(wxWindow *parent, + wxWindowID id, + int lowerValue, + int higherValue, + int minValue, + int maxValue, + const wxPoint& pos, + const wxSize& size, + long style, + const wxValidator& val, + const wxString& name) : + wxControl(parent, id, pos, size, wxWANTS_CHARS | wxBORDER_NONE), + m_lower_value(lowerValue), m_higher_value (higherValue), + m_min_value(minValue), m_max_value(maxValue), + m_style(style == wxSL_HORIZONTAL || style == wxSL_VERTICAL ? style: wxSL_HORIZONTAL) +{ +#ifndef __WXOSX__ // SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX + SetDoubleBuffered(true); +#endif //__WXOSX__ + + m_bmp_thumb_higher = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("right_half_circle.png")) : + Slic3r::GUI::from_u8(Slic3r::var("up_half_circle.png")), wxBITMAP_TYPE_PNG); + m_bmp_thumb_lower = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("left_half_circle.png")) : + Slic3r::GUI::from_u8(Slic3r::var("down_half_circle.png")), wxBITMAP_TYPE_PNG); + m_thumb_size = m_bmp_thumb_lower.GetSize(); + + m_bmp_add_tick_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_add_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_off.png")), wxBITMAP_TYPE_PNG); + m_bmp_del_tick_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_del_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_off.png")), wxBITMAP_TYPE_PNG); + m_tick_icon_dim = m_bmp_add_tick_on.GetSize().x; + + m_bmp_one_layer_lock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_one_layer_lock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_off.png")), wxBITMAP_TYPE_PNG); + m_bmp_one_layer_unlock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_one_layer_unlock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_off.png")), wxBITMAP_TYPE_PNG); + m_lock_icon_dim = m_bmp_one_layer_lock_on.GetSize().x; + + m_selection = ssUndef; + + // slider events + Bind(wxEVT_PAINT, &PrusaDoubleSlider::OnPaint, this); + Bind(wxEVT_LEFT_DOWN, &PrusaDoubleSlider::OnLeftDown, this); + Bind(wxEVT_MOTION, &PrusaDoubleSlider::OnMotion, this); + Bind(wxEVT_LEFT_UP, &PrusaDoubleSlider::OnLeftUp, this); + Bind(wxEVT_MOUSEWHEEL, &PrusaDoubleSlider::OnWheel, this); + Bind(wxEVT_ENTER_WINDOW,&PrusaDoubleSlider::OnEnterWin, this); + Bind(wxEVT_LEAVE_WINDOW,&PrusaDoubleSlider::OnLeaveWin, this); + Bind(wxEVT_KEY_DOWN, &PrusaDoubleSlider::OnKeyDown, this); + Bind(wxEVT_KEY_UP, &PrusaDoubleSlider::OnKeyUp, this); + Bind(wxEVT_RIGHT_DOWN, &PrusaDoubleSlider::OnRightDown,this); + Bind(wxEVT_RIGHT_UP, &PrusaDoubleSlider::OnRightUp, this); + + // control's view variables + SLIDER_MARGIN = 4 + (style == wxSL_HORIZONTAL ? m_bmp_thumb_higher.GetWidth() : m_bmp_thumb_higher.GetHeight()); + + DARK_ORANGE_PEN = wxPen(wxColour(253, 84, 2)); + ORANGE_PEN = wxPen(wxColour(253, 126, 66)); + LIGHT_ORANGE_PEN = wxPen(wxColour(254, 177, 139)); + + DARK_GREY_PEN = wxPen(wxColour(128, 128, 128)); + GREY_PEN = wxPen(wxColour(164, 164, 164)); + LIGHT_GREY_PEN = wxPen(wxColour(204, 204, 204)); + + line_pens = { &DARK_GREY_PEN, &GREY_PEN, &LIGHT_GREY_PEN }; + segm_pens = { &DARK_ORANGE_PEN, &ORANGE_PEN, &LIGHT_ORANGE_PEN }; +} + +int PrusaDoubleSlider::GetActiveValue() const +{ + return m_selection == ssLower ? + m_lower_value : m_selection == ssHigher ? + m_higher_value : -1; +} + +wxSize PrusaDoubleSlider::DoGetBestSize() const +{ + const wxSize size = wxControl::DoGetBestSize(); + if (size.x > 1 && size.y > 1) + return size; + const int new_size = is_horizontal() ? 80 : 120; + return wxSize(new_size, new_size); +} + +void PrusaDoubleSlider::SetLowerValue(const int lower_val) +{ + m_selection = ssLower; + m_lower_value = lower_val; + correct_lower_value(); + Refresh(); + Update(); + + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); +} + +void PrusaDoubleSlider::SetHigherValue(const int higher_val) +{ + m_selection = ssHigher; + m_higher_value = higher_val; + correct_higher_value(); + Refresh(); + Update(); + + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); +} + +void PrusaDoubleSlider::SetMaxValue(const int max_value) +{ + m_max_value = max_value; + Refresh(); + Update(); +} + +void PrusaDoubleSlider::draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos) +{ + int width; + int height; + get_size(&width, &height); + + wxCoord line_beg_x = is_horizontal() ? SLIDER_MARGIN : width*0.5 - 1; + wxCoord line_beg_y = is_horizontal() ? height*0.5 - 1 : SLIDER_MARGIN; + wxCoord line_end_x = is_horizontal() ? width - SLIDER_MARGIN + 1 : width*0.5 - 1; + wxCoord line_end_y = is_horizontal() ? height*0.5 - 1 : height - SLIDER_MARGIN + 1; + + wxCoord segm_beg_x = is_horizontal() ? lower_pos : width*0.5 - 1; + wxCoord segm_beg_y = is_horizontal() ? height*0.5 - 1 : lower_pos-1; + wxCoord segm_end_x = is_horizontal() ? higher_pos : width*0.5 - 1; + wxCoord segm_end_y = is_horizontal() ? height*0.5 - 1 : higher_pos-1; + + for (int id = 0; id < line_pens.size(); id++) + { + dc.SetPen(*line_pens[id]); + dc.DrawLine(line_beg_x, line_beg_y, line_end_x, line_end_y); + dc.SetPen(*segm_pens[id]); + dc.DrawLine(segm_beg_x, segm_beg_y, segm_end_x, segm_end_y); + if (is_horizontal()) + line_beg_y = line_end_y = segm_beg_y = segm_end_y += 1; + else + line_beg_x = line_end_x = segm_beg_x = segm_end_x += 1; + } +} + +double PrusaDoubleSlider::get_scroll_step() +{ + const wxSize sz = get_size(); + const int& slider_len = m_style == wxSL_HORIZONTAL ? sz.x : sz.y; + return double(slider_len - SLIDER_MARGIN * 2) / (m_max_value - m_min_value); +} + +// get position on the slider line from entered value +wxCoord PrusaDoubleSlider::get_position_from_value(const int value) +{ + const double step = get_scroll_step(); + const int val = is_horizontal() ? value : m_max_value - value; + return wxCoord(SLIDER_MARGIN + int(val*step + 0.5)); +} + +wxSize PrusaDoubleSlider::get_size() +{ + int w, h; + get_size(&w, &h); + return wxSize(w, h); +} + +void PrusaDoubleSlider::get_size(int *w, int *h) +{ + GetSize(w, h); + is_horizontal() ? *w -= m_lock_icon_dim : *h -= m_lock_icon_dim; +} + +double PrusaDoubleSlider::get_double_value(const SelectedSlider& selection) const +{ + if (m_values.empty()) + return 0.0; + return m_values[selection == ssLower ? m_lower_value : m_higher_value].second; +} + +void PrusaDoubleSlider::get_lower_and_higher_position(int& lower_pos, int& higher_pos) +{ + const double step = get_scroll_step(); + if (is_horizontal()) { + lower_pos = SLIDER_MARGIN + int(m_lower_value*step + 0.5); + higher_pos = SLIDER_MARGIN + int(m_higher_value*step + 0.5); + } + else { + lower_pos = SLIDER_MARGIN + int((m_max_value - m_lower_value)*step + 0.5); + higher_pos = SLIDER_MARGIN + int((m_max_value - m_higher_value)*step + 0.5); + } +} + +void PrusaDoubleSlider::draw_focus_rect() +{ + if (!m_is_focused) + return; + const wxSize sz = GetSize(); + wxPaintDC dc(this); + const wxPen pen = wxPen(wxColour(128, 128, 10), 1, wxPENSTYLE_DOT); + dc.SetPen(pen); + dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT)); + dc.DrawRectangle(1, 1, sz.x - 2, sz.y - 2); +} + +void PrusaDoubleSlider::render() +{ + SetBackgroundColour(GetParent()->GetBackgroundColour()); + draw_focus_rect(); + + wxPaintDC dc(this); + wxFont font = dc.GetFont(); + const wxFont smaller_font = font.Smaller(); + dc.SetFont(smaller_font); + + const wxCoord lower_pos = get_position_from_value(m_lower_value); + const wxCoord higher_pos = get_position_from_value(m_higher_value); + + // draw line + draw_scroll_line(dc, lower_pos, higher_pos); + +// //lower slider: +// draw_thumb(dc, lower_pos, ssLower); +// //higher slider: +// draw_thumb(dc, higher_pos, ssHigher); + + // draw both sliders + draw_thumbs(dc, lower_pos, higher_pos); + + //draw color print ticks + draw_ticks(dc); + + //draw color print ticks + draw_one_layer_icon(dc); +} + +void PrusaDoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end) +{ + const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; + wxBitmap* icon = m_is_action_icon_focesed ? &m_bmp_add_tick_off : &m_bmp_add_tick_on; + if (m_ticks.find(tick) != m_ticks.end()) + icon = m_is_action_icon_focesed ? &m_bmp_del_tick_off : &m_bmp_del_tick_on; + + wxCoord x_draw, y_draw; + is_horizontal() ? x_draw = pt_beg.x - 0.5*m_tick_icon_dim : y_draw = pt_beg.y - 0.5*m_tick_icon_dim; + if (m_selection == ssLower) + is_horizontal() ? y_draw = pt_end.y + 3 : x_draw = pt_beg.x - m_tick_icon_dim-2; + else + is_horizontal() ? y_draw = pt_beg.y - m_tick_icon_dim-2 : x_draw = pt_end.x + 3; + + dc.DrawBitmap(*icon, x_draw, y_draw); + + //update rect of the tick action icon + m_rect_tick_action = wxRect(x_draw, y_draw, m_tick_icon_dim, m_tick_icon_dim); +} + +void PrusaDoubleSlider::draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, const SelectedSlider selection) +{ + if (m_selection == selection) { + //draw info line + dc.SetPen(DARK_ORANGE_PEN); + const wxPoint pt_beg = is_horizontal() ? wxPoint(pos.x, pos.y - m_thumb_size.y) : wxPoint(pos.x - m_thumb_size.x, pos.y - 1); + const wxPoint pt_end = is_horizontal() ? wxPoint(pos.x, pos.y + m_thumb_size.y) : wxPoint(pos.x + m_thumb_size.x, pos.y - 1); + dc.DrawLine(pt_beg, pt_end); + + //draw action icon + draw_action_icon(dc, pt_beg, pt_end); + } +} + +wxString PrusaDoubleSlider::get_label(const SelectedSlider& selection) const +{ + const int value = selection == ssLower ? m_lower_value : m_higher_value; + + if (m_label_koef == 1.0 && m_values.empty()) + return wxString::Format("%d", value); + + const wxString str = m_values.empty() ? + wxNumberFormatter::ToString(m_label_koef*value, 2, wxNumberFormatter::Style_None) : + wxNumberFormatter::ToString(m_values[value].second, 2, wxNumberFormatter::Style_None); + return wxString::Format("%s\n(%d)", str, m_values.empty() ? value : m_values[value].first); +} + +void PrusaDoubleSlider::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const +{ + if ((m_is_one_layer || m_higher_value==m_lower_value) && selection != m_selection || !selection) + return; + wxCoord text_width, text_height; + const wxString label = get_label(selection); + dc.GetMultiLineTextExtent(label, &text_width, &text_height); + wxPoint text_pos; + if (selection ==ssLower) + text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x) : + wxPoint(pos.x + m_thumb_size.x+1, pos.y - 0.5*text_height - 1); + else + text_pos = is_horizontal() ? wxPoint(pos.x - text_width - 1, pos.y - m_thumb_size.x - text_height) : + wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5*text_height + 1); + dc.DrawText(label, text_pos); +} + +void PrusaDoubleSlider::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) +{ + wxCoord x_draw, y_draw; + if (selection == ssLower) { + if (is_horizontal()) { + x_draw = pos.x - m_thumb_size.x; + y_draw = pos.y - int(0.5*m_thumb_size.y); + } + else { + x_draw = pos.x - int(0.5*m_thumb_size.x); + y_draw = pos.y; + } + } + else{ + if (is_horizontal()) { + x_draw = pos.x; + y_draw = pos.y - int(0.5*m_thumb_size.y); + } + else { + x_draw = pos.x - int(0.5*m_thumb_size.x); + y_draw = pos.y - m_thumb_size.y; + } + } + dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower : m_bmp_thumb_higher, x_draw, y_draw); + + // Update thumb rect + update_thumb_rect(x_draw, y_draw, selection); +} + +void PrusaDoubleSlider::draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection) +{ + //calculate thumb position on slider line + int width, height; + get_size(&width, &height); + const wxPoint pos = is_horizontal() ? wxPoint(pos_coord, height*0.5) : wxPoint(0.5*width, pos_coord); + + // Draw thumb + draw_thumb_item(dc, pos, selection); + + // Draw info_line + draw_info_line_with_icon(dc, pos, selection); + + // Draw thumb text + draw_thumb_text(dc, pos, selection); +} + +void PrusaDoubleSlider::draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord& higher_pos) +{ + //calculate thumb position on slider line + int width, height; + get_size(&width, &height); + const wxPoint pos_l = is_horizontal() ? wxPoint(lower_pos, height*0.5) : wxPoint(0.5*width, lower_pos); + const wxPoint pos_h = is_horizontal() ? wxPoint(higher_pos, height*0.5) : wxPoint(0.5*width, higher_pos); + + // Draw lower thumb + draw_thumb_item(dc, pos_l, ssLower); + // Draw lower info_line + draw_info_line_with_icon(dc, pos_l, ssLower); + + // Draw higher thumb + draw_thumb_item(dc, pos_h, ssHigher); + // Draw higher info_line + draw_info_line_with_icon(dc, pos_h, ssHigher); + // Draw higher thumb text + draw_thumb_text(dc, pos_h, ssHigher); + + // Draw lower thumb text + draw_thumb_text(dc, pos_l, ssLower); +} + +void PrusaDoubleSlider::draw_ticks(wxDC& dc) +{ + dc.SetPen(DARK_GREY_PEN); + int height, width; + get_size(&width, &height); + const wxCoord mid = is_horizontal() ? 0.5*height : 0.5*width; + for (auto tick : m_ticks) + { + const wxCoord pos = get_position_from_value(tick); + + is_horizontal() ? dc.DrawLine(pos, mid-14, pos, mid-9) : + dc.DrawLine(mid - 14, pos - 1, mid - 9, pos - 1); + is_horizontal() ? dc.DrawLine(pos, mid+14, pos, mid+9) : + dc.DrawLine(mid + 14, pos - 1, mid + 9, pos - 1); + } +} + +void PrusaDoubleSlider::draw_one_layer_icon(wxDC& dc) +{ + wxBitmap* icon = m_is_one_layer ? + m_is_one_layer_icon_focesed ? &m_bmp_one_layer_lock_off : &m_bmp_one_layer_lock_on : + m_is_one_layer_icon_focesed ? &m_bmp_one_layer_unlock_off : &m_bmp_one_layer_unlock_on; + + int width, height; + get_size(&width, &height); + + wxCoord x_draw, y_draw; + is_horizontal() ? x_draw = width-2 : x_draw = 0.5*width - 0.5*m_lock_icon_dim; + is_horizontal() ? y_draw = 0.5*height - 0.5*m_lock_icon_dim : y_draw = height-2; + + dc.DrawBitmap(*icon, x_draw, y_draw); + + //update rect of the lock/unlock icon + m_rect_one_layer_icon = wxRect(x_draw, y_draw, m_lock_icon_dim, m_lock_icon_dim); +} + +void PrusaDoubleSlider::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection) +{ + const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, m_thumb_size.y); + if (selection == ssLower) + m_rect_lower_thumb = rect; + else + m_rect_higher_thumb = rect; +} + +int PrusaDoubleSlider::get_value_from_position(const wxCoord x, const wxCoord y) +{ + const int height = get_size().y; + const double step = get_scroll_step(); + + if (is_horizontal()) + return int(double(x - SLIDER_MARGIN) / step + 0.5); + else + return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5); +} + +void PrusaDoubleSlider::detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel /*= false*/) +{ + if (is_mouse_wheel) + { + if (is_horizontal()) { + m_selection = pt.x <= m_rect_lower_thumb.GetRight() ? ssLower : + pt.x >= m_rect_higher_thumb.GetLeft() ? ssHigher : ssUndef; + } + else { + m_selection = pt.y >= m_rect_lower_thumb.GetTop() ? ssLower : + pt.y <= m_rect_higher_thumb.GetBottom() ? ssHigher : ssUndef; + } + return; + } + + m_selection = is_point_in_rect(pt, m_rect_lower_thumb) ? ssLower : + is_point_in_rect(pt, m_rect_higher_thumb) ? ssHigher : ssUndef; +} + +bool PrusaDoubleSlider::is_point_in_rect(const wxPoint& pt, const wxRect& rect) +{ + if (rect.GetLeft() <= pt.x && pt.x <= rect.GetRight() && + rect.GetTop() <= pt.y && pt.y <= rect.GetBottom()) + return true; + return false; +} + +void PrusaDoubleSlider::ChangeOneLayerLock() +{ + m_is_one_layer = !m_is_one_layer; + m_selection == ssLower ? correct_lower_value() : correct_higher_value(); + if (!m_selection) m_selection = ssHigher; + + Refresh(); + Update(); + + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); +} + +void PrusaDoubleSlider::OnLeftDown(wxMouseEvent& event) +{ + this->CaptureMouse(); + wxClientDC dc(this); + wxPoint pos = event.GetLogicalPosition(dc); + if (is_point_in_rect(pos, m_rect_tick_action)) { + action_tick(taOnIcon); + return; + } + + m_is_left_down = true; + if (is_point_in_rect(pos, m_rect_one_layer_icon)){ + m_is_one_layer = !m_is_one_layer; + m_selection == ssLower ? correct_lower_value() : correct_higher_value(); + if (!m_selection) m_selection = ssHigher; + } + else + detect_selected_slider(pos); + + Refresh(); + Update(); + event.Skip(); +} + +void PrusaDoubleSlider::correct_lower_value() +{ + if (m_lower_value < m_min_value) + m_lower_value = m_min_value; + else if (m_lower_value > m_max_value) + m_lower_value = m_max_value; + + if (m_lower_value >= m_higher_value && m_lower_value <= m_max_value || m_is_one_layer) + m_higher_value = m_lower_value; +} + +void PrusaDoubleSlider::correct_higher_value() +{ + if (m_higher_value > m_max_value) + m_higher_value = m_max_value; + else if (m_higher_value < m_min_value) + m_higher_value = m_min_value; + + if (m_higher_value <= m_lower_value && m_higher_value >= m_min_value || m_is_one_layer) + m_lower_value = m_higher_value; +} + +void PrusaDoubleSlider::OnMotion(wxMouseEvent& event) +{ + const wxClientDC dc(this); + const wxPoint pos = event.GetLogicalPosition(dc); + m_is_one_layer_icon_focesed = is_point_in_rect(pos, m_rect_one_layer_icon); + if (!m_is_left_down && !m_is_one_layer){ + m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action); + } + else if (m_is_left_down || m_is_right_down){ + if (m_selection == ssLower) { + m_lower_value = get_value_from_position(pos.x, pos.y); + correct_lower_value(); + } + else if (m_selection == ssHigher) { + m_higher_value = get_value_from_position(pos.x, pos.y); + correct_higher_value(); + } + } + Refresh(); + Update(); + event.Skip(); + + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); +} + +void PrusaDoubleSlider::OnLeftUp(wxMouseEvent& event) +{ + this->ReleaseMouse(); + m_is_left_down = false; + Refresh(); + Update(); + event.Skip(); + + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); +} + +void PrusaDoubleSlider::enter_window(wxMouseEvent& event, const bool enter) +{ + m_is_focused = enter; + Refresh(); + Update(); + event.Skip(); +} + +// "condition" have to be true for: +// - value increase (if wxSL_VERTICAL) +// - value decrease (if wxSL_HORIZONTAL) +void PrusaDoubleSlider::move_current_thumb(const bool condition) +{ + m_is_one_layer = wxGetKeyState(WXK_CONTROL); + int delta = condition ? -1 : 1; + if (is_horizontal()) + delta *= -1; + + if (m_selection == ssLower) { + m_lower_value -= delta; + correct_lower_value(); + } + else if (m_selection == ssHigher) { + m_higher_value -= delta; + correct_higher_value(); + } + Refresh(); + Update(); + + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); +} + +void PrusaDoubleSlider::action_tick(const TicksAction action) +{ + if (m_selection == ssUndef) + return; + + const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; + + if (action == taOnIcon && !m_ticks.insert(tick).second) + m_ticks.erase(tick); + else { + const auto it = m_ticks.find(tick); + if (it == m_ticks.end() && action == taAdd) + m_ticks.insert(tick); + else if (it != m_ticks.end() && action == taDel) + m_ticks.erase(tick); + else + return; + } + + Refresh(); + Update(); +} + +void PrusaDoubleSlider::OnWheel(wxMouseEvent& event) +{ + wxClientDC dc(this); + wxPoint pos = event.GetLogicalPosition(dc); + detect_selected_slider(pos, true); + + if (m_selection == ssUndef) + return; + + move_current_thumb(event.GetWheelRotation() > 0); +} + +void PrusaDoubleSlider::OnKeyDown(wxKeyEvent &event) +{ + const int key = event.GetKeyCode(); + if (key == '+' || key == WXK_NUMPAD_ADD) + action_tick(taAdd); + else if (key == '-' || key == 390 || key == WXK_DELETE || key == WXK_BACK) + action_tick(taDel); + else if (is_horizontal()) + { + if (key == WXK_LEFT || key == WXK_RIGHT) + move_current_thumb(key == WXK_LEFT); + else if (key == WXK_UP || key == WXK_DOWN){ + m_selection = key == WXK_UP ? ssHigher : ssLower; + Refresh(); + } + } + else { + if (key == WXK_LEFT || key == WXK_RIGHT) { + m_selection = key == WXK_LEFT ? ssHigher : ssLower; + Refresh(); + } + else if (key == WXK_UP || key == WXK_DOWN) + move_current_thumb(key == WXK_UP); + } +} + +void PrusaDoubleSlider::OnKeyUp(wxKeyEvent &event) +{ + if (event.GetKeyCode() == WXK_CONTROL) + m_is_one_layer = false; + Refresh(); + Update(); + event.Skip(); +} + +void PrusaDoubleSlider::OnRightDown(wxMouseEvent& event) +{ + this->CaptureMouse(); + const wxClientDC dc(this); + detect_selected_slider(event.GetLogicalPosition(dc)); + if (!m_selection) + return; + + if (m_selection == ssLower) + m_higher_value = m_lower_value; + else + m_lower_value = m_higher_value; + + m_is_right_down = m_is_one_layer = true; + + Refresh(); + Update(); + event.Skip(); +} + +void PrusaDoubleSlider::OnRightUp(wxMouseEvent& event) +{ + this->ReleaseMouse(); + m_is_right_down = m_is_one_layer = false; + + Refresh(); + Update(); + event.Skip(); +} + + +// ---------------------------------------------------------------------------- +// PrusaLockButton +// ---------------------------------------------------------------------------- + +PrusaLockButton::PrusaLockButton( wxWindow *parent, + wxWindowID id, + const wxPoint& pos /*= wxDefaultPosition*/, + const wxSize& size /*= wxDefaultSize*/): + wxButton(parent, id, wxEmptyString, pos, size, wxBU_EXACTFIT | wxNO_BORDER) +{ + m_bmp_lock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_lock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_off.png")), wxBITMAP_TYPE_PNG); + m_bmp_unlock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_unlock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_off.png")), wxBITMAP_TYPE_PNG); + m_lock_icon_dim = m_bmp_lock_on.GetSize().x; + +#ifdef __WXMSW__ + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif // __WXMSW__ + SetBitmap(m_bmp_unlock_on); + + //button events + Bind(wxEVT_BUTTON, &PrusaLockButton::OnButton, this); + Bind(wxEVT_ENTER_WINDOW, &PrusaLockButton::OnEnterBtn, this); + Bind(wxEVT_LEAVE_WINDOW, &PrusaLockButton::OnLeaveBtn, this); +} + +void PrusaLockButton::OnButton(wxCommandEvent& event) +{ + m_is_pushed = !m_is_pushed; + enter_button(true); + + event.Skip(); +} + +void PrusaLockButton::enter_button(const bool enter) +{ + wxBitmap* icon = m_is_pushed ? + enter ? &m_bmp_lock_off : &m_bmp_lock_on : + enter ? &m_bmp_unlock_off : &m_bmp_unlock_on; + SetBitmap(*icon); + + Refresh(); + Update(); +} + +// ************************************** EXPERIMENTS *************************************** + +// ***************************************************************************** + + + |