// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/panels/panel_browser_window_gtk.h" #include "base/bind.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/gtk/browser_titlebar.h" #include "chrome/browser/ui/gtk/custom_button.h" #include "chrome/browser/ui/gtk/gtk_theme_service.h" #include "chrome/browser/ui/gtk/nine_box.h" #include "chrome/browser/ui/panels/panel.h" #include "chrome/browser/ui/panels/panel_bounds_animation.h" #include "chrome/browser/ui/panels/panel_browser_titlebar_gtk.h" #include "chrome/browser/ui/panels/panel_drag_gtk.h" #include "chrome/browser/ui/panels/panel_manager.h" #include "chrome/browser/ui/panels/panel_strip.h" #include "chrome/common/chrome_notification_types.h" #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/notification_service.h" #include "grit/theme_resources_standard.h" #include "grit/ui_resources.h" #include "third_party/skia/include/core/SkShader.h" #include "ui/gfx/canvas.h" #include "ui/gfx/image/cairo_cached_surface.h" #include "ui/gfx/image/image.h" #include "ui/gfx/skia_util.h" using content::NativeWebKeyboardEvent; using content::WebContents; namespace { // Colors used to draw titlebar and frame for drawing attention under default // theme. It is also used in non-default theme since attention color is not // defined in the theme. const SkColor kAttentionBackgroundColorStart = SkColorSetRGB(0xff, 0xab, 0x57); const SkColor kAttentionBackgroundColorEnd = SkColorSetRGB(0xe6, 0x9a, 0x4e); // Set minimium width for window really small. const int kMinWindowWidth = 26; gfx::Image* CreateGradientImage(SkColor start_color, SkColor end_color) { // Though the height of titlebar, used for creating gradient, cannot be // pre-determined, we use a reasonably bigger value that is obtained from // the experimentation and should work for most cases. const int gradient_size = 32; SkShader* shader = gfx::CreateGradientShader( 0, gradient_size, start_color, end_color); SkPaint paint; paint.setStyle(SkPaint::kFill_Style); paint.setAntiAlias(true); paint.setShader(shader); shader->unref(); gfx::Canvas canvas(gfx::Size(1, gradient_size), true); canvas.DrawRect(gfx::Rect(0, 0, 1, gradient_size), paint); return new gfx::Image(canvas.ExtractBitmap()); } gfx::Image* GetAttentionBackgroundImage() { static gfx::Image* image = NULL; if (!image) { image = CreateGradientImage(kAttentionBackgroundColorStart, kAttentionBackgroundColorEnd); } return image; } } // namespace NativePanel* Panel::CreateNativePanel(Browser* browser, Panel* panel, const gfx::Rect& bounds) { PanelBrowserWindowGtk* panel_browser_window_gtk = new PanelBrowserWindowGtk(browser, panel, bounds); panel_browser_window_gtk->Init(); return panel_browser_window_gtk; } PanelBrowserWindowGtk::PanelBrowserWindowGtk(Browser* browser, Panel* panel, const gfx::Rect& bounds) : BrowserWindowGtk(browser), panel_(panel), bounds_(bounds), is_drawing_attention_(false) { } PanelBrowserWindowGtk::~PanelBrowserWindowGtk() { } void PanelBrowserWindowGtk::Init() { BrowserWindowGtk::Init(); // Keep the window always on top. gtk_window_set_keep_above(window(), TRUE); // Show the window on all the virtual desktops. gtk_window_stick(window()); // Do not show an icon in the task bar. Window operations such as close, // minimize etc. can only be done from the panel UI. gtk_window_set_skip_taskbar_hint(window(), TRUE); // Only need to watch for titlebar button release event. BrowserWindowGtk // already watches for button-press-event and determines if titlebar or // window edge was hit. g_signal_connect(titlebar_widget(), "button-release-event", G_CALLBACK(OnTitlebarButtonReleaseEventThunk), this); registrar_.Add( this, chrome::NOTIFICATION_WINDOW_CLOSED, content::Source(window())); } bool PanelBrowserWindowGtk::ShouldDrawContentDropShadow() const { return !panel_->IsMinimized(); } BrowserTitlebar* PanelBrowserWindowGtk::CreateBrowserTitlebar() { return new PanelBrowserTitlebarGtk(this, window()); } PanelBrowserTitlebarGtk* PanelBrowserWindowGtk::GetPanelTitlebar() const { return static_cast(titlebar()); } PanelBrowserWindowGtk::PaintState PanelBrowserWindowGtk::GetPaintState() const { if (is_drawing_attention_) return PAINT_FOR_ATTENTION; return IsActive() ? PAINT_AS_ACTIVE : PAINT_AS_INACTIVE; } bool PanelBrowserWindowGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) { // Only detect the window edge when panels can be resized by the user. // This method is used by the base class to detect when the cursor has // hit the window edge in order to change the cursor to a resize cursor // and to detect when to initiate a resize drag. panel::Resizability resizability = panel_->CanResizeByMouse(); if (panel::NOT_RESIZABLE == resizability) return false; if (!BrowserWindowGtk::GetWindowEdge(x, y, edge)) return FALSE; // Special handling if bottom edge is not resizable. if (panel::RESIZABLE_ALL_SIDES_EXCEPT_BOTTOM == resizability) { if (*edge == GDK_WINDOW_EDGE_SOUTH) return FALSE; if (*edge == GDK_WINDOW_EDGE_SOUTH_WEST) *edge = GDK_WINDOW_EDGE_WEST; else if (*edge == GDK_WINDOW_EDGE_SOUTH_EAST) *edge = GDK_WINDOW_EDGE_EAST; } return TRUE; } GdkRegion* PanelBrowserWindowGtk::GetWindowShape(int width, int height) const { // For panels, only top corners are rounded. The bottom corners are not // rounded because panels are aligned to the bottom edge of the screen. GdkRectangle top_top_rect = { 3, 0, width - 6, 1 }; GdkRectangle top_mid_rect = { 1, 1, width - 2, 2 }; GdkRectangle mid_rect = { 0, 3, width, height - 3 }; GdkRegion* mask = gdk_region_rectangle(&top_top_rect); gdk_region_union_with_rect(mask, &top_mid_rect); gdk_region_union_with_rect(mask, &mid_rect); return mask; } void PanelBrowserWindowGtk::DrawCustomFrameBorder(GtkWidget* widget) { static NineBox* custom_frame_border = NULL; if (!custom_frame_border) { custom_frame_border = new NineBox(IDR_WINDOW_TOP_LEFT_CORNER, IDR_WINDOW_TOP_CENTER, IDR_WINDOW_TOP_RIGHT_CORNER, IDR_WINDOW_LEFT_SIDE, 0, IDR_WINDOW_RIGHT_SIDE, IDR_PANEL_BOTTOM_LEFT_CORNER, IDR_WINDOW_BOTTOM_CENTER, IDR_PANEL_BOTTOM_RIGHT_CORNER); } custom_frame_border->RenderToWidget(widget); } void PanelBrowserWindowGtk::EnsureDragHelperCreated() { if (drag_helper_.get()) return; drag_helper_.reset(new PanelDragGtk(panel_.get())); gtk_box_pack_end(GTK_BOX(window_vbox_), drag_helper_->widget(), FALSE, FALSE, 0); } bool PanelBrowserWindowGtk::HandleTitleBarLeftMousePress( GdkEventButton* event, guint32 last_click_time, gfx::Point last_click_position) { DCHECK_EQ(1U, event->button); DCHECK_EQ(GDK_BUTTON_PRESS, event->type); EnsureDragHelperCreated(); drag_helper_->InitialTitlebarMousePress(event, titlebar_widget()); return TRUE; } bool PanelBrowserWindowGtk::HandleWindowEdgeLeftMousePress( GtkWindow* window, GdkWindowEdge edge, GdkEventButton* event) { DCHECK_EQ(1U, event->button); DCHECK_EQ(GDK_BUTTON_PRESS, event->type); EnsureDragHelperCreated(); // Resize cursor was set by BrowserWindowGtk when mouse moved over // window edge. GdkCursor* cursor = gdk_window_get_cursor(gtk_widget_get_window(GTK_WIDGET(window_))); drag_helper_->InitialWindowEdgeMousePress(event, cursor, edge); return TRUE; } void PanelBrowserWindowGtk::SaveWindowPosition() { // We don't save window position for panels as it's controlled by // PanelManager. return; } void PanelBrowserWindowGtk::SetGeometryHints() { // Set minimum height the window can be set to. GdkGeometry hints; hints.min_height = panel::kMinimizedPanelHeight; hints.min_width = kMinWindowWidth; gtk_window_set_geometry_hints( window(), GTK_WIDGET(window()), &hints, GDK_HINT_MIN_SIZE); DCHECK(frame_size_.IsEmpty()); } void PanelBrowserWindowGtk::SetBounds(const gfx::Rect& bounds) { // This should never be called. DLOG(WARNING) << "Unexpected call to PanelBrowserWindowGtk::SetBounds()"; } void PanelBrowserWindowGtk::OnSizeChanged(int width, int height) { BrowserWindowGtk::OnSizeChanged(width, height); if (!frame_size_.IsEmpty()) return; // The system is allowed to size the window before the desired panel // bounds are applied. Save the frame size allocated by the system as // it will be affected when we shrink the panel smaller than the frame. frame_size_ = GetNonClientFrameSize(); int top = bounds_.bottom() - height; int left = bounds_.right() - width; gtk_window_move(window_, left, top); StartBoundsAnimation(gfx::Rect(left, top, width, height), bounds_); panel_->OnWindowSizeAvailable(); content::NotificationService::current()->Notify( chrome::NOTIFICATION_PANEL_WINDOW_SIZE_KNOWN, content::Source(panel_.get()), content::NotificationService::NoDetails()); } bool PanelBrowserWindowGtk::UseCustomFrame() const { // We always use custom frame for panels. return true; } bool PanelBrowserWindowGtk::UsingCustomPopupFrame() const { // We do not draw custom popup frame. return false; } void PanelBrowserWindowGtk::DrawPopupFrame(cairo_t* cr, GtkWidget* widget, GdkEventExpose* event) { NOTREACHED(); } const gfx::Image* PanelBrowserWindowGtk::GetThemeFrameImage() const { PaintState paint_state = GetPaintState(); if (paint_state == PAINT_FOR_ATTENTION) return GetAttentionBackgroundImage(); GtkThemeService* theme_provider = GtkThemeService::GetFrom( GetPanelBrowser()->profile()); if (theme_provider->UsingDefaultTheme()) { // We choose to use the window frame theme to paint panels for the default // theme. This is because the default tab theme does not work well for the // user to recognize active and inactive panels. return theme_provider->GetImageNamed(paint_state == PAINT_AS_ACTIVE ? IDR_THEME_FRAME : IDR_THEME_FRAME_INACTIVE); } return theme_provider->GetImageNamed(paint_state == PAINT_AS_ACTIVE ? IDR_THEME_TOOLBAR : IDR_THEME_TAB_BACKGROUND); } void PanelBrowserWindowGtk::DrawCustomFrame(cairo_t* cr, GtkWidget* widget, GdkEventExpose* event) { gfx::CairoCachedSurface* surface = GetThemeFrameImage()->ToCairo(); surface->SetSource(cr, widget, 0, 0); cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); cairo_rectangle(cr, event->area.x, event->area.y, event->area.width, event->area.height); cairo_fill(cr); } void PanelBrowserWindowGtk::ActiveWindowChanged(GdkWindow* active_window) { bool was_active = IsActive(); BrowserWindowGtk::ActiveWindowChanged(active_window); bool is_active = IsActive(); if (!window() || was_active == is_active) // State didn't change. return; panel_->OnActiveStateChanged(is_active); } void PanelBrowserWindowGtk::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { switch (type) { case chrome::NOTIFICATION_WINDOW_CLOSED: // Cleanup. if (bounds_animator_.get()) bounds_animator_.reset(); if (drag_helper_.get()) drag_helper_.reset(); panel_->OnNativePanelClosed(); break; } BrowserWindowGtk::Observe(type, source, details); } void PanelBrowserWindowGtk::ShowPanel() { Show(); } void PanelBrowserWindowGtk::ShowPanelInactive() { ShowInactive(); } gfx::Rect PanelBrowserWindowGtk::GetPanelBounds() const { return bounds_; } void PanelBrowserWindowGtk::SetPanelBounds(const gfx::Rect& bounds) { SetBoundsInternal(bounds, true); } void PanelBrowserWindowGtk::SetPanelBoundsInstantly(const gfx::Rect& bounds) { SetBoundsInternal(bounds, false); } void PanelBrowserWindowGtk::SetBoundsInternal(const gfx::Rect& bounds, bool animate) { if (bounds == bounds_) return; if (!animate) { // If no animation is in progress, apply bounds change instantly. Otherwise, // continue the animation with new target bounds. if (!IsAnimatingBounds()) gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window())), bounds.x(), bounds.y(), bounds.width(), bounds.height()); } else if (!frame_size_.IsEmpty()) { StartBoundsAnimation(bounds_, bounds); } else { // Wait until the window frame has been sized before starting the animation // to avoid animating the window before it is shown. } bounds_ = bounds; } void PanelBrowserWindowGtk::ClosePanel() { Close(); } void PanelBrowserWindowGtk::ActivatePanel() { Activate(); } void PanelBrowserWindowGtk::DeactivatePanel() { BrowserWindow* browser_window = panel_->manager()->GetNextBrowserWindowToActivate(GetPanelBrowser()); if (browser_window) { browser_window->Activate(); } else { Deactivate(); } } bool PanelBrowserWindowGtk::IsPanelActive() const { return IsActive(); } void PanelBrowserWindowGtk::PreventActivationByOS(bool prevent_activation) { gtk_window_set_accept_focus(window(), !prevent_activation); return; } gfx::NativeWindow PanelBrowserWindowGtk::GetNativePanelHandle() { return GetNativeHandle(); } void PanelBrowserWindowGtk::UpdatePanelTitleBar() { UpdateTitleBar(); } void PanelBrowserWindowGtk::UpdatePanelLoadingAnimations(bool should_animate) { UpdateLoadingAnimations(should_animate); } void PanelBrowserWindowGtk::ShowTaskManagerForPanel() { ShowTaskManager(); } FindBar* PanelBrowserWindowGtk::CreatePanelFindBar() { return CreateFindBar(); } void PanelBrowserWindowGtk::NotifyPanelOnUserChangedTheme() { UserChangedTheme(); } void PanelBrowserWindowGtk::PanelWebContentsFocused(WebContents* contents) { WebContentsFocused(contents); } void PanelBrowserWindowGtk::PanelCut() { Cut(); } void PanelBrowserWindowGtk::PanelCopy() { Copy(); } void PanelBrowserWindowGtk::PanelPaste() { Paste(); } void PanelBrowserWindowGtk::DrawAttention(bool draw_attention) { DCHECK((panel_->attention_mode() & Panel::USE_PANEL_ATTENTION) != 0); if (is_drawing_attention_ == draw_attention) return; is_drawing_attention_ = draw_attention; GetPanelTitlebar()->UpdateTextColor(); InvalidateWindow(); if ((panel_->attention_mode() & Panel::USE_SYSTEM_ATTENTION) != 0) ::BrowserWindowGtk::FlashFrame(draw_attention); } bool PanelBrowserWindowGtk::IsDrawingAttention() const { return is_drawing_attention_; } bool PanelBrowserWindowGtk::PreHandlePanelKeyboardEvent( const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { return PreHandleKeyboardEvent(event, is_keyboard_shortcut); } void PanelBrowserWindowGtk::FullScreenModeChanged(bool is_full_screen) { // Nothing to do here as z-order rules for panels ensures that they're below // any app running in full screen mode. } void PanelBrowserWindowGtk::HandlePanelKeyboardEvent( const NativeWebKeyboardEvent& event) { HandleKeyboardEvent(event); } Browser* PanelBrowserWindowGtk::GetPanelBrowser() const { return browser(); } void PanelBrowserWindowGtk::DestroyPanelBrowser() { DestroyBrowser(); } void PanelBrowserWindowGtk::EnsurePanelFullyVisible() { gtk_window_present(window()); } void PanelBrowserWindowGtk::SetPanelAlwaysOnTop(bool on_top) { gtk_window_set_keep_above(window(), on_top); } void PanelBrowserWindowGtk::EnableResizeByMouse(bool enable) { } void PanelBrowserWindowGtk::UpdatePanelMinimizeRestoreButtonVisibility() { GetPanelTitlebar()->UpdateMinimizeRestoreButtonVisibility(); } gfx::Size PanelBrowserWindowGtk::WindowSizeFromContentSize( const gfx::Size& content_size) const { return gfx::Size(content_size.width() + frame_size_.width(), content_size.height() + frame_size_.height()); } gfx::Size PanelBrowserWindowGtk::ContentSizeFromWindowSize( const gfx::Size& window_size) const { return gfx::Size(window_size.width() - frame_size_.width(), window_size.height() - frame_size_.height()); } int PanelBrowserWindowGtk::TitleOnlyHeight() const { GtkAllocation allocation; gtk_widget_get_allocation(titlebar_widget(), &allocation); return allocation.height; } void PanelBrowserWindowGtk::StartBoundsAnimation( const gfx::Rect& from_bounds, const gfx::Rect& to_bounds) { animation_start_bounds_ = IsAnimatingBounds() ? last_animation_progressed_bounds_ : from_bounds; bounds_animator_.reset(new PanelBoundsAnimation( this, panel_.get(), animation_start_bounds_, to_bounds)); bounds_animator_->Start(); last_animation_progressed_bounds_ = animation_start_bounds_; } bool PanelBrowserWindowGtk::IsAnimatingBounds() const { return bounds_animator_.get() && bounds_animator_->is_animating(); } void PanelBrowserWindowGtk::AnimationEnded(const ui::Animation* animation) { GetPanelTitlebar()->SendEnterNotifyToCloseButtonIfUnderMouse(); panel_->manager()->OnPanelAnimationEnded(panel_.get()); } void PanelBrowserWindowGtk::AnimationProgressed( const ui::Animation* animation) { DCHECK(!frame_size_.IsEmpty()); gfx::Rect new_bounds = bounds_animator_->CurrentValueBetween( animation_start_bounds_, bounds_); gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window())), new_bounds.x(), new_bounds.y(), new_bounds.width(), new_bounds.height()); last_animation_progressed_bounds_ = new_bounds; } gboolean PanelBrowserWindowGtk::OnTitlebarButtonReleaseEvent( GtkWidget* widget, GdkEventButton* event) { if (event->button != 1) return TRUE; panel_->OnTitlebarClicked((event->state & GDK_CONTROL_MASK) ? panel::APPLY_TO_ALL : panel::NO_MODIFIER); return TRUE; } void PanelBrowserWindowGtk::PanelExpansionStateChanging( Panel::ExpansionState old_state, Panel::ExpansionState new_state) { } // NativePanelTesting implementation. class NativePanelTestingGtk : public NativePanelTesting { public: explicit NativePanelTestingGtk( PanelBrowserWindowGtk* panel_browser_window_gtk); private: virtual void PressLeftMouseButtonTitlebar( const gfx::Point& mouse_location, panel::ClickModifier modifier) OVERRIDE; virtual void ReleaseMouseButtonTitlebar( panel::ClickModifier modifier) OVERRIDE; virtual void DragTitlebar(const gfx::Point& mouse_location) OVERRIDE; virtual void CancelDragTitlebar() OVERRIDE; virtual void FinishDragTitlebar() OVERRIDE; virtual bool VerifyDrawingAttention() const OVERRIDE; virtual bool VerifyActiveState(bool is_active) OVERRIDE; virtual void WaitForWindowCreationToComplete() const OVERRIDE; virtual bool IsWindowSizeKnown() const OVERRIDE; virtual bool IsAnimatingBounds() const OVERRIDE; virtual bool IsButtonVisible(TitlebarButtonType button_type) const OVERRIDE; PanelBrowserWindowGtk* panel_browser_window_gtk_; }; // static NativePanelTesting* NativePanelTesting::Create(NativePanel* native_panel) { return new NativePanelTestingGtk(static_cast( native_panel)); } NativePanelTestingGtk::NativePanelTestingGtk( PanelBrowserWindowGtk* panel_browser_window_gtk) : panel_browser_window_gtk_(panel_browser_window_gtk) { } void NativePanelTestingGtk::PressLeftMouseButtonTitlebar( const gfx::Point& mouse_location, panel::ClickModifier modifier) { // If there is an animation, wait for it to finish as we don't handle button // clicks while animation is in progress. while (panel_browser_window_gtk_->IsAnimatingBounds()) MessageLoopForUI::current()->RunAllPending(); GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS); event->button.button = 1; event->button.x_root = mouse_location.x(); event->button.y_root = mouse_location.y(); if (modifier == panel::APPLY_TO_ALL) event->button.state |= GDK_CONTROL_MASK; panel_browser_window_gtk_->HandleTitleBarLeftMousePress( reinterpret_cast(event), GDK_CURRENT_TIME, gfx::Point()); gdk_event_free(event); MessageLoopForUI::current()->RunAllPending(); } void NativePanelTestingGtk::ReleaseMouseButtonTitlebar( panel::ClickModifier modifier) { GdkEvent* event = gdk_event_new(GDK_BUTTON_RELEASE); event->button.button = 1; if (modifier == panel::APPLY_TO_ALL) event->button.state |= GDK_CONTROL_MASK; if (panel_browser_window_gtk_->drag_helper_.get()) { panel_browser_window_gtk_->drag_helper_->OnButtonReleaseEvent( NULL, reinterpret_cast(event)); } else { panel_browser_window_gtk_->OnTitlebarButtonReleaseEvent( NULL, reinterpret_cast(event)); } gdk_event_free(event); MessageLoopForUI::current()->RunAllPending(); } void NativePanelTestingGtk::DragTitlebar(const gfx::Point& mouse_location) { if (!panel_browser_window_gtk_->drag_helper_.get()) return; GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY); event->motion.x_root = mouse_location.x(); event->motion.y_root = mouse_location.y(); panel_browser_window_gtk_->drag_helper_->OnMouseMoveEvent( NULL, reinterpret_cast(event)); gdk_event_free(event); MessageLoopForUI::current()->RunAllPending(); } void NativePanelTestingGtk::CancelDragTitlebar() { if (!panel_browser_window_gtk_->drag_helper_.get()) return; panel_browser_window_gtk_->drag_helper_->OnGrabBrokenEvent(NULL, NULL); MessageLoopForUI::current()->RunAllPending(); } void NativePanelTestingGtk::FinishDragTitlebar() { if (!panel_browser_window_gtk_->drag_helper_.get()) return; ReleaseMouseButtonTitlebar(panel::NO_MODIFIER); } bool NativePanelTestingGtk::VerifyDrawingAttention() const { return panel_browser_window_gtk_->IsDrawingAttention(); } bool NativePanelTestingGtk::VerifyActiveState(bool is_active) { // TODO(jianli): to be implemented. http://crbug.com/102737 return false; } void NativePanelTestingGtk::WaitForWindowCreationToComplete() const { while (panel_browser_window_gtk_->frame_size_.IsEmpty()) MessageLoopForUI::current()->RunAllPending(); while (panel_browser_window_gtk_->IsAnimatingBounds()) MessageLoopForUI::current()->RunAllPending(); } bool NativePanelTestingGtk::IsWindowSizeKnown() const { return !panel_browser_window_gtk_->frame_size_.IsEmpty(); } bool NativePanelTestingGtk::IsAnimatingBounds() const { return panel_browser_window_gtk_->IsAnimatingBounds(); } bool NativePanelTestingGtk::IsButtonVisible( TitlebarButtonType button_type) const { PanelBrowserTitlebarGtk* titlebar = panel_browser_window_gtk_->GetPanelTitlebar(); CustomDrawButton* button; switch (button_type) { case CLOSE_BUTTON: button = titlebar->close_button(); break; case MINIMIZE_BUTTON: button = titlebar->minimize_button(); break; case RESTORE_BUTTON: button = titlebar->unminimize_button(); break; default: NOTREACHED(); return false; } return gtk_widget_get_visible(button->widget()); }