Merge remote-tracking branch 'remotes/origin/master' into fs_QuadricEdgeCollapse

This commit is contained in:
Vojtech Bubnik 2021-08-05 17:17:18 +02:00
commit ea5a90f08c
42 changed files with 468 additions and 142 deletions

View file

@ -499,10 +499,10 @@ void Bed3D::render_model() const
if (shader != nullptr) {
shader->start_using();
shader->set_uniform("emission_factor", 0.0);
::glPushMatrix();
::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z());
glsafe(::glPushMatrix());
glsafe(::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z()));
model->render();
::glPopMatrix();
glsafe(::glPopMatrix());
shader->stop_using();
}
}

View file

@ -93,6 +93,7 @@ BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech)
});
Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this);
GUI::wxGetApp().UpdateDlgDarkUI(this);
}
BonjourDialog::~BonjourDialog()

View file

@ -335,3 +335,36 @@ bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& val
}
// ----------------------------------------------------------------------------
// TextRenderer
// ----------------------------------------------------------------------------
bool TextRenderer::SetValue(const wxVariant& value)
{
m_value = value.GetString();
return true;
}
bool TextRenderer::GetValue(wxVariant& value) const
{
return false;
}
bool TextRenderer::Render(wxRect rect, wxDC* dc, int state)
{
#ifdef _WIN32
// workaround for Windows DarkMode : Don't respect to the state & wxDATAVIEW_CELL_SELECTED to avoid update of the text color
RenderText(m_value, 0, rect, dc, state & wxDATAVIEW_CELL_SELECTED ? 0 : state);
#else
RenderText(m_value, 0, rect, dc, state);
#endif
return true;
}
wxSize TextRenderer::GetSize() const
{
return GetTextExtent(m_value);
}

View file

@ -161,4 +161,29 @@ private:
};
// ----------------------------------------------------------------------------
// TextRenderer
// ----------------------------------------------------------------------------
class TextRenderer : public wxDataViewCustomRenderer
{
public:
TextRenderer(wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT
, int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL
) : wxDataViewCustomRenderer(wxT("string"), mode, align) {}
bool SetValue(const wxVariant& value) override;
bool GetValue(wxVariant& value) const override;
virtual bool Render(wxRect cell, wxDC* dc, int state) override;
virtual wxSize GetSize() const override;
bool HasEditorCtrl() const override { return false; }
private:
wxString m_value;
};
#endif // slic3r_GUI_ExtraRenderers_hpp_

View file

@ -2379,8 +2379,12 @@ void GCodeViewer::render_toolpaths() const
shader.set_uniform("uniform_color", color4);
};
#if ENABLE_GCODE_VIEWER_STATISTICS
auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color]
#else
auto render_as_points = [zoom, point_size, near_plane_height, set_uniform_color]
(const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
#endif // ENABLE_GCODE_VIEWER_STATISTICS
(const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS
shader.set_uniform("use_fixed_screen_size", 1);
#else
@ -2409,7 +2413,12 @@ void GCodeViewer::render_toolpaths() const
glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE));
};
auto render_as_lines = [light_intensity, set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
#if ENABLE_GCODE_VIEWER_STATISTICS
auto render_as_lines = [this, light_intensity, set_uniform_color]
#else
auto render_as_lines = [light_intensity, set_uniform_color]
#endif // ENABLE_GCODE_VIEWER_STATISTICS
(const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
shader.set_uniform("light_intensity", light_intensity);
for (const RenderPath& path : buffer.render_paths) {
if (path.ibuffer_id == ibuffer_id) {
@ -2422,7 +2431,12 @@ void GCodeViewer::render_toolpaths() const
}
};
auto render_as_triangles = [set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
#if ENABLE_GCODE_VIEWER_STATISTICS
auto render_as_triangles = [this, set_uniform_color]
#else
auto render_as_triangles = [set_uniform_color]
#endif // ENABLE_GCODE_VIEWER_STATISTICS
(const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
for (const RenderPath& path : buffer.render_paths) {
if (path.ibuffer_id == ibuffer_id) {
set_uniform_color(path.color, shader);
@ -2495,7 +2509,12 @@ void GCodeViewer::render_toolpaths() const
}
}
auto render_sequential_range_cap = [set_uniform_color](const SequentialRangeCap& cap) {
#if ENABLE_GCODE_VIEWER_STATISTICS
auto render_sequential_range_cap = [this, set_uniform_color]
#else
auto render_sequential_range_cap = [set_uniform_color]
#endif // ENABLE_GCODE_VIEWER_STATISTICS
(const SequentialRangeCap& cap) {
GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str());
if (shader != nullptr) {
shader->start_using();

View file

@ -1,4 +1,5 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/Platform.hpp"
#include "GLShadersManager.hpp"
#include "3DScene.hpp"
#include "GUI_App.hpp"
@ -43,9 +44,19 @@ std::pair<bool, std::string> GLShadersManager::init()
// used to render extrusion and travel paths as lines in gcode preview
valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" });
// used to render objects in 3d editor
valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }
// For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction.
// Because of this, objects had darker colors inside the multi-material gizmo.
// Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU.
if (platform_flavor() == PlatformFlavor::OSXOnArm)
valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv
#if ENABLE_ENVIRONMENT_MAP
, { "ENABLE_ENVIRONMENT_MAP"sv }
, "ENABLE_ENVIRONMENT_MAP"sv
#endif
});
else
valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }
#if ENABLE_ENVIRONMENT_MAP
, { "ENABLE_ENVIRONMENT_MAP"sv }
#endif
);
// used to render variable layers heights in 3d editor

View file

@ -428,6 +428,7 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension)
/* FT_GCODE */ "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC",
/* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA",
/* FT_PROJECT */ "Project files (*.3mf, *.amf)|*.3mf;*.3MF;*.amf;*.AMF",
/* FT_GALLERY */ "Known files (*.stl, *.obj)|*.stl;*.STL;*.obj;*.OBJ",
/* FT_INI */ "INI files (*.ini)|*.ini;*.INI",
/* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG",
@ -662,7 +663,7 @@ void GUI_App::post_init()
}
// show "Did you know" notification
if (app_config->get("show_hints") == "1")
if (app_config->get("show_hints") == "1" && ! is_gcode_viewer())
plater_->get_notification_manager()->push_hint_notification();
// The extra CallAfter() is needed because of Mac, where this is the only way

View file

@ -59,6 +59,7 @@ enum FileType
FT_GCODE,
FT_MODEL,
FT_PROJECT,
FT_GALLERY,
FT_INI,
FT_SVG,

View file

@ -433,7 +433,7 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty
[type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, "", menu);
}
if (wxGetApp().get_mode() == comExpert) {
if (wxGetApp().get_mode() >= comAdvanced) {
sub_menu->AppendSeparator();
append_menu_item(sub_menu, wxID_ANY, _L("Gallery"), "",
[type](wxCommandEvent&) { obj_list()->load_subobject(type, true); }, "", menu);

View file

@ -2517,7 +2517,7 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D
}
void ObjectList::update_info_items(size_t obj_idx)
void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selections/* = nullptr*/)
{
const ModelObject* model_object = (*m_objects)[obj_idx];
wxDataViewItem item_obj = m_objects_model->GetItemById(obj_idx);
@ -2565,9 +2565,21 @@ void ObjectList::update_info_items(size_t obj_idx)
}
else if (shows && ! should_show) {
Unselect(item);
if (!selections)
Unselect(item);
m_objects_model->Delete(item);
Select(item_obj);
if (selections) {
if (selections->Index(item) != wxNOT_FOUND) {
// If info item was deleted from the list,
// it's need to be deleted from selection array, if it was there
selections->Remove(item);
// Select item_obj, if info_item doesn't exist for item anymore, but was selected
if (selections->Index(item_obj) == wxNOT_FOUND)
selections->Add(item_obj);
}
}
else
Select(item_obj);
}
}
}
@ -3760,7 +3772,7 @@ void ObjectList::update_object_list_by_printer_technology()
for (auto& object_item : object_items) {
// update custom supports info
update_info_items(m_objects_model->GetObjectIdByItem(object_item));
update_info_items(m_objects_model->GetObjectIdByItem(object_item), &sel);
// Update Settings Item for object
update_settings_item_and_selection(object_item, sel);

View file

@ -350,7 +350,7 @@ public:
void update_and_show_object_settings_item();
void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections);
void update_object_list_by_printer_technology();
void update_info_items(size_t obj_idx);
void update_info_items(size_t obj_idx, wxDataViewItemArray* selections = nullptr);
void instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idx);
void instances_to_separated_objects(const int obj_idx);

View file

@ -32,6 +32,7 @@
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Format/OBJ.hpp"
#include "../Utils/MacDarkMode.hpp"
namespace Slic3r {
@ -205,7 +206,11 @@ static void add_lock(wxImage& image)
static void add_default_image(wxImageList* img_list, bool is_system)
{
wxBitmap bmp = create_scaled_bitmap("cog", nullptr, IMG_PX_CNT, true);
int sz = IMG_PX_CNT;
#ifdef __APPLE__
sz /= mac_max_scaling_factor();
#endif
wxBitmap bmp = create_scaled_bitmap("cog", nullptr, sz, true);
if (is_system) {
wxImage image = bmp.ConvertToImage();
@ -232,10 +237,11 @@ static std::string get_dir_path(bool sys_dir)
#endif
}
static void generate_thumbnail_from_stl(const std::string& filename)
static void generate_thumbnail_from_model(const std::string& filename)
{
if (!boost::algorithm::iends_with(filename, ".stl")) {
BOOST_LOG_TRIVIAL(error) << "Found invalid file type in generate_thumbnail_from_stl() [" << filename << "]";
if (!boost::algorithm::iends_with(filename, ".stl") &&
!boost::algorithm::iends_with(filename, ".obj")) {
BOOST_LOG_TRIVIAL(error) << "Found invalid file type in generate_thumbnail_from_model() [" << filename << "]";
return;
}
@ -244,7 +250,7 @@ static void generate_thumbnail_from_stl(const std::string& filename)
model = Model::read_from_file(filename);
}
catch (std::exception&) {
BOOST_LOG_TRIVIAL(error) << "Error loading model from " << filename << " in generate_thumbnail_from_stl()";
BOOST_LOG_TRIVIAL(error) << "Error loading model from " << filename << " in generate_thumbnail_from_model()";
return;
}
@ -294,25 +300,28 @@ static void generate_thumbnail_from_stl(const std::string& filename)
void GalleryDialog::load_label_icon_list()
{
// load names from files
auto add_files_from_gallery = [](std::vector<Item>& items, bool sys_dir, std::string& dir_path)
auto add_files_from_gallery = [](std::vector<Item>& items, bool is_sys_dir, std::string& dir_path)
{
fs::path dir = get_dir(sys_dir);
fs::path dir = get_dir(is_sys_dir);
if (!fs::exists(dir))
return;
dir_path = get_dir_path(sys_dir);
dir_path = get_dir_path(is_sys_dir);
std::vector<std::string> sorted_names;
for (auto& dir_entry : fs::directory_iterator(dir))
if (TriangleMesh mesh; is_stl_file(dir_entry) && mesh.ReadSTLFile(dir_entry.path().string().c_str()))
sorted_names.push_back(dir_entry.path().stem().string());
for (auto& dir_entry : fs::directory_iterator(dir)) {
TriangleMesh mesh;
if ((is_gallery_file(dir_entry, ".stl") && mesh.ReadSTLFile(dir_entry.path().string().c_str())) ||
(is_gallery_file(dir_entry, ".obj") && load_obj(dir_entry.path().string().c_str(), &mesh) ) )
sorted_names.push_back(dir_entry.path().filename().string());
}
// sort the filename case insensitive
std::sort(sorted_names.begin(), sorted_names.end(), [](const std::string& a, const std::string& b)
{ return boost::algorithm::to_lower_copy(a) < boost::algorithm::to_lower_copy(b); });
for (const std::string& name : sorted_names)
items.push_back(Item{ name, sys_dir });
items.push_back(Item{ name, is_sys_dir });
};
wxBusyCursor busy;
@ -330,10 +339,24 @@ void GalleryDialog::load_label_icon_list()
std::string ext = ".png";
for (const auto& item : list_items) {
std::string img_name = (item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name + ext;
std::string stl_name = (item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name + ".stl";
if (!fs::exists(img_name))
generate_thumbnail_from_stl(stl_name);
fs::path model_path = fs::path((item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name);
std::string model_name = model_path.string();
model_path.replace_extension("png");
std::string img_name = model_path.string();
#if 0 // use "1" just in DEBUG mode to the generation of the thumbnails for the sistem shapes
bool can_generate_thumbnail = true;
#else
bool can_generate_thumbnail = !item.is_system;
#endif //DEBUG
if (!fs::exists(img_name)) {
if (can_generate_thumbnail)
generate_thumbnail_from_model(model_name);
else {
add_default_image(m_image_list, item.is_system);
continue;
}
}
wxImage image;
if (!image.CanRead(from_u8(img_name)) ||
@ -363,15 +386,15 @@ void GalleryDialog::load_label_icon_list()
void GalleryDialog::get_input_files(wxArrayString& input_files)
{
for (const Item& item : m_selected_items)
input_files.Add(from_u8(get_dir_path(item.is_system) + item.name + ".stl"));
input_files.Add(from_u8(get_dir_path(item.is_system) + item.name));
}
void GalleryDialog::add_custom_shapes(wxEvent& event)
{
wxArrayString input_files;
wxFileDialog dialog(this, _L("Choose one or more files (STL):"),
wxFileDialog dialog(this, _L("Choose one or more files (STL, OBJ):"),
from_u8(wxGetApp().app_config->get_last_dir()), "",
file_wildcards(FT_STL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
file_wildcards(FT_GALLERY), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() == wxID_OK)
dialog.GetPaths(input_files);
@ -398,8 +421,10 @@ void GalleryDialog::del_custom_shapes(wxEvent& event)
};
for (const Item& item : m_selected_items) {
remove_file(item.name + ".stl");
remove_file(item.name + ".png");
remove_file(item.name);
fs::path path = fs::path(item.name);
path.replace_extension("png");
remove_file(path.string());
}
update();
@ -490,26 +515,32 @@ bool GalleryDialog::load_files(const wxArrayString& input_files)
return false;
}
// Iterate through the source directory
// Iterate through the input files
for (size_t i = 0; i < input_files.size(); ++i) {
std::string input_file = into_u8(input_files.Item(i));
if (TriangleMesh mesh; !mesh.ReadSTLFile(input_file.c_str())) {
TriangleMesh mesh;
if (is_gallery_file(input_file, ".stl") && !mesh.ReadSTLFile(input_file.c_str())) {
show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "STL");
continue;
}
if (is_gallery_file(input_file, ".obj") && !load_obj(input_file.c_str(), &mesh)) {
show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "OBJ");
continue;
}
try {
fs::path current = fs::path(input_file);
if (!fs::exists(dest_dir / current.filename()))
fs::copy_file(current, dest_dir / current.filename());
else {
std::string filename = current.stem().string();
std::string filename = current.filename().string();
int file_idx = 0;
for (auto& dir_entry : fs::directory_iterator(dest_dir))
if (is_stl_file(dir_entry)) {
std::string name = dir_entry.path().stem().string();
if (is_gallery_file(dir_entry, ".stl") || is_gallery_file(dir_entry, ".obj")) {
std::string name = dir_entry.path().filename().string();
if (filename == name) {
if (file_idx == 0)
file_idx++;
@ -524,7 +555,7 @@ bool GalleryDialog::load_files(const wxArrayString& input_files)
file_idx = cur_idx+1;
}
if (file_idx > 0) {
filename += " (" + std::to_string(file_idx) + ").stl";
filename += " (" + std::to_string(file_idx) + ")." + (is_gallery_file(input_file, ".stl") ? "stl" : "obj");
fs::copy_file(current, dest_dir / filename);
}
}

View file

@ -359,10 +359,10 @@ void ObjectClipper::render_cut() const
clipper->set_plane(*m_clp);
clipper->set_transformation(trafo);
::glPushMatrix();
::glColor3f(1.0f, 0.37f, 0.0f);
glsafe(::glPushMatrix());
glsafe(::glColor3f(1.0f, 0.37f, 0.0f));
clipper->render_cut();
::glPopMatrix();
glsafe(::glPopMatrix());
++clipper_id;
}
@ -472,10 +472,10 @@ void SupportsClipper::render_cut() const
m_clipper->set_plane(*ocl->get_clipping_plane());
m_clipper->set_transformation(supports_trafo);
::glPushMatrix();
::glColor3f(1.0f, 0.f, 0.37f);
glsafe(::glPushMatrix());
glsafe(::glColor3f(1.0f, 0.f, 0.37f));
m_clipper->render_cut();
::glPopMatrix();
glsafe(::glPopMatrix());
}

View file

@ -434,6 +434,12 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
e.Skip();
temp->GetToolTip()->Enable(true);
#endif // __WXGTK__
// Remove all leading and trailing spaces from the input
std::string trimed_str, str = trimed_str = temp->GetValue().ToStdString();
boost::trim(trimed_str);
if (trimed_str != str)
temp->SetValue(trimed_str);
TextCtrl* field = dynamic_cast<TextCtrl*>(printhost_field);
if (field)
field->propagate_value();

View file

@ -2389,8 +2389,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
//wxMessageDialog msg_dlg(q, _L(
MessageDialog msg_dlg(q, _L(
"This file contains several objects positioned at multiple heights.\n"
"Instead of considering them as multiple objects, should I consider\n"
"this file as a single object having multiple parts?") + "\n",
"Instead of considering them as multiple objects, should \n"
"should the file be loaded as a single object having multiple parts?") + "\n",
_L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES) {
model.convert_multipart_object(nozzle_dmrs->values.size());
@ -3212,9 +3212,10 @@ void Plater::priv::replace_with_stl()
new_volume->set_material_id(old_volume->material_id());
new_volume->set_transformation(old_volume->get_transformation());
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
assert(! old_volume->source.is_converted_from_inches || ! old_volume->source.is_converted_from_meters);
if (old_volume->source.is_converted_from_inches)
new_volume->convert_from_imperial_units();
if (old_volume->source.is_converted_from_meters)
else if (old_volume->source.is_converted_from_meters)
new_volume->convert_from_meters();
new_volume->supported_facets.assign(old_volume->supported_facets);
new_volume->seam_facets.assign(old_volume->seam_facets);
@ -3420,13 +3421,11 @@ void Plater::priv::reload_from_disk()
new_volume->set_material_id(old_volume->material_id());
new_volume->set_transformation(old_volume->get_transformation());
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
assert(! old_volume->source.is_converted_from_inches || ! old_volume->source.is_converted_from_meters);
if (old_volume->source.is_converted_from_inches)
new_volume->convert_from_imperial_units();
if (old_volume->source.is_converted_from_meters)
else if (old_volume->source.is_converted_from_meters)
new_volume->convert_from_meters();
new_volume->supported_facets.assign(old_volume->supported_facets);
new_volume->seam_facets.assign(old_volume->seam_facets);
new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets);
std::swap(old_model_object->volumes[sel_v.volume_idx], old_model_object->volumes.back());
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
if (!sinking)
@ -5223,7 +5222,7 @@ void Plater::convert_unit(ConversionType conv_type)
void Plater::toggle_layers_editing(bool enable)
{
if (canvas3D()->is_layers_editing_enabled() != enable)
wxPostEvent(canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING));
canvas3D()->force_main_toolbar_left_action(canvas3D()->get_main_toolbar_item_id("layersediting"));
}
void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes)

View file

@ -27,6 +27,7 @@
#include "MainFrame.hpp"
#include "libslic3r/AppConfig.hpp"
#include "NotificationManager.hpp"
#include "ExtraRenderers.hpp"
namespace fs = boost::filesystem;
@ -214,14 +215,25 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
}
job_list = new wxDataViewListCtrl(this, wxID_ANY);
// MSW DarkMode: workaround for the selected item in the list
auto append_text_column = [this](const wxString& label, int width, wxAlignment align = wxALIGN_LEFT,
int flags = wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE) {
#ifdef _WIN32
job_list->AppendColumn(new wxDataViewColumn(label, new TextRenderer(), job_list->GetColumnCount(), width, align, flags));
#else
job_list->AppendTextColumn(label, wxDATAVIEW_CELL_INERT, width, align, flags);
#endif
};
// Note: Keep these in sync with Column
job_list->AppendTextColumn(_L("ID"), wxDATAVIEW_CELL_INERT, widths[0], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
job_list->AppendTextColumn(_L("Status"), wxDATAVIEW_CELL_INERT, widths[2], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
job_list->AppendTextColumn(_L("Host"), wxDATAVIEW_CELL_INERT, widths[3], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
job_list->AppendTextColumn(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), wxDATAVIEW_CELL_INERT, widths[4], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
job_list->AppendTextColumn(_L("Filename"), wxDATAVIEW_CELL_INERT, widths[5], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
job_list->AppendTextColumn(_L("Error Message"), wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN);
append_text_column(_L("ID"), widths[0]);
job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
append_text_column(_L("Status"),widths[2]);
append_text_column(_L("Host"), widths[3]);
append_text_column(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), widths[4]);
append_text_column(_L("Filename"), widths[5]);
append_text_column(_L("Error Message"), -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN);
auto *btnsizer = new wxBoxSizer(wxHORIZONTAL);
btn_cancel = new wxButton(this, wxID_DELETE, _L("Cancel selected"));