# Copyright (c) 2017 Ruby-GNOME2 Project Team
# This program is licenced under the same licence as Ruby-GNOME2.
#
=begin
=  Tool Palette

 A tool palette widget shows groups of toolbar items as a grid of icons
 or a list of names.
=end
class ToolpaletteDemo
  def initialize(main_window)
    @window = Gtk::Window.new(:toplevel)
    @window.screen = main_window.screen
    @window.title = "Tool Palette"
    @window.set_default_size(200, 600)
    @canvas_items = []

    # Add widgets to control the ToolPalette appearance
    @box = Gtk::Box.new(:vertical, 6)
    @box.margin = 6
    @window.add(@box)

    create_orientation_combobox
    create_style_combobox

    # Add hbox.
    @hbox = Gtk::Box.new(:horizontal, 5)
    @box.pack_start(@hbox, :expand => true, :fill => true, :padding => 0)

    create_and_fill_the_toolpalette
    add_comboboxes_signals

    # Notebook
    create_and_fill_the_notebook
  end

  def run
    if !@window.visible?
      @window.show_all
    else
      @window.destroy
    end
    @window
  end

  private

  def create_style_combobox
    style_model = Gtk::ListStore.new(String, Integer)
    iter = style_model.append
    iter[0] = "Text"
    iter[1] = Gtk::ToolbarStyle::TEXT.to_i
    iter = style_model.append
    iter[0] = "Both"
    iter[1] = Gtk::ToolbarStyle::BOTH.to_i
    iter = style_model.append
    iter[0] = "Both: Horizontal"
    iter[1] = Gtk::ToolbarStyle::BOTH_HORIZ.to_i
    iter = style_model.append
    iter[0] = "Icons"
    iter[1] = Gtk::ToolbarStyle::ICONS.to_i
    iter = style_model.append
    iter[0] = "Default"
    iter[1] = -1 # A custom meaning for this demo.
    @combo_style = Gtk::ComboBox.new(:model => style_model)
    cell_renderer = Gtk::CellRendererText.new
    @combo_style.pack_start(cell_renderer, true)
    @combo_style.set_attributes(cell_renderer, "text" => 0)
    @combo_style.active_iter = iter
    @box.pack_start(@combo_style, :expand => false,
                                  :fill => false,
                                  :padding => 0)
  end

  def create_orientation_combobox
    orientation_model = Gtk::ListStore.new(String, Integer)
    iter = orientation_model.append
    iter[0] = "Horizontal"
    iter[1] = Gtk::Orientation::HORIZONTAL
    iter = orientation_model.append
    iter[0] = "Vertical"
    iter[1] = Gtk::Orientation::VERTICAL

    @combo_orientation = Gtk::ComboBox.new(:model => orientation_model)
    cell_renderer = Gtk::CellRendererText.new
    @combo_orientation.pack_start(cell_renderer, true)
    @combo_orientation.set_attributes(cell_renderer, "text" => 0)
    @combo_orientation.active_iter = iter
    @box.pack_start(@combo_orientation, :expand => false,
                                        :fill => false,
                                        :padding => 0)
  end

  def create_and_fill_the_toolpalette
    @palette = Gtk::ToolPalette.new
    load_icon_items
    load_toggle_items
    load_special_items

    palette_scroller = Gtk::ScrolledWindow.new
    palette_scroller.set_policy(:never, :automatic)
    palette_scroller.margin = 6
    palette_scroller.hexpand = true
    palette_scroller.add(@palette)
    @hbox.add(palette_scroller)
    @box.show_all
    add_dnd_for_tool_items_to_palette
  end

  def add_dnd_for_tool_items_to_palette
    @palette.signal_connect "drag-data-received" do |widget, context, x, y|
      drag_item = @drag_palette.drag_item
      drop_group = widget.get_drop_group(x, y)

      if drag_item.class == Gtk::ToolItemGroup
        palette_drop_group(drag_palette, drag_item, drop_group)
      elsif drag_item.class == Gtk::ToolButton && drop_group
        allocation = drop_group.allocation
        palette_drop_item(drag_item, drop_group, x - allocation.x, y - allocation.y)
      end
    end

    @palette.add_drag_dest(@palette,
                           Gtk::DestDefaults::ALL,
                           [Gtk::ToolPaletteDragTargets::ITEMS,
                            Gtk::ToolPaletteDragTargets::GROUPS],
                           Gdk::DragAction::COPY)
  end

  def add_comboboxes_signals
    @combo_orientation.signal_connect "changed" do |widget|
      on_combo_orientation_changed(widget)
    end

    @combo_style.signal_connect "changed" do |widget|
      on_combo_style_changed(widget)
    end

    # Kepp the widgets in sync
    on_combo_orientation_changed(@combo_orientation)
  end

  def create_passive_contents
    @passive_contents = Gtk::DrawingArea.new
    @passive_contents.signal_connect "draw" do |widget, cr|
      canvas_draw(widget, cr)
    end
    @passive_contents.signal_connect "drag-data-received" do |widget, _context, x, y, selection, _info, _time|
      tool_item = @palette.get_drag_item(selection)

      # append a new canvas item when a tool button was found
      if tool_item.class == Gtk::ToolButton
        canvas_item = CanvasItem.new(widget, tool_item, x, y)
        @canvas_items << canvas_item
        widget.queue_draw
      end
    end
  end

  def create_interactive_contents
    @interactive_contents = Gtk::DrawingArea.new
    @interactive_contents.signal_connect "draw" do |widget, cr|
      canvas_draw(widget, cr)
    end

    @interactive_contents.signal_connect "drag-motion" do |widget, context, x, y, time|
      if @drop_item
        # already have a drop indicator - just update position
        @drop_item.x = x
        @drop_item.y = y
        widget.queue_draw
        Gdk.drag_status(context, Gdk::DragAction::COPY, time)
      else
        # request DnD data for creating a drop indicator
        target = widget.drag_dest_find_target(context, nil)
        return false unless target
        @drag_data_requested_for_drop = false
        widget.drag_get_data(context, target, time)
      end
      true
    end

    @interactive_contents.signal_connect "drag-data-received" do |widget, context, x, y, selection, _info, time|
      # find the tool button which is the source of this DnD operation
      tool_item = @palette.get_drag_item(selection)

      if tool_item.class == Gtk::ToolButton
        item = CanvasItem.new(widget, tool_item, x, y)

        # Either create a new item or just create a preview item, depending on
        # why the drag data was requested.
        if @drag_data_requested_for_drop
          @canvas_items << item
          context.finish(:success => true, :delete => false, :time => time)
          @drop_item = nil
        else
          @drop_item = item
          Gdk.drag_status(context, Gdk::DragAction::COPY, time)
        end
        widget.queue_draw
      end
    end

    @interactive_contents.signal_connect "drag-leave" do |widget|
      @drop_item = nil if @drop_item
      widget.queue_draw
    end

    @interactive_contents.signal_connect "drag-drop" do |widget, context, _x, _y, time|
      target = widget.drag_dest_find_target(context, nil)
      if target
        @drag_data_requested_for_drop = true
        widget.drag_get_data(context, target, time)
      end
      false
    end
  end

  def create_and_fill_the_notebook
    notebook = Gtk::Notebook.new
    notebook.margin = 6
    @hbox.pack_end(notebook, :expand => false, :fill => false, :padding => false)

    # Passive DnD dest
    create_passive_contents
    @palette.add_drag_dest(@passive_contents, Gtk::DestDefaults::ALL,
                                     [Gtk::ToolPaletteDragTargets::ITEMS],
                                     Gdk::DragAction::COPY)
    contents_scroller = Gtk::ScrolledWindow.new
    contents_scroller.set_policy(:automatic, :always)
    contents_scroller.add(@passive_contents)
    notebook.append_page(contents_scroller, Gtk::Label.new("Passive DnD Mode"))
    contents_scroller.margin = 6

    # Interactive DnD dest
    create_interactive_contents
    @palette.add_drag_dest(@interactive_contents, Gtk::DestDefaults::HIGHLIGHT,
                           [Gtk::ToolPaletteDragTargets::ITEMS],
                           Gdk::DragAction::COPY)
    contents_scroller = Gtk::ScrolledWindow.new
    contents_scroller.set_policy(:automatic, :always)
    contents_scroller.add(@interactive_contents)
    notebook.append_page(contents_scroller, Gtk::Label.new("Interactive DnD Mode"))
  end

  def canvas_draw(widget, cr)
    cr.set_source_rgb(1, 1, 1)
    cr.paint

    @canvas_items.each do |canvas_item|
      canvas_item.draw(cr, false)
    end

    @drop_item.draw(cr, true) if @drop_item
  end

  def palette_drop_group(drag_palette, drag_group, drop_group)
    drop_position = -1

    drop_position = drag_palette.get_group_position(drop_group) if drop_group
    drag_palette.set_group_position(drag_group, drop_position)
  end

  def palette_drop_item(drag_item, drop_group, x, y)
    drag_group = drag_item.parent
    drop_item = drop_group.get_drop_item(x, y)
    drop_position = -1

    drop_position = drop_gropu.get_item_position(drop_item) if drop_item

    if drag_group != drop_group
      homogeneous = drag_group.child_get_property(drag_item, "homogeneous")
      expand = drag_group.child_get_property(drag_item, "expand")
      fill = drag_group.child_get_property(drag_item, "fill")
      new_row = drag_group.child_get_property(drag_item, "new-row")
      drop_group.insert(drag_item, drop_position)
      drop_group.child_set(drag_item, "homogeneous" => homogeneous,
                                      "expand" => expand,
                                      "fill" => fill,
                                      "new-row" => new_row)
    else
      drop_group.set_item_position(drag_item, drop_position)
    end
  end

  def on_combo_orientation_changed(combo_box)
    sw = @palette.parent
    iter = combo_box.active_iter
    return unless iter

    @palette.orientation = iter[1]

    if @palette.orientation == Gtk::Orientation::HORIZONTAL
      sw.set_policy(:automatic, :never)
    else
      sw.set_policy(:never, :automatic)
    end
  end

  def on_combo_style_changed(combo_box)
    iter = combo_box.active_iter
    return unless iter

    if iter[1] == -1
      @palette.unset_style
    else
      @palette.style = iter[1]
    end
  end

  def load_icon_items
    max_icons = 10
    icons_count = 0
    icon_theme = Gtk::IconTheme.get_for_screen(@palette.screen)
    contexts = icon_theme.contexts
    contexts.each do |context|
      group = Gtk::ToolItemGroup.new(context)
      @palette.add(group)

      next if context == "Animations"
      puts "Got context '#{context}"

      icon_names = icon_theme.icons(context)
      icon_names.sort

      icon_names.each do |icon_name|
        next if icon_name == "emblem-desktop"
        next if icon_name =~ /^.*-symbolic$/
        puts "Got icon name '#{icon_name}'"

        item = Gtk::ToolButton.new
        item.icon_name = icon_name
        item.tooltip_text = icon_name
        group.insert(item, -1)

        # Prevent us having an insane number of icons
        icons_count += 1
        break if icons_count >= max_icons
      end
    end
  end

  def load_toggle_items
    group = Gtk::ToolItemGroup.new("Radio Item")
    @palette.add(group)

    item = nil
    (1..10).each do |i|
      label = "##{i}"
      item = Gtk::RadioToolButton.new(item)
      item.label = label
      group.insert(item, -1)
    end
  end

  def create_entry_item(text)
    entry = Gtk::Entry.new
    entry.text = text
    entry.width_chars = 5

    item = Gtk::ToolItem.new
    item.add(entry)
    item
  end

  def load_special_items
    group = Gtk::ToolItemGroup.new("Advanced Features group")
    label_button = Gtk::Button.new(:label => "Advanced Features")
    label_button.show
    group.label_widget = label_button
    @palette.add(group)

    item = create_entry_item("homogeneous=FALSE")
    group.insert(item, -1)
    group.child_set_property(item, "homogeneous", false)

    item = create_entry_item("homogeneous=FALSE, expand=TRUE")
    group.insert(item, -1)
    group.child_set_property(item, "homogeneous", false)
    group.child_set_property(item, "expand", true)

    item = create_entry_item("homogeneous=FALSE, expand=TRUE, fill=FALSE")
    group.insert(item, -1)
    group.child_set_property(item, "homogeneous", false)
    group.child_set_property(item, "expand", true)
    group.child_set_property(item, "fill", false)

    item = create_entry_item("homogeneous=FALSE, expand=TRUE, new-row=TRUE")
    group.insert(item, -1)
    group.child_set_property(item, "homogeneous", false)
    group.child_set_property(item, "expand", true)
    group.child_set_property(item, "new-row", true)

    item = Gtk::ToolButton.new
    item.icon_name = "go-up"
    item.tooltip_text = "Show on vertical palettes only"
    group.insert(item, -1)
    item.visible_horizontal = false

    item = Gtk::ToolButton.new
    item.icon_name = "go-next"
    item.tooltip_text = "Show on horizontal palettes only"
    group.insert(item, -1)
    item.visible_vertical = false

    item = Gtk::ToolButton.new
    item.icon_name = "edit-delete"
    item.tooltip_text = "Do not show at all"
    group.insert(item, -1)
    item.no_show_all = true

    item = Gtk::ToolButton.new
    item.icon_name = "view-fullscreen"
    item.tooltip_text = "Expanded this item"
    group.insert(item, -1)
    group.child_set_property(item, "homogeneous", false)
    group.child_set_property(item, "expand", true)

    item = Gtk::ToolButton.new
    item.icon_name = "help-browser"
    item.tooltip_text = "A regular item"
    group.insert(item, -1)
  end
end

class CanvasItem
  attr_reader :pixbuf
  attr_accessor :x, :y

  def initialize(widget, button, x, y)
    icon_name = button.icon_name
    icon_theme = Gtk::IconTheme.get_for_screen(widget.screen)
    width, _height = Gtk::IconSize.lookup(:dialog)
    @pixbuf = icon_theme.load_icon(icon_name,
                                   width,
                                   Gtk::IconLookupFlags::GENERIC_FALLBACK)
    return nil unless @pixbuf
    @x = x
    @y = y
  end

  def draw(cr, preview)
    cx = @pixbuf.width
    cy = @pixbuf.height
    cr.set_source_pixbuf(@pixbuf, @x - cx * 0.5, @y - cy * 0.5)

    if preview
      cr.paint(0.6)
    else
      cr.paint
    end
  end
end
