File: gtk_crud.rb

package info (click to toggle)
mikutter 3.8.6%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 10,544 kB
  • sloc: ruby: 20,548; sh: 99; makefile: 19
file content (208 lines) | stat: -rw-r--r-- 6,751 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# -*- coding: utf-8 -*-
require 'gtk2'
require_relative '../utils'
miquire :mui, 'extension'
miquire :mui, 'contextmenu'

# CRUDなリストビューを簡単に実現するためのクラス
class Gtk::CRUD < Gtk::TreeView
  extend Memoist

  attr_accessor :creatable, :updatable, :deletable, :dialog_title
  type_register

  def initialize
    super()
    initialize_model
    @creatable = @updatable = @deletable = true
    set_columns
    # self.set_enable_search(true).set_search_column(1).set_search_equal_func{ |model, column, key, iter|
    #   not iter[column].include?(key) }
    handle_release_event
    handle_row_activated
  end

  def buttons(box_klass)
    box_klass.new(false, 4).closeup(create_button).closeup(update_button).closeup(delete_button) end

  def create_button
    if not defined? @create_button
      @create_button = Gtk::Button.new(Gtk::Stock::ADD)
      @create_button.ssc(:clicked) {
        record_create(nil, nil) } end
    @create_button end

  def update_button
    if not defined? @update_button
      @update_button = Gtk::Button.new(Gtk::Stock::EDIT)
      @update_button.ssc(:clicked) {
        record_update(nil, nil) } end
    @update_button end

  def delete_button
    if not defined? @delete_button
      @delete_button = Gtk::Button.new(Gtk::Stock::DELETE)
      @delete_button.ssc(:clicked) {
        record_delete(nil, nil) } end
    @delete_button end

  protected

  def handle_row_activated
    self.signal_connect("row-activated"){|view, path, column|
      if @updatable and iter = view.model.get_iter(path)
        if record = popup_input_window((0...model.n_columns).map{|i| iter[i] })
          force_record_update(iter, record) end end }
  end

  def handle_release_event
    self.signal_connect('button_release_event'){ |widget, event|
      if (event.button == 3)
        menu_pop(self, event)
        true end }
  end

  def on_created(iter)
  end

  def on_updated(iter)
  end

  def on_deleted(iter)
  end

  private

  def initialize_model
    set_model(Gtk::ListStore.new(*column_schemer.flatten.map{|x| x[:type]}))
  end

  def set_columns
    column_schemer.inject(0){ |index, scheme|
      if scheme.is_a? Array
        col = Gtk::TreeViewColumn.new(scheme.first[:label])
        col.resizable = scheme.first[:resizable]
        scheme.each{ |cell|
          if cell[:kind]
            cell_renderer = get_render_by(cell, index)
            col.pack_start(cell_renderer, cell[:expand])
            col.add_attribute(cell_renderer, cell[:kind], index) end
          index += 1 }
        append_column(col)
      else
        if(scheme[:label] and scheme[:kind])
          col = Gtk::TreeViewColumn.new(scheme[:label], get_render_by(scheme, index), scheme[:kind] => index)
          col.resizable = scheme[:resizable]
          append_column(col) end
        index += 1 end
      index }
  end

  def get_render_by(scheme, index)
    kind = scheme[:kind]
    renderer = scheme[:renderer]
    case
    when renderer
      if renderer.is_a?(Proc)
        renderer.call(scheme, index)
      else
        renderer.new end
    when kind == :text
      Gtk::CellRendererText.new
    when kind == :pixbuf
      Gtk::CellRendererPixbuf.new
    when kind == :active
      toggled = Gtk::CellRendererToggle.new
      toggled.signal_connect('toggled'){ |toggled, path|
        iter = model.get_iter(path)
        iter[index] = !iter[index]
        on_updated(iter) }
      toggled
    end
  end

  def column_schemer
    [{:kind => :active, :widget => :boolean, :type => TrueClass, :label => '表示'},
     {:kind => :text, :widget => :input, :type => String, :label => '名前'},
     {:type => Object, :widget => :message_picker},
    ].freeze
  end
  memoize :column_schemer

  def force_record_create(record)
    iter = model.model.append
    record.each_with_index{ |item, index|
      iter[index] = item }
    on_created(iter) end

  def force_record_update(iter, record)
    if defined? model.convert_iter_to_child_iter(iter)
      iter = model.convert_iter_to_child_iter iter end
    record.each_with_index{ |item, index|
      iter[index] = item }
    on_updated(iter) end

  def force_record_delete(iter)
    if defined? model.convert_iter_to_child_iter(iter)
      iter = model.convert_iter_to_child_iter iter end
    on_deleted(iter)
    model.model.remove(iter)
  end

  def record_create(optional, widget)
    if @creatable
      record = popup_input_window()
      if record
        force_record_create(record) end end end

  def record_update(optional, widget)
    if @updatable
      self.selection.selected_each {|model, path, iter|
        record = popup_input_window((0...model.n_columns).map{|i| iter[i] })
        if record and not model.destroyed?
          force_record_update(iter, record) end } end end

  def record_delete(optional, widget)
    if @deletable
      self.selection.selected_each {|model, path, iter|
        if Gtk::Dialog.confirm("本当に削除しますか?\n" +
                               "一度削除するともうもどってこないよ。")
          force_record_delete(iter) end } end end

  def menu_pop(widget, event)
    if(@creatable or @updatable or @deletable)
      contextmenu = Gtk::ContextMenu.new
      contextmenu.register("新規作成", &method(:record_create)) if @creatable
      contextmenu.register("編集", &method(:record_update)) if @updatable
      contextmenu.register("削除", &method(:record_delete)) if @deletable
      contextmenu.popup(widget, widget) end end

  # 入力ウィンドウを表示する
  def popup_input_window(defaults = [])
    input = gen_popup_window_widget(defaults)
    Mtk.scrolled_dialog(dialog_title || "", input[:widget], self.toplevel || self, &input[:result]) end

  def gen_popup_window_widget(results = [])
    widget = Gtk::VBox.new
    column_schemer.flatten.each_with_index{ |scheme, index|
      case scheme[:widget]
      when :message_picker
        widget.closeup(Mtk.message_picker(lambda{ |new|
                                            if(new.nil?)
                                              results[index].freeze_ifn
                                            else
                                              results[index] = new.freeze_ifn end }))
      when nil
        ;
      else
        widget.closeup(Mtk.__send__((scheme[:widget] or :input), lambda{ |new|
                                   if(new.nil?)
                                     results[index].freeze_ifn
                                   else
                                     results[index] = new.freeze_ifn end },
                                 scheme[:label], *(scheme[:args].to_a or []))) end }
    { :widget => widget,
      :result => lambda{
        results } } end

end