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.
- checksums.yaml +7 -0
- data/AUTHORS +1 -0
- data/COPYING +340 -0
- data/README.md +57 -0
- data/bin/etti +10 -0
- data/lib/data/locale/de/LC_MESSAGES/etti.mo +0 -0
- data/lib/data/page-data.json +28 -0
- data/lib/data/pixmaps/logo.png +0 -0
- data/lib/data/pixmaps/logo.svg +102 -0
- data/lib/data/pixmaps/win-logo.png +0 -0
- data/lib/data/pixmaps/win-logo.svg +223 -0
- data/lib/etti/colors.rb +15 -0
- data/lib/etti/dnd-data.rb +12 -0
- data/lib/etti/elements/base.rb +14 -0
- data/lib/etti/elements/data.rb +109 -0
- data/lib/etti/elements/text.rb +74 -0
- data/lib/etti/file/data-reader.rb +67 -0
- data/lib/etti/monkeys/array.rb +8 -0
- data/lib/etti/monkeys/cairo-context.rb +94 -0
- data/lib/etti/monkeys/gtk-event-box.rb +7 -0
- data/lib/etti/page-datas.rb +110 -0
- data/lib/etti/page.rb +40 -0
- data/lib/etti/print-view.rb +21 -0
- data/lib/etti/ui/data-page.rb +236 -0
- data/lib/etti/ui/label-elements.rb +74 -0
- data/lib/etti/ui/label-layout-page.rb +193 -0
- data/lib/etti/ui/label-layout.rb +315 -0
- data/lib/etti/ui/label-selection-page.rb +91 -0
- data/lib/etti/ui/message-dialog.rb +103 -0
- data/lib/etti/ui/page-layout.rb +405 -0
- data/lib/etti/ui/property-editor-data.rb +92 -0
- data/lib/etti/ui/window.rb +117 -0
- data/lib/etti/version.rb +3 -0
- data/lib/etti.rb +45 -0
- metadata +184 -0
@@ -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
|