/* 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 class Popup : public Gtk::Window { public: Popup(const Glib::RefPtr& 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& display); virtual Gtk::Window* query_for_toplevel(const Glib::RefPtr& 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 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 m_name; Gtk::TreeModelColumn< Glib::RefPtr > 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 m_refListStore_Display; Gtk::Box m_ButtonBox_Display; Gtk::Button m_Button_Display_Open, m_Button_Display_Close; Glib::RefPtr m_refSizeGroup_Display; Glib::RefPtr m_refCurrentDisplay; 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_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 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() { } void Example_ChangeDisplay::setup_frame(Gtk::Frame& frame, Gtk::TreeView& treeview, Gtk::Box& buttonbox) { Gtk::Box* pHBox = Gtk::make_managed(Gtk::ORIENTATION_HORIZONTAL, 8); pHBox->set_border_width(8); frame.add(*pHBox); Gtk::ScrolledWindow* pScrolledWindow = Gtk::make_managed(); 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 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 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 display) { for(auto row : m_refListStore_Display->children()) { Glib::RefPtr 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 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 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 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& screen, const Glib::ustring& prompt) { auto device = Glib::wrap(gtk_get_current_event_device(), true); if (!device) return nullptr; Glib::RefPtr refDisplay = screen->get_display(); std::unique_ptr pPopup(new Popup(screen, prompt)); pPopup->show(); Glib::RefPtr cursor = Gdk::Cursor::create(refDisplay, Gdk::CROSSHAIR); Gtk::Window* toplevel = nullptr; auto seat = device->get_seat(); if (seat && seat->grab(pPopup->get_window(), Gdk::SEAT_CAPABILITY_ALL_POINTING, false, cursor) == Gdk::GRAB_SUCCESS) { m_popup_clicked = false; 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(find_toplevel_at_pointer(refDisplay)); if (toplevel == pPopup.get()) toplevel = nullptr; } refDisplay->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& /* display */) { auto device = Glib::wrap(gtk_get_current_event_device(), true); if (!device) return nullptr; auto refPointerWindow = device->get_window_at_position(); if (refPointerWindow) { // The user data field of a GdkWindow is used to store a pointer // to the widget that created it. gpointer user_data = nullptr; refPointerWindow->get_user_data(&user_data); GtkWidget* cWidget = static_cast(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& 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(); }