etti 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,315 @@
1
+ # encoding: UTF-8
2
+
3
+ module Etti
4
+ class LabelLayout < Gtk::EventBox
5
+ include GetText
6
+ GetText.bindtextdomain 'etti', :path => Etti::LOCALE_PATH
7
+
8
+ SCALE_MIN = 0.1
9
+ SCALE_MAX = 50.0
10
+
11
+ def initialize propertyEditors
12
+ super()
13
+ @propertyEditors = propertyEditors
14
+ @elements = []
15
+ @selectedElement = nil
16
+
17
+ @area = Gtk::DrawingArea.new
18
+ add @area
19
+ # I don't use the integrated double buffering, cause it flickers
20
+ # I can do it better myself
21
+ @area.double_buffered = false
22
+ @backBuffer = nil
23
+ $colors = Etti::Colors.new
24
+
25
+ self.drag_dest_set :all, [['elements2area', :same_app, 123]], :copy
26
+
27
+
28
+ @area.add_events Gdk::Event::Mask::SCROLL_MASK |
29
+ Gdk::Event::Mask::BUTTON_PRESS_MASK |
30
+ Gdk::Event::Mask::BUTTON_RELEASE_MASK
31
+
32
+ @area.signal_connect(:scroll_event) do |object, event|
33
+ dir = event.direction
34
+ if dir == :up or dir == :left
35
+ zoom_out event.x, event.y
36
+ elsif dir == :down or dir == :right
37
+ zoom_in event.x, event.y
38
+ end
39
+ end
40
+
41
+ @area.signal_connect(:draw) {|object, cc| redraw }
42
+ @area.signal_connect(:configure_event) {|object, event| on_configure event}
43
+ @area.signal_connect(:button_press_event) {|object, event| on_button_press event}
44
+ @area.signal_connect(:button_release_event) {|object, event| on_button_release event}
45
+
46
+ self.signal_connect(:drag_drop) {|widget, context, x, y, time| on_drag_drop context, x, y}
47
+
48
+
49
+
50
+ @scale = 1.0
51
+ @centerX = 0.0
52
+ @centerY = 0.0
53
+ @width = nil
54
+ @height = nil
55
+ end
56
+ attr_reader :backBuffer, :scale, :centerX, :centerY, :elements, :elementDataData
57
+
58
+
59
+ def on_drag_drop context, x, y
60
+ type = context.source_widget.dnd_data.type
61
+ data = context.source_widget.dnd_data.data
62
+ cx, cy = *view_to_pos(x, y)
63
+ if type == :data
64
+ idx = @elementDataHeads.index data
65
+ txt = @elementDataData.last[idx]
66
+ @elements << Etti::Element::Data.new(data, txt, cx, cy)
67
+ elsif type == :text
68
+ @elements << Etti::Element::Text.new(data, cx, cy)
69
+ end
70
+ queue_draw
71
+ end
72
+
73
+
74
+ def on_button_press event
75
+ @buttonData = {:x => event.x, :y => event.y,
76
+ :tempX => event.x, :tempY => event.y,
77
+ :button => event.button}
78
+ end
79
+
80
+
81
+ def on_button_release event
82
+ return if @buttonData.nil?
83
+
84
+ # left button
85
+ if event.button == 1
86
+ if @buttonData[:x].in(event.x() -5, event.x() +5) and @buttonData[:y].in(event.y() -5, event.y() +5)
87
+ x, y = *view_to_pos(event.x, event.y)
88
+ lastSelected = @selectedElement
89
+ @selectedElement = nil
90
+ type = :none
91
+ # get the type of the element at_pos
92
+ @elements.each do |element|
93
+ if element.at_pos self, x, y
94
+ if element.is_a? Etti::Element::Data
95
+ type = :data
96
+ else
97
+ raise RuntimeError, 'invalid type ' + element.class.to_s
98
+ end
99
+ @selectedElement = element
100
+ break
101
+ end
102
+ end
103
+
104
+ # set the current property editor
105
+ @propertyEditors.each_pair do |key, val|
106
+ if key == type
107
+ val.show
108
+ else
109
+ val.hide
110
+ end
111
+ end
112
+
113
+ unless type == :none
114
+ @propertyEditors[type].set_selected_element @selectedElement
115
+ @propertyEditors[type].set_from_data
116
+ end
117
+ queue_draw
118
+ end
119
+ end
120
+ @buttonData = nil
121
+ end
122
+
123
+
124
+ def queue_draw
125
+ @area.queue_draw_area 0, 0, @area.allocation.width, @area.allocation.height
126
+ end
127
+
128
+
129
+ def on_configure event
130
+ @width = event.width
131
+ @height = event.height
132
+ # at the first run zoom to the default label
133
+ zoom_fit if @backBuffer.nil?
134
+ @backBuffer = Cairo::ImageSurface.new Cairo::Format::RGB24, @width, @height
135
+ queue_draw
136
+ end
137
+
138
+
139
+ def update_page_data pageData
140
+ @pageData = pageData
141
+ end
142
+
143
+
144
+ def redraw
145
+ # clear the background
146
+ return if @area.window.nil?
147
+ cc = Cairo::Context.new @backBuffer
148
+ cc.set_source_rgb *$colors.bg
149
+ cc.paint
150
+
151
+ # draw the shadow under the label
152
+ w, h = *@pageData[:size]
153
+ size = Math.sqrt(60 * @scale)
154
+ sx, sy = *pos_to_view(0, 0)
155
+ cc.shadow_mp sx + size, sy + size, w * @scale - size, h * @scale - size, size, 'rb'
156
+
157
+ # draw the label
158
+ cc.set_line_width 2.0
159
+ cc.set_source_rgb 1.0, 1.0, 1.0
160
+ cc.rectangle *pos_to_view(0, 0), w * @scale, h * @scale
161
+ cc.fill_preserve
162
+ cc.set_source_rgb 0.0, 0.0, 0.0
163
+ cc.stroke
164
+
165
+
166
+ # draw the elements
167
+ @elements.each {|element| element.redraw self, cc}
168
+
169
+ # draw the selection
170
+ @selectedElement.draw_selection self unless @selectedElement.nil?
171
+
172
+ # draw widget frame
173
+ cc.set_source_rgb 0.0, 0.0, 0.0
174
+ cc.antialias = :none
175
+ cc.line_width = 2.0
176
+ cc.rectangle 0, 0, @area.allocation.width() -1.0, @area.allocation.height() -1.0
177
+ cc.stroke
178
+
179
+ # copy backbuffer to the window
180
+ cb = @area.window.create_cairo_context
181
+ cb.set_source @backBuffer
182
+ cb.paint
183
+ end
184
+
185
+
186
+ def print_labels cc, page
187
+ @printView = Etti::PrintView.new if @printView.nil?
188
+ w = @pageData[:size][0] * 2.8346457
189
+ h = @pageData[:size][1] * 2.8346457
190
+
191
+ sl = (@pageData[:cols] * @pageData[:rows]) * page
192
+ el = elementDataData.length
193
+ labels = el - sl
194
+
195
+ idc = []
196
+ savedVals = []
197
+ @elements.each do |element|
198
+ idc << @elementDataHeads.index(element.dataGroup)
199
+ savedVals << element.get_by_name('text') if element.is_a? Etti::Element::Text
200
+ end
201
+
202
+ catch :done do
203
+ 0.upto(@pageData[:rows] -1) do |row|
204
+ cc.save
205
+ cc.translate 0, h * row
206
+ 0.upto(@pageData[:cols] -1) do |col|
207
+ @elements.each_with_index do |element, idx|
208
+ element.set_by_name 'text', @elementDataData[sl][idc[idx]]
209
+ element.redraw @printView, cc
210
+ end
211
+ sl +=1
212
+ throw :done if sl == el
213
+ cc.translate w, 0
214
+ end
215
+ cc.restore
216
+ end
217
+ end
218
+ @elements.each_with_index do |element, idx|
219
+ element.set_by_name('text', savedVals[idx]) if element.is_a? Etti::Element::Text
220
+ end
221
+ end
222
+
223
+
224
+ def set_element_data heads, data
225
+ @elementDataHeads = heads
226
+ @elementDataData = data
227
+
228
+ @elements.delete_if do |element|
229
+ !heads.include? element.dataGroup
230
+ end
231
+ unless data.nil?
232
+ @elements.each do |element|
233
+ idx = heads.index element.dataGroup
234
+ element.set_by_name 'text', data.last[idx]
235
+ end
236
+ end
237
+ queue_draw
238
+ end
239
+
240
+
241
+ # x, y is the mouse position, if zoomed with the mouse wheel.
242
+ # with this we can zoom _at_ the mouse position.
243
+ def zoom_in x=nil, y=nil
244
+ return if @scale >= SCALE_MAX
245
+ @scale = (@scale * 2.0).min SCALE_MAX
246
+ unless x.nil? or y.nil?
247
+ x0 = (@width / 2.0 - x) / @scale
248
+ y0 = (@height / 2.0 - y) / @scale
249
+ @centerX += x0
250
+ @centerY += y0
251
+ end
252
+ queue_draw
253
+ end
254
+
255
+
256
+ def zoom_out x=nil, y=nil
257
+ return if @scale <= SCALE_MIN
258
+ @scale = (@scale / 2.0).max SCALE_MIN
259
+ unless x.nil? or y.nil?
260
+ x0 = (@width / 2.0 - x) / @scale
261
+ y0 = (@height / 2.0 - y) / @scale
262
+ @centerX -= x0 / 2.0
263
+ @centerY -= y0 / 2.0
264
+ end
265
+ queue_draw
266
+ end
267
+
268
+
269
+ def zoom_fit
270
+ minX = 2**31
271
+ maxX = - 2**31
272
+ minY = 2**31
273
+ maxY = - 2**31
274
+
275
+ @elements.each do |element|
276
+ ext = element.get_extent self
277
+ # TODO use min max methodes
278
+ minX = ext.x if ext.x < minX
279
+ minY = ext.y if ext.y < minY
280
+ maxX = ext.x + ext.width if ext.x + ext.width > maxX
281
+ maxY = ext.y + ext.height if ext.y + ext.height > maxY
282
+ end
283
+
284
+ # get the label in
285
+ w, h = *@pageData[:size]
286
+ minX = minX.min 0
287
+ minY = minY.min 0
288
+ maxX = maxX.max w
289
+ maxY = maxY.max h
290
+
291
+ width = maxX - minX
292
+ height = maxY - minY
293
+ @scale = (@width / width).min(@height / height) * 0.95
294
+ @scale = @scale.clamp SCALE_MIN, SCALE_MAX
295
+ @centerX = -(minX + width / 2.0)
296
+ @centerY = -(minY + height / 2.0)
297
+ queue_draw
298
+ end
299
+
300
+
301
+ def pos_to_view x=0.0, y=0.0
302
+ x1 = (x + @centerX) * @scale + @width / 2.0
303
+ y1 = (y + @centerY) * @scale + @height / 2.0
304
+ [x1, y1]
305
+ end
306
+
307
+
308
+ def view_to_pos x, y
309
+ x1 = (x - @width / 2.0) / @scale - @centerX
310
+ y1 = (y - @height / 2.0) / @scale - @centerY
311
+ [x1, y1]
312
+ end
313
+ end
314
+ end
315
+
@@ -0,0 +1,91 @@
1
+ #encoding: UTF-8
2
+
3
+ module Etti
4
+ class LabelSelectionPage < Gtk::Paned
5
+ self.type_register
6
+ self.signal_new :page_changed, GLib::Signal::RUN_FIRST, nil, GLib::Type['void'], Hash
7
+ def signal_do_page_changed foo; end
8
+
9
+ include GetText
10
+ GetText.bindtextdomain 'etti', :path => Etti::LOCALE_PATH
11
+
12
+ def initialize
13
+ super(:orientation => :horizontal)
14
+ @pageDatas = Etti::PageDatas.new
15
+ @previewArea = Etti::PageLayout.new @pageDatas
16
+
17
+ create_label_list
18
+ @pageDatas.signal_connect(:new_page) {|ettipd, ary| label_list_add ary[0], ary[1]}
19
+
20
+ scroll = Gtk::ScrolledWindow.new
21
+ scroll.set_policy :automatic, :automatic
22
+ scroll.add @labelList
23
+ pack1 scroll
24
+
25
+ pack2 @previewArea
26
+
27
+ self.position = 300
28
+ end
29
+
30
+ attr_reader :labelList, :pageData
31
+
32
+ def create_label_list
33
+ @labelListStore = Gtk::ListStore.new String
34
+ @labelList = Gtk::TreeView.new @labelListStore
35
+ @labelList.signal_connect(:cursor_changed) {on_label_list_cursor_changed}
36
+ renderer = Gtk::CellRendererText.new
37
+ @labelList.insert_column -1, _('Label'), renderer, :text => 0
38
+
39
+ names = @pageDatas.get_page_names
40
+ names.each_with_index do |name, i|
41
+ iter = @labelListStore.append
42
+ iter.set_value 0, name
43
+ if i == 0
44
+ @pageData = @pageDatas.get_page_data name
45
+ @previewArea.update_page_data @pageData
46
+ end
47
+ end
48
+ @labelList.selection.select_path Gtk::TreePath.new('0')
49
+ @pageData = @pageDatas.get_page_data names[0]
50
+ end
51
+
52
+
53
+ def on_label_list_cursor_changed
54
+ name = @labelListStore.get_iter(@labelList.cursor[0]).get_value 0
55
+ if @previewArea.entryChanged and @pageData[:name] != name
56
+ message = _("The page was changed. Should it be saved?")
57
+ dialog = Etti::MessageDialog.new self.toplevel, :buttons => [:yes, :no, :cancel],
58
+ :type => :info, :message => message, :title => _('Page was changed')
59
+ ret = dialog.run
60
+ case ret
61
+ when 'cancel'
62
+ idx = @pageDatas.get_page_names.index @pageData[:name]
63
+ @labelList.selection.select_path Gtk::TreePath.new(idx.to_s)
64
+ return
65
+ when 'yes'
66
+ @previewArea.save
67
+ end
68
+ end
69
+
70
+
71
+ @pageData = @pageDatas.get_page_data name
72
+ signal_emit :page_changed, @pageData
73
+ @previewArea.update_page_data @pageData
74
+ end
75
+
76
+
77
+
78
+ def label_list_add name, data
79
+ idx = 0
80
+ pt = nil
81
+ @labelListStore.each do |model, path, iter|
82
+ pt = path
83
+ break if iter[0] > name
84
+ idx += 1
85
+ end
86
+ iter = @labelListStore.insert idx
87
+ iter[0] = name
88
+ @labelList.selection.select_path pt.next!
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,103 @@
1
+ #encoding: UTF-8
2
+
3
+ module Etti
4
+ class MessageDialog < Gtk::Window
5
+ self.type_register
6
+ self.signal_new :response, GLib::Signal::RUN_FIRST, nil, GLib::Type['void'], Symbol
7
+ def signal_do_response foo; end
8
+
9
+
10
+ def initialize parent, flags=nil
11
+ super()
12
+ self.transient_for = parent
13
+ self.window_position = :center
14
+ self.modal = true
15
+ self.destroy_with_parent = true
16
+
17
+ @vbox = Gtk::Box.new :vertical
18
+ add @vbox
19
+
20
+ @hbox = Gtk::Box.new :horizontal
21
+ @vbox.pack_start @hbox, :expand => true, :fill => true, :padding => 12
22
+
23
+ self.title = flags[:title] if flags[:title]
24
+
25
+
26
+ if flags[:buttons]
27
+ @buttonBox = Gtk::Box.new :horizontal
28
+ @buttonBox.homogeneous = true
29
+ @vbox.pack_start @buttonBox, :expand => false, :fill => false
30
+
31
+ flags[:buttons].each do |btn|
32
+ stock = case btn
33
+ when :yes
34
+ Gtk::Stock::YES
35
+ when :no
36
+ Gtk::Stock::NO
37
+ when :cancel
38
+ Gtk::Stock::CANCEL
39
+ when :close
40
+ Gtk::Stock::CLOSE
41
+ else
42
+ warn "unknown button type %s" % btn.to_s
43
+ nil
44
+ end
45
+ if stock
46
+ button = Gtk::Button.new :stock_id => stock
47
+ button.child.border_width = 4
48
+ button.signal_connect(:clicked) {signal_emit :response, btn}
49
+ @buttonBox.pack_end button, :expand => true, :fill => true
50
+ end
51
+
52
+ end
53
+ end
54
+
55
+ if flags[:type]
56
+ stock = case flags[:type]
57
+ when :info
58
+ Gtk::Stock::DIALOG_INFO
59
+ when :warning
60
+ Gtk::Stock::DIALOG_WARNING
61
+ when :error
62
+ Gtk::Stock::DIALOG_ERROR
63
+ else
64
+ warn "unknown dialog type %s" % flags[:type].to_s
65
+ nil
66
+ end
67
+ if stock
68
+ img = Gtk::Image.new :stock => stock, :size => :dialog
69
+ img.set_padding 12, 6
70
+ @hbox.pack_start img
71
+ end
72
+ end
73
+
74
+
75
+ if flags[:message]
76
+ label = Gtk::Label.new flags[:message]
77
+ @hbox.pack_start label, :expand => true, :fill => true, :padding => 12
78
+ end
79
+
80
+ if flags[:remind]
81
+
82
+ end
83
+ end
84
+
85
+
86
+ def run
87
+ self.show_all
88
+ resp = nil
89
+ self.signal_connect(:response) do |wid, res|
90
+ resp = res
91
+ Gtk.main_quit
92
+ self.destroy
93
+ end
94
+ Gtk.main
95
+ resp
96
+ end
97
+
98
+ private
99
+ def warn warning
100
+ printf "%s:%i: %s\n", __FILE__, __LINE__, warning
101
+ end
102
+ end
103
+ end