Renamed fill_{,pos_}{rel,abs} to set_fill_{,pos}{rel,abs} for consistency.
[redact.git] / lib / redact / part.rb
1 #--
2 # $Id: part.rb 77 2006-07-28 17:30:51Z tilman $
3 #
4 # Copyright (c) 2005 Tilman Sauerbeck (tilman at code-monkey de)
5 #
6 # Permission is hereby granted, free of charge, to any person obtaining
7 # a copy of this software and associated documentation files (the
8 # "Software"), to deal in the Software without restriction, including
9 # without limitation the rights to use, copy, modify, merge, publish,
10 # distribute, sublicense, and/or sell copies of the Software, and to
11 # permit persons to whom the Software is furnished to do so, subject to
12 # the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be
15 # included in all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25 module Redact
26         class Part
27                 TYPE_RECTANGLE = 1
28                 TYPE_TEXT = 2
29                 TYPE_IMAGE = 3
30                 TYPE_SWALLOW = 4
31
32                 include Comparable
33
34                 attr_reader :collection, :id, :name, :dragable, :clip,
35                             :mouse_events, :repeat_events
36
37                 def initialize(collection, id, name)
38                         @collection = collection
39                         @id = id
40
41                         @name = name.to_str.dup.freeze
42                         @type = TYPE_RECTANGLE
43                         @mouse_events = true
44                         @repeat_events = false
45                         @clip = nil
46                         @dragable = Dragable.new(self)
47
48                         @descriptions = Hash.new do |h, k|
49                                 desc, value = k.split("\0")
50                                 value = value.to_f
51
52                                 h[k] = description_class.new(desc, value)
53                         end
54                 end
55
56                 def <=>(b)
57                         @id <=> b.id
58                 end
59
60                 def mouse_events=(val)
61                         @mouse_events = (val == true)
62                 end
63
64                 def repeat_events=(val)
65                         @repeat_events = (val == true)
66                 end
67
68                 def clip=(part)
69                         if part == self
70                                 raise(ArgumentError, "cannot clip part to itself")
71                         elsif !part.nil? && part.collection != @collection
72                                 raise(ArgumentError, "items not in the same collection")
73                         else
74                                 @clip = part
75                         end
76                 end
77
78                 def description(name = "default", value = 0.0) # :yields: desc
79                         d = @descriptions[desc_key(name, value)]
80
81                         block_given? ? (yield d) : d
82                 end
83
84                 protected
85                 def description_class
86                         Description
87                 end
88
89                 def to_eet_name
90                         "Edje_Part"
91                 end
92
93                 def to_eet_properties
94                         other_desc = @descriptions.dup
95                         other_desc.delete(desc_key("default", 0.0))
96
97                         confine_id = @dragable.confine.nil? ?
98                                          -1 : @dragable.confine.id
99
100                         {"name" => [@name],
101                          "id" => [@id],
102                          "type" => [@type, :char],
103                          "effect" => [0, :char],
104                          "mouse_events" => [@mouse_events],
105                          "repeat_events" => [@repeat_events],
106                          "clip_to_id" => [@clip.nil? ? -1 : @clip.id],
107                          "default_desc" => [description("default", 0.0)],
108                          "other_desc" => [other_desc],
109                          "dragable.x" => [@dragable.enabled[0], :char],
110                          "dragable.step_x" => [@dragable.step[0]],
111                          "dragable.count_x" => [@dragable.count[0]],
112                          "dragable.y" => [@dragable.enabled[1], :char],
113                          "dragable.step_y" => [@dragable.step[1]],
114                          "dragable.count_y" => [@dragable.count[1]],
115                          "dragable.events_id" => [-1],
116                          "dragable.counfine_id" => [confine_id]} # not a typo!
117                 end
118
119                 private
120                 def desc_key(name, value)
121                         name + "\0" + value.to_s
122                 end
123         end
124
125         class SwallowPart < Part
126                 def initialize(collection, id, name)
127                         super
128
129                         @type = TYPE_SWALLOW
130                 end
131         end
132
133         class TextPart < Part
134                 attr_accessor :effect
135
136                 def initialize(collection, id, name)
137                         super
138
139                         @type = TYPE_TEXT
140                         @effect = :none
141                 end
142
143                 protected
144                 def description_class
145                         TextDescription
146                 end
147
148                 def to_eet_properties
149                         effect = case @effect
150                         when :none: 0
151                         when :plain: 1
152                         when :outline: 2
153                         when :soft_outline: 3
154                         when :shadow: 4
155                         when :soft_shadow: 5
156                         when :outline_shadow: 6
157                         when :outline_soft_shadow: 7
158                         else
159                                 raise(RedactError, "invalid effect value - #{@effect}")
160                         end
161
162                         super.merge!({"effect" => [effect, :char]})
163                 end
164         end
165
166         class ImagePart < Part
167                 def initialize(collection, id, name)
168                         super
169
170                         @type = TYPE_IMAGE
171                 end
172
173                 protected
174                 def description_class
175                         ImageDescription
176                 end
177         end
178
179         class Dragable
180                 attr_reader :enabled, :step, :count, :confine
181
182                 def initialize(part)
183                         @part= part
184
185                         @enabled = [false, false]
186                         @step = [0, 0]
187                         @count = [0, 0]
188                         @confine = nil
189                 end
190
191                 def confine=(part)
192                         if part == @part
193                                 raise(ArgumentError, "cannot confine part to itself")
194                         elsif !part.nil? && part.collection != @part.collection
195                                 raise(ArgumentError, "items not in the same collection")
196                         else
197                                 @confine = part
198                         end
199                 end
200         end
201
202         class Relation
203                 attr_reader :rel, :to_id, :offset
204
205                 def initialize(rel, offset)
206                         @rel = [rel.to_f, rel.to_f]
207                         @to_id = [-1, -1]
208                         @offset = [offset, offset]
209                 end
210
211                 def set_rel(x, y)
212                         @rel = [x.to_f, y.to_f]
213                 end
214
215                 def set_offset(x, y)
216                         @offset = [x, y]
217                 end
218
219                 def to=(part)
220                         self.set_to(part)
221                 end
222
223                 def set_to(part_x, part_y = part_x)
224                         @to_id = [part_x.nil? ? -1 : part_x.id,
225                                   part_y.nil? ? -1 : part_y.id]
226                 end
227         end
228
229         class Description
230                 attr_reader :rel, :aspect, :step, :visible, :color_class
231                 attr_accessor :aspect_preference
232
233                 def initialize(name = "default", value = 0.0)
234                         @name = name.to_str.dup.freeze
235                         @value = value.freeze
236                         @visible = true
237                         @align = [0.5, 0.5]
238                         @min = [0, 0]
239                         @max = [-1, -1]
240                         @step = [0, 0]
241                         @aspect = [0.0, 0.0]
242                         @aspect_preference = :none
243                         @rel = [Relation.new(0.0, 0), Relation.new(1.0, -1)]
244                         @color = [].fill(255, 0..3)
245                         @color_class = ""
246                 end
247
248                 def inherit(other)
249                         unless other.is_a?(Description)
250                                 raise(ArgumentError, "Cannot inherit from description")
251                         end
252
253                         prot = ["@name", "@value"]
254
255                         (instance_variables - prot).each do |v|
256                                 n = other.instance_variable_get(v.intern)
257                                 n = n.dup rescue n
258                                 instance_variable_set(v.intern, n)
259                         end
260                 end
261
262                 def visible=(v)
263                         @visible = (v == true)
264                 end
265
266                 def color_class=(v)
267                         @color_class = v.to_str.dup
268                 end
269
270                 def set_step(x = 0, y = 0)
271                         @step = [x, y]
272                 end
273
274                 def set_aspect(x = 0.0, y = 0.0)
275                         @aspect = [x, y]
276                 end
277
278                 def set_align(x = 0.5, y = 0.5)
279                         @align = [x, y]
280                 end
281
282                 def set_size(w, h)
283                         set_min(w, h)
284                         set_max(w, h)
285                 end
286
287                 def set_min(w, h)
288                         @min = [w, h]
289                 end
290
291                 def set_max(w, h)
292                         @max = [w, h]
293                 end
294
295                 def color=(c)
296                         @color = parse_hex_color(c)
297                 end
298
299                 protected
300                 def parse_hex_color(c)
301                         md = c.match(/^#?(([[:xdigit:]]{2}){1,4})$/)
302                         if md.nil?
303                                 raise(ArgumentError, "Argument is not a hex string")
304                         end
305
306                         pairs = md.captures.shift.split(/(..)/).delete_if do |item|
307                                 item == ""
308                         end
309
310                         pairs.push("00") while pairs.length < 3
311                         pairs.push("ff") if pairs.length == 3
312
313                         pairs.map { |p| p.hex }
314                 end
315
316                 def to_eet_name
317                         "Edje_Part_Description"
318                 end
319
320                 def to_eet_properties
321                         asp_pref = case @aspect_preference
322                         when :none: 0
323                         when :vertical: 1
324                         when :horizontal: 2
325                         when :both: 3
326                         else
327                                 raise(RedactError, "invalid aspect preference value - " +
328                                       @aspect_preference.to_s)
329                         end
330
331                         {"state.name" => [@name],
332                          "state.value" => [@value, :double],
333                          "visible" => [@visible],
334                          "align.x" => [@align[0], :double],
335                          "align.y" => [@align[1], :double],
336                          "min.w" => [@min[0]],
337                          "min.h" => [@min[1]],
338                          "max.w" => [@max[0]],
339                          "max.h" => [@max[1]],
340                          "step.x" => [@step[0]],
341                          "step.y" => [@step[1]],
342                          "aspect.min" => [@aspect[0], :double],
343                          "aspect.max" => [@aspect[1], :double],
344                          "aspect.prefer" => [asp_pref, :char],
345                          "rel1.relative_x" => [@rel[0].rel[0], :double],
346                          "rel1.relative_y" => [@rel[0].rel[1], :double],
347                          "rel1.offset_x" => [@rel[0].offset[0]],
348                          "rel1.offset_y" => [@rel[0].offset[1]],
349                          "rel1.id_x" => [@rel[0].to_id[0]],
350                          "rel1.id_y" => [@rel[0].to_id[1]],
351                          "rel2.relative_x" => [@rel[1].rel[0], :double],
352                          "rel2.relative_y" => [@rel[1].rel[1], :double],
353                          "rel2.offset_x" => [@rel[1].offset[0]],
354                          "rel2.offset_y" => [@rel[1].offset[1]],
355                          "rel2.id_x" => [@rel[1].to_id[0]],
356                          "rel2.id_y" => [@rel[1].to_id[1]],
357                          "color_class" => [@color_class],
358                          "color.r" => [@color[0], :char],
359                          "color.g" => [@color[1], :char],
360                          "color.b" => [@color[2], :char],
361                          "color.a" => [@color[3], :char],
362
363                          # image properties
364                          "image.id" => [-1],
365                          "image.tween_list" => [nil],
366                          "border.l" => [0],
367                          "border.r" => [0],
368                          "border.t" => [0],
369                          "border.b" => [0],
370                          "border.no_fill" => [false],
371                          "fill.smooth" => [true],
372                          "fill.pos_rel_x" => [0.0, :double],
373                          "fill.pos_abs_x" => [0],
374                          "fill.rel_x" => [1.0, :double],
375                          "fill.abs_x" => [0],
376                          "fill.pos_rel_y" => [0.0, :double],
377                          "fill.pos_abs_y" => [0],
378                          "fill.rel_y" => [1.0, :double],
379                          "fill.abs_y" => [0],
380
381                          # text properties
382                          "color2.r" => [0, :char],
383                          "color2.g" => [0, :char],
384                          "color2.b" => [0, :char],
385                          "color2.a" => [255, :char],
386                          "color3.r" => [0, :char],
387                          "color3.g" => [0, :char],
388                          "color3.b" => [0, :char],
389                          "color3.a" => [128, :char],
390                          "text.text" => [""],
391                          "text.text_class" => [""],
392                          "text.font" => [""],
393                          "text.size" => [0],
394                          "text.fit_x" => [false],
395                          "text.fit_y" => [false],
396                          "text.min_x" => [0],
397                          "text.min_y" => [0],
398                          "text.align.x" => [0.0, :double],
399                          "text.align.y" => [0.0, :double],
400                          "text.id_source" => [-1],
401                          "text.id_text_source" => [-1]}
402                 end
403         end
404
405         class Tween
406                 def initialize(image)
407                         @id = image.id
408                 end
409
410                 protected
411                 def to_eet_name
412                         "Edje_Part_Image_Id"
413                 end
414         end
415
416         class Tweens < Array
417                 def <<(im)
418                         im2 = find_image(im.to_str.strip)
419                         raise(RedactError, "cannot find image - #{im}") if im2.nil?
420
421                         image = EDJE.image_dir.find { |e| e.filename == im2 }
422                         if image.nil?
423                                 image = ImageDirectoryEntry.new(im, im2)
424                                 EDJE.image_dir << image
425                         end
426
427                         super(Tween.new(image))
428                 end
429
430                 private
431                 def find_image(file)
432                         [".", OPTIONS.image_dir].each do |d|
433                                 f2 = File.join(d, file)
434                                 return Pathname.new(f2).cleanpath.to_s if File.file?(f2)
435                         end
436
437                         nil
438                 end
439
440         end
441
442         class ImageDescription < Description
443                 attr_reader :image, :auto_rel, :tweens, :border_fill_middle,
444                             :fill_smooth, :fill_pos_rel, :fill_pos_abs,
445                             :fill_rel, :fill_abs
446
447                 def initialize(name = "default", value = 0.0)
448                         super
449
450                         @image = nil
451                         @tweens = Tweens.new
452                         @border = [0, 0, 0, 0]
453                         @border_fill_middle = true
454
455                         @fill_smooth = true
456                         @fill_pos_rel = [0.0, 0.0]
457                         @fill_pos_abs = [0, 0]
458                         @fill_rel = [1.0, 1.0]
459                         @fill_abs = [0, 0]
460
461                         @auto_rel = false
462                 end
463
464                 def border_fill_middle=(var)
465                         @border_fill_middle = (var == true)
466                 end
467
468                 def image=(im)
469                         im2 = find_image(im.to_str.strip)
470                         raise(RedactError, "cannot find image - #{im}") if im2.nil?
471
472                         return if !@image.nil? && im2 == @image.filename
473
474                         @image = EDJE.image_dir.find { |e| e.filename == im2 }
475                         if @image.nil?
476                                 @image = ImageDirectoryEntry.new(im, im2)
477                                 EDJE.image_dir << @image
478                         end
479
480                         self.auto_rel = @auto_rel
481                 end
482
483                 def auto_rel=(b)
484                         @auto_rel = b
485
486                         if @auto_rel && !@image.nil?
487                                 off = @rel[0].offset
488
489                                 @rel[1].set_rel(0.0, 0.0)
490                                 @rel[1].set_offset(off[0] + @image.image.width - 1,
491                                                    off[1] + @image.image.height - 1)
492                         end
493                 end
494
495                 def set_border(l = 0, r = 0, t = 0, b = 0)
496                         @border = [l, r, t, b]
497                 end
498
499                 def fill_smooth=(v)
500                         @fill_smooth = (v == true)
501                 end
502
503                 def set_fill_pos_rel(x, y)
504                         @fill_pos_rel = [x.to_f, y.to_f]
505                 end
506
507                 def set_fill_pos_abs(x, y)
508                         @fill_pos_abs = [x.to_i, y.to_i]
509                 end
510
511                 def set_fill_rel(x, y)
512                         @fill_rel = [x.to_f, y.to_f]
513                 end
514
515                 def set_fill_abs(x, y)
516                         @fill_abs = [x.to_i, y.to_i]
517                 end
518
519                 protected
520                 def to_eet_properties
521                         super.merge!(
522                         {"image.id" => [@image.nil? ? -1 : @image.id],
523                          "image.tween_list" => [@tweens],
524                          "border.l" => [@border[0]],
525                          "border.r" => [@border[1]],
526                          "border.t" => [@border[2]],
527                          "border.b" => [@border[3]],
528                          "border.no_fill" => [!@border_fill_middle],
529                          "fill.smooth" => [@fill_smooth],
530                          "fill.pos_rel_x" => [@fill_pos_rel[0], :double],
531                          "fill.pos_abs_x" => [@fill_pos_abs[0]],
532                          "fill.rel_x" => [@fill_rel[0], :double],
533                          "fill.abs_x" => [@fill_abs[0]],
534                          "fill.pos_rel_y" => [@fill_pos_rel[1], :double],
535                          "fill.pos_abs_y" => [@fill_pos_abs[1]],
536                          "fill.rel_y" => [@fill_rel[1], :double],
537                          "fill.abs_y" => [@fill_abs[1]]})
538                 end
539
540                 private
541                 def find_image(file)
542                         [".", OPTIONS.image_dir].each do |d|
543                                 f2 = File.join(d, file)
544                                 return Pathname.new(f2).cleanpath.to_s if File.file?(f2)
545                         end
546
547                         nil
548                 end
549         end
550
551         class TextDescription < Description
552                 attr_reader :font, :text, :font_size, :text_class
553
554                 def initialize(name = "default", value = 0.0)
555                         super
556
557                         @outline_color = [0, 0, 0, 255]
558                         @shadow_color = [0, 0, 0, 128]
559                         @text = ""
560                         @text_class = ""
561                         @font = ""
562                         @font_size = 0
563                         @fit = [false, false]
564                         @text_min = [false, false]
565                         @text_align = [0.5, 0.5]
566                         @text_id_source = -1
567                         @text_id_text_source = -1
568                 end
569
570                 def text=(v)
571                         @text = v.to_str.dup
572                 end
573
574                 def font_size=(v)
575                         @font_size = v.to_int
576                 end
577
578                 def text_class=(v)
579                         @text_class = v.to_str.dup
580                 end
581
582                 def set_fit(x = false, y = false)
583                         @fit = [x, y]
584                 end
585
586                 def set_text_min(x = false, y = false)
587                         @text_min = [x, y]
588                 end
589
590                 def set_text_align(x = 0.5, y = 0.5)
591                         @text_align = [x, y]
592                 end
593
594                 def font=(f)
595                         f = f.to_str.strip
596                         md = f.match(/.*\.ttf$/)
597                         unless md.nil?
598                                 f2 = find_font(f)
599                                 raise(RedactError, "cannot find font - #{f}") if f2.nil?
600
601                                 found = EDJE.font_dir.find { |font| font.filename == f2 }
602                                 if found.nil?
603                                         EDJE.font_dir << FontDirectoryEntry.new(f, f2)
604                                         @font = EDJE.font_dir.last.alias
605                                 else
606                                         @font = found.alias
607                                 end
608                         else
609                                 @font = f
610                         end
611                 end
612
613                 def outline_color=(c)
614                         @outline_color = parse_hex_color(c)
615                 end
616
617                 def shadow_color=(c)
618                         @shadow_color = parse_hex_color(c)
619                 end
620
621                 protected
622                 def to_eet_properties
623                         super.merge!(
624                         {"color2.r" => [@outline_color[0], :char],
625                          "color2.g" => [@outline_color[1], :char],
626                          "color2.b" => [@outline_color[2], :char],
627                          "color2.a" => [@outline_color[3], :char],
628                          "color3.r" => [@shadow_color[0], :char],
629                          "color3.g" => [@shadow_color[1], :char],
630                          "color3.b" => [@shadow_color[2], :char],
631                          "color3.a" => [@shadow_color[3], :char],
632                          "text.text" => [@text],
633                          "text.text_class" => [@text_class],
634                          "text.font" => [@font],
635                          "text.size" => [@font_size],
636                          "text.fit_x" => [@fit[0]],
637                          "text.fit_y" => [@fit[1]],
638                          "text.min_x" => [@text_min[0]],
639                          "text.min_y" => [@text_min[1]],
640                          "text.align.x" => [@text_align[0], :double],
641                          "text.align.y" => [@text_align[1], :double],
642                          "text.id_source" => [@text_id_source],
643                          "text.id_text_source" => [@text_id_text_source]})
644                 end
645
646                 private
647                 def find_font(file)
648                         [".", OPTIONS.font_dir].each do |d|
649                                 f2 = File.join(d, file)
650                                 return Pathname.new(f2).cleanpath.to_s if File.file?(f2)
651                         end
652
653                         nil
654                 end
655         end
656 end