text: #include "PrintHostDialogs.hpp"
  
  #include <algorithm>
  
  #include <wx/frame.h>
  #include <wx/progdlg.h>
  #include <wx/sizer.h>
  #include <wx/stattext.h>
  #include <wx/textctrl.h>
  #include <wx/checkbox.h>
  #include <wx/button.h>
  #include <wx/dataview.h>
  #include <wx/wupdlock.h>
  #include <wx/debug.h>
  
  #include "GUI.hpp"
  #include "GUI_App.hpp"
  #include "MsgDialog.hpp"
  #include "I18N.hpp"
  #include "../Utils/PrintHost.hpp"
  #include "wxExtensions.hpp"
  #include "libslic3r/AppConfig.hpp"
  
  namespace fs = boost::filesystem;
  
  namespace Slic3r {
  namespace GUI {
  
  static const char *CONFIG_KEY_PATH  = "printhost_path";
  static const char *CONFIG_KEY_PRINT = "printhost_print";
  
  PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print)
      : MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE)
      , txt_filename(new wxTextCtrl(this, wxID_ANY))
      , box_print(can_start_print ? new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload"))) : nullptr)
  {
  #ifdef __APPLE__
      txt_filename->OSXDisableAllSmartSubstitutions();
  #endif
      const AppConfig *app_config = wxGetApp().app_config;
  
      auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed.")));
      label_dir_hint->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
  
      content_sizer->Add(txt_filename, 0, wxEXPAND);
      content_sizer->Add(label_dir_hint);
      content_sizer->AddSpacer(VERT_SPACING);
      if (box_print != nullptr) {
          content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
          box_print->SetValue(app_config->get("recent", CONFIG_KEY_PRINT) == "1");
      }
  
      btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
  
  
      wxString recent_path = from_u8(app_config->get("recent", CONFIG_KEY_PATH));
      if (recent_path.Length() > 0 && recent_path[recent_path.Length() - 1] != '/') {
          recent_path += '/';
      }
      const auto recent_path_len = recent_path.Length();
      recent_path += path.filename().wstring();
      wxString stem(path.stem().wstring());
      const auto stem_len = stem.Length();
  
      txt_filename->SetValue(recent_path);
      txt_filename->SetFocus();
  
      Fit();
  
      Bind(wxEVT_SHOW, [=](const wxShowEvent &) {
          // Another similar case where the function only works with EVT_SHOW + CallAfter,
          // this time on Mac.
          CallAfter([=]() {
              txt_filename->SetSelection(recent_path_len, recent_path_len + stem_len);
          });
      });
  }
  
  fs::path PrintHostSendDialog::filename() const
  {
      return into_path(txt_filename->GetValue());
  }
  
  bool PrintHostSendDialog::start_print() const
  {
      return box_print != nullptr ? box_print->GetValue() : false;
  }
  
  void PrintHostSendDialog::EndModal(int ret)
  {
      if (ret == wxID_OK) {
          // Persist path and print settings
          wxString path = txt_filename->GetValue();
          int last_slash = path.Find('/', true);
  		if (last_slash == wxNOT_FOUND)
  			path.clear();
  		else
              path = path.SubString(0, last_slash);
  		AppConfig *app_config = wxGetApp().app_config;
  		app_config->set("recent", CONFIG_KEY_PATH, into_u8(path));
          app_config->set("recent", CONFIG_KEY_PRINT, start_print() ? "1" : "0");
      }
  
      MsgDialog::EndModal(ret);
  }
  
  
  
  wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
  wxDEFINE_EVENT(EVT_PRINTHOST_ERROR,    PrintHostQueueDialog::Event);
  wxDEFINE_EVENT(EVT_PRINTHOST_CANCEL,   PrintHostQueueDialog::Event);
  
  PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id)
      : wxEvent(winid, eventType)
      , job_id(job_id)
  {}
  
  PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, int progress)
      : wxEvent(winid, eventType)
      , job_id(job_id)
      , progress(progress)
  {}
  
  PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, wxString error)
      : wxEvent(winid, eventType)
      , job_id(job_id)
      , error(std::move(error))
  {}
  
  wxEvent *PrintHostQueueDialog::Event::Clone() const
  {
      return new Event(*this);
  }
  
  PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
      : DPIDialog(parent, wxID_ANY, _(L("Print host upload queue")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
      , on_progress_evt(this, EVT_PRINTHOST_PROGRESS, &PrintHostQueueDialog::on_progress, this)
      , on_error_evt(this, EVT_PRINTHOST_ERROR, &PrintHostQueueDialog::on_error, this)
      , on_cancel_evt(this, EVT_PRINTHOST_CANCEL, &PrintHostQueueDialog::on_cancel, this)
  {
      const auto em = GetTextExtent("m").x;
  
      auto *topsizer = new wxBoxSizer(wxVERTICAL);
  
      job_list = new wxDataViewListCtrl(this, wxID_ANY);
      // Note: Keep these in sync with Column
      job_list->AppendTextColumn(_(L("ID")), wxDATAVIEW_CELL_INERT);
      job_list->AppendProgressColumn(_(L("Progress")), wxDATAVIEW_CELL_INERT);
      job_list->AppendTextColumn(_(L("Status")), wxDATAVIEW_CELL_INERT);
      job_list->AppendTextColumn(_(L("Host")), wxDATAVIEW_CELL_INERT);
      job_list->AppendTextColumn(_(L("Filename")), wxDATAVIEW_CELL_INERT);
      job_list->AppendTextColumn(_(L("Error Message")), wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN);
  
      auto *btnsizer = new wxBoxSizer(wxHORIZONTAL);
      btn_cancel = new wxButton(this, wxID_DELETE, _(L("Cancel selected")));
      btn_cancel->Disable();
      btn_error = new wxButton(this, wxID_ANY, _(L("Show error message")));
      btn_error->Disable();
      auto *btn_close = new wxButton(this, wxID_CANCEL, _(L("Close")));  // Note: The label needs to be present, otherwise we get accelerator bugs on Mac
      btnsizer->Add(btn_cancel, 0, wxRIGHT, SPACING);
      btnsizer->Add(btn_error, 0);
      btnsizer->AddStretchSpacer();
      btnsizer->Add(btn_close);
  
      topsizer->Add(job_list, 1, wxEXPAND | wxBOTTOM, SPACING);
      topsizer->Add(btnsizer, 0, wxEXPAND);
      SetSizer(topsizer);
  
      SetSize(wxSize(HEIGHT * em, WIDTH * em));
  
      job_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent&) { on_list_select(); });
  
      btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
          int selected = job_list->GetSelectedRow();
          if (selected == wxNOT_FOUND) { return; }
  
          const JobState state = get_state(selected);
          if (state < ST_ERROR) {
              // TODO: cancel
              GUI::wxGetApp().printhost_job_queue().cancel(selected);
          }
      });
  
      btn_error->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
          int selected = job_list->GetSelectedRow();
          if (selected == wxNOT_FOUND) { return; }
          GUI::show_error(nullptr, job_list->GetTextValue(selected, COL_ERRORMSG));
      });
  }
  
  void PrintHostQueueDialog::append_job(const PrintHostJob &job)
  {
      wxCHECK_RET(!job.empty(), "PrintHostQueueDialog: Attempt to append an empty job");
  
      wxVector<wxVariant> fields;
      fields.push_back(wxVariant(wxString::Format("%d", job_list->GetItemCount() + 1)));
      fields.push_back(wxVariant(0));
      fields.push_back(wxVariant(_(L("Enqueued"))));
      fields.push_back(wxVariant(job.printhost->get_host()));
      fields.push_back(wxVariant(job.upload_data.upload_path.string()));
      fields.push_back(wxVariant(""));
      job_list->AppendItem(fields, static_cast<wxUIntPtr>(ST_NEW));
  }
  
  void PrintHostQueueDialog::on_dpi_changed(const wxRect &suggested_rect)
  {
      const int& em = em_unit();
  
      msw_buttons_rescale(this, em, { wxID_DELETE, wxID_CANCEL, btn_error->GetId() });
  
      SetMinSize(wxSize(HEIGHT * em, WIDTH * em));
  
      Fit();
      Refresh();
  }
  
  PrintHostQueueDialog::JobState PrintHostQueueDialog::get_state(int idx)
  {
      wxCHECK_MSG(idx >= 0 && idx < job_list->GetItemCount(), ST_ERROR, "Out of bounds access to job list");
      return static_cast<JobState>(job_list->GetItemData(job_list->RowToItem(idx)));
  }
  
  void PrintHostQueueDialog::set_state(int idx, JobState state)
  {
      wxCHECK_RET(idx >= 0 && idx < job_list->GetItemCount(), "Out of bounds access to job list");
      job_list->SetItemData(job_list->RowToItem(idx), static_cast<wxUIntPtr>(state));
  
      switch (state) {
          case ST_NEW:        job_list->SetValue(_(L("Enqueued")), idx, COL_STATUS); break;
          case ST_PROGRESS:   job_list->SetValue(_(L("Uploading")), idx, COL_STATUS); break;
          case ST_ERROR:      job_list->SetValue(_(L("Error")), idx, COL_STATUS); break;
          case ST_CANCELLING: job_list->SetValue(_(L("Cancelling")), idx, COL_STATUS); break;
          case ST_CANCELLED:  job_list->SetValue(_(L("Cancelled")), idx, COL_STATUS); break;
          case ST_COMPLETED:  job_list->SetValue(_(L("Completed")), idx, COL_STATUS); break;
      }
  }
  
  void PrintHostQueueDialog::on_list_select()
  {
      int selected = job_list->GetSelectedRow();
      if (selected != wxNOT_FOUND) {
          const JobState state = get_state(selected);
          btn_cancel->Enable(state < ST_ERROR);
          btn_error->Enable(state == ST_ERROR);
          Layout();
      } else {
          btn_cancel->Disable();
      }
  }
  
  void PrintHostQueueDialog::on_progress(Event &evt)
  {
      wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list");
  
      if (evt.progress < 100) {
          set_state(evt.job_id, ST_PROGRESS);
          job_list->SetValue(wxVariant(evt.progress), evt.job_id, COL_PROGRESS);
      } else {
          set_state(evt.job_id, ST_COMPLETED);
          job_list->SetValue(wxVariant(100), evt.job_id, COL_PROGRESS);
      }
  
      on_list_select();
  }
  
  void PrintHostQueueDialog::on_error(Event &evt)
  {
      wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list");
  
      set_state(evt.job_id, ST_ERROR);
  
      auto errormsg = from_u8((boost::format("%1%\n%2%") % _utf8(L("Error uploading to print host:")) % std::string(evt.error.ToUTF8())).str());
      job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS);
      job_list->SetValue(wxVariant(errormsg), evt.job_id, COL_ERRORMSG);    // Stashes the error message into a hidden column for later
  
      on_list_select();
  
      GUI::show_error(nullptr, errormsg);
  }
  
  void PrintHostQueueDialog::on_cancel(Event &evt)
  {
      wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list");
  
      set_state(evt.job_id, ST_CANCELLED);
      job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS);
  
      on_list_select();
  }
  
  
  }}
  