/* Change Display
 *
 * Demonstrates migrating a window between different displays.
 * A display is a mouse and keyboard with some number of
 * associated monitors. The neat thing about having multiple
 * displays is that they can be on completely separate
 * computers, as long as there is a network connection to the
 * computer where the application is running.
 *
 * Only some of the windowing systems where GTK+ runs have the
 * concept of multiple displays. (The X Window System is the
 * main example.) Other windowing systems can only handle one
 * keyboard and mouse, and combine all monitors into
 * a single display.
 *
 * This is a moderately complex example, and demonstrates:
 *
 *  - Tracking the currently open displays
 *
 *  - Changing the display for a window
 *
 *  - Letting the user choose a window by clicking on it
 *
 *  - Using Gtk::ListStore and Gtk::TreeView
 *
 *  - Using Gtk::Dialog
 */

#include <gtkmm.h>


class Popup : public Gtk::Window
{
public:
  Popup(const Glib::RefPtr<Gdk::Screen>& screen, const Glib::ustring& prompt);
  ~Popup() override;

protected:
  Gtk::Frame m_Frame;
  Gtk::Label m_Label;
};

class Example_ChangeDisplay : public Gtk::Dialog
{
public:
  Example_ChangeDisplay();
  ~Example_ChangeDisplay() override;

protected:
  virtual void setup_frame(Gtk::Frame& frame, Gtk::TreeView& treeview, Gtk::Box& buttonbox);

  virtual void initialize_displays();
  virtual void query_change_display();
  virtual Gtk::Widget* find_toplevel_at_pointer(const Glib::RefPtr<Gdk::Display>& display);
  virtual Gtk::Window* query_for_toplevel(const Glib::RefPtr<Gdk::Screen>& screen, const Glib::ustring& prompt);

  //signal handlers:
  virtual void on_button_display_open();
  virtual void on_button_display_close();

  virtual void on_treeview_display_selection_changed();

  virtual void on_display_closed(bool is_error, Glib::RefPtr<Gdk::Display> display);

  virtual bool on_popup_button_release_event(GdkEventButton* event);

  void on_response(int response_id) override;


  class ModelColumns_Display : public Gtk::TreeModelColumnRecord
  {
  public:
    Gtk::TreeModelColumn<Glib::ustring> m_name;
    Gtk::TreeModelColumn< Glib::RefPtr<Gdk::Display> > m_display; //hidden

    ModelColumns_Display() { add(m_name); add(m_display); }
  };
  ModelColumns_Display m_columns_display;

  Gtk::Box m_VBox;
  Gtk::Frame m_Frame_Display;
  Gtk::TreeView m_TreeView_Display;
  Glib::RefPtr<Gtk::ListStore> m_refListStore_Display;
  Gtk::Box m_ButtonBox_Display;

  Gtk::Button m_Button_Display_Open, m_Button_Display_Close;

  Glib::RefPtr<Gtk::SizeGroup> m_refSizeGroup_Display;


  Glib::RefPtr<Gdk::Display> m_refCurrentDisplay;

  Popup* m_pPopup;

  bool m_popup_clicked;
};




Example_ChangeDisplay::Example_ChangeDisplay()
: Gtk::Dialog("Change Display"),
  m_VBox(Gtk::ORIENTATION_VERTICAL, 5),
  m_Frame_Display("Display"),
  m_ButtonBox_Display(Gtk::ORIENTATION_VERTICAL, 5),
  m_Button_Display_Open("_Open...", true), m_Button_Display_Close("_Close...", true),
  m_pPopup(nullptr),
  m_popup_clicked(false)
{
  add_button("_Close", Gtk::RESPONSE_CLOSE);
  add_button("Change", Gtk::RESPONSE_OK);

  set_default_size(300, 400);

  m_VBox.set_border_width(8);
  get_content_area()->pack_start(m_VBox);


  //Display:
  {
    setup_frame(m_Frame_Display, m_TreeView_Display, m_ButtonBox_Display);
    m_ButtonBox_Display.pack_start(m_Button_Display_Open, Gtk::PACK_SHRINK);
    m_Button_Display_Open.signal_clicked().connect( sigc::mem_fun(*this, &Example_ChangeDisplay::on_button_display_open) );
    m_ButtonBox_Display.pack_start(m_Button_Display_Close, Gtk::PACK_SHRINK);
    m_Button_Display_Close.signal_clicked().connect( sigc::mem_fun(*this, &Example_ChangeDisplay::on_button_display_close) );

    //Setup TreeView:
    m_refListStore_Display = Gtk::ListStore::create(m_columns_display);
    m_TreeView_Display.set_model(m_refListStore_Display);
    m_TreeView_Display.append_column("Name", m_columns_display.m_name);

    //Connect signal:
    Glib::RefPtr<Gtk::TreeView::Selection> refSelection = m_TreeView_Display.get_selection();
    refSelection->signal_changed().connect( sigc::mem_fun(*this, &Example_ChangeDisplay::on_treeview_display_selection_changed) );

    m_VBox.pack_start(m_Frame_Display);

    m_refSizeGroup_Display = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
    m_refSizeGroup_Display->add_widget(m_ButtonBox_Display);
  }

  initialize_displays();

  show_all();
}

Example_ChangeDisplay::~Example_ChangeDisplay()
{
  if(m_pPopup)
  {
    delete m_pPopup;
    m_pPopup = nullptr;
  }
}

void Example_ChangeDisplay::setup_frame(Gtk::Frame& frame, Gtk::TreeView& treeview, Gtk::Box& buttonbox)
{
  Gtk::Box* pHBox = Gtk::manage( new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 8) );
  pHBox->set_border_width(8);
  frame.add(*pHBox);

  Gtk::ScrolledWindow* pScrolledWindow = Gtk::manage( new Gtk::ScrolledWindow() );
  pScrolledWindow->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
  pScrolledWindow->set_shadow_type(Gtk::SHADOW_IN);
  pHBox->pack_start(*pScrolledWindow);

  treeview.set_headers_visible(false);
  pScrolledWindow->add(treeview);

  Glib::RefPtr<Gtk::TreeView::Selection> refSelection = treeview.get_selection();
  refSelection->set_mode(Gtk::SELECTION_BROWSE);

  pHBox->pack_start(buttonbox, Gtk::PACK_SHRINK);
}

void Example_ChangeDisplay::initialize_displays()
{

#ifndef G_OS_WIN32
  Glib::RefPtr<Gdk::DisplayManager> refDisplayManager = Gdk::DisplayManager::get();

  for(auto refDisplay : refDisplayManager->list_displays())
  {
    Gtk::TreeRow row = *(m_refListStore_Display->append());
    row[m_columns_display.m_name] = refDisplay->get_name();;
    row[m_columns_display.m_display] = refDisplay;

    refDisplay->signal_closed().connect(
      sigc::bind<-1>( sigc::mem_fun(*this, &Example_ChangeDisplay::on_display_closed), refDisplay) );
  }

#endif
}

void Example_ChangeDisplay::on_display_closed(bool /* is_error */, Glib::RefPtr<Gdk::Display> display)
{
  for(auto row : m_refListStore_Display->children())
  {
    Glib::RefPtr<Gdk::Display> refDisplay = row[m_columns_display.m_display];
    if(refDisplay == display)
    {
      m_refListStore_Display->erase(row);
    }
  }
}

void Example_ChangeDisplay::on_button_display_open()
{
  Gtk::Dialog dialog("Open Display", true);
  dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL);
  dialog.add_button("_OK", Gtk::RESPONSE_OK);

  dialog.set_default_response(Gtk::RESPONSE_OK);

  Gtk::Entry entry;
  entry.set_activates_default();

  Gtk::Label label("Please enter the name of\nthe new display\n");

  dialog.get_content_area()->add(label);
  dialog.get_content_area()->add(entry);

  entry.grab_focus();

  dialog.show_all_children();

  Glib::RefPtr<Gdk::Display> refResult;
  while (!refResult)
  {
    gint response_id = dialog.run();
    if (response_id != Gtk::RESPONSE_OK)
      break;

    Glib::ustring new_screen_name = entry.get_text();

    if( !new_screen_name.empty() )
    {
      refResult = Gdk::Display::open(new_screen_name);
      if (!refResult)
      {
        label.set_text("Can't open display.\nplease try another one\n");
      }
    }
  }
}

void Example_ChangeDisplay::on_button_display_close()
{
  if(m_refCurrentDisplay)
    m_refCurrentDisplay->close();
}

void Example_ChangeDisplay::on_treeview_display_selection_changed()
{
  Glib::RefPtr<Gtk::TreeSelection> refSelection = m_TreeView_Display.get_selection();
  Gtk::TreeModel::iterator iter = refSelection->get_selected();
  if(iter)
    m_refCurrentDisplay = (*iter)[m_columns_display.m_display];
  else
    m_refCurrentDisplay.reset();
}


/* Prompts the user for a toplevel window to move, and then moves
 * that window to the currently selected display
 */
void Example_ChangeDisplay::query_change_display()
{
  Glib::RefPtr<Gdk::Screen> refScreen = get_screen();
  Gtk::Window* pTopLevel = query_for_toplevel(refScreen,
   "Please select the toplevel\n"
   "to move to the new screen");

  if (pTopLevel)
    pTopLevel->set_screen( m_refCurrentDisplay->get_default_screen() );
  else
    refScreen->get_display()->beep();
}


void Example_ChangeDisplay::on_response(int response_id)
{
  if (response_id == Gtk::RESPONSE_OK)
    query_change_display();
  else
    hide();
}



/* Asks the user to click on a window, then waits for them click
 * the mouse. When the mouse is released, returns the toplevel
 * window under the pointer, or NULL, if there is none.
 */
Gtk::Window* Example_ChangeDisplay::query_for_toplevel(const Glib::RefPtr<Gdk::Screen>& screen, const Glib::ustring& prompt)
{
  Glib::RefPtr<Gdk::Display> refDisplay = screen->get_display();

  if(m_pPopup)
  {
    delete m_pPopup;
    m_pPopup = nullptr;
  }

  m_pPopup = new Popup(screen, prompt);

  m_pPopup->show();

  Glib::RefPtr<Gdk::Cursor> cursor = Gdk::Cursor::create(refDisplay, Gdk::CROSSHAIR);

  Gtk::Window* toplevel = nullptr;

  //TODO: Find a suitable replacement for this:
  //const GdkGrabStatus grabbed =  m_pPopup->get_window()->grab(false, Gdk::BUTTON_RELEASE_MASK, cursor, GDK_CURRENT_TIME);
  //Check it when the GTK+ example has been updated and file a bug about the unhelpful deprecation comment.
  const Gdk::GrabStatus grabbed = Gdk::GRAB_SUCCESS;
  if(grabbed == Gdk::GRAB_SUCCESS )
  {
    m_popup_clicked = false;
    m_pPopup->signal_button_release_event().connect( sigc::mem_fun(*this, &Example_ChangeDisplay::on_popup_button_release_event) );


    // Process events until clicked is set by button_release_event_cb.
    // We pass in may_block=true since we want to wait if there
    // are no events currently.
    while (!m_popup_clicked)
      Gtk::Main::iteration(true);

    toplevel = dynamic_cast<Gtk::Window*>(find_toplevel_at_pointer(screen->get_display()));
    if (toplevel == m_pPopup)
       toplevel = nullptr;
  }

  Gdk::flush(); /* Really release the grab */

  return toplevel;
}

// Finds the toplevel window under the mouse pointer, if any.
Gtk::Widget* Example_ChangeDisplay::find_toplevel_at_pointer(const Glib::RefPtr<Gdk::Display>& /* display */)
{
  //TODO: This needs to use Device::get_window_at_position(), when we can figure that out.
  //See https://bugzilla.gnome.org/show_bug.cgi?id=638907
  /*
  Glib::RefPtr<Gdk::Window> refPointerWindow = display->get_window_at_pointer();

  if (refPointerWindow)
  {
    // The user data field of a GdkWindow is used to store a pointer
    // to the widget that created it.
    GtkWidget* cWidget = nullptr;
    gpointer* user_data = nullptr;
    refPointerWindow->get_user_data(user_data);
    cWidget = (GtkWidget*)user_data;

    Gtk::Widget* pWidget = Glib::wrap(cWidget);
    if(pWidget)
      return pWidget->get_toplevel();
  }
  */

  return nullptr;
}


bool Example_ChangeDisplay::on_popup_button_release_event(GdkEventButton* /* event */)
{
  m_popup_clicked = true;
  return true;
}

Popup::Popup(const Glib::RefPtr<Gdk::Screen>& screen, const Glib::ustring& prompt)
: Gtk::Window(Gtk::WINDOW_POPUP),
  m_Label(prompt)
{
  set_screen(screen);
  set_modal(true);
  set_position(Gtk::WIN_POS_CENTER);

  m_Frame.set_shadow_type(Gtk::SHADOW_OUT);
  add(m_Frame);

  m_Label.property_margin() = 10;
  m_Frame.add(m_Label);

  show_all_children();
}

Popup::~Popup()
{
}


// called by DemoWindow
Gtk::Window* do_change_display()
{
  return new Example_ChangeDisplay();
}
