3 # A.k.a `Best web-album Of the world, Or your money back, Humerus'.
5 # The acronyn sucks, however this is a tribute to Dragon Ball by
6 # Akira Toriyama, where the last enemy beaten by heroes of Dragon
7 # Ball is named "Boo". But there was already a free software project
8 # called Boo, so this one will be it "Booh". Or whatever.
11 # Copyright (c) 2004-2013 Guillaume Cottenceau
13 # This software may be freely redistributed under the terms of the GNU
14 # public license version 2.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 require 'rexml/document'
28 bindtextdomain("booh")
30 require 'booh/config.rb'
31 require 'booh/version.rb'
35 rescue LoadError, Gtk::InitError
39 require 'booh/libadds'
46 $CURRENT_CHARSET = `locale charmap`.chomp
47 #- check charset availability. a locale configuration of C or POSIX yields the unsupported 'ANSI_X3.4-1968'.
49 REXML::XMLDecl.new(REXML::XMLDecl::DEFAULT_VERSION, $CURRENT_CHARSET)
51 $CURRENT_CHARSET = 'UTF-8'
53 $convert = 'convert -interlace line +profile "*"'
54 $convert_enhance = '-contrast -enhance -normalize'
58 return GLib.convert(string, 'UTF-8', $CURRENT_CHARSET)
64 def utf8cut(string, maxlen)
66 return GLib.convert(string[0..maxlen-1], 'UTF-8', $CURRENT_CHARSET)
67 rescue Iconv::InvalidCharacter
68 return utf8cut(string, maxlen-1)
74 def sizename(key, translate)
75 #- fake for gettext to find these; if themes need more sizes, english name for them should be added here
76 sizenames = { 'small' => N_("small"), 'medium' => N_("medium"), 'large' => N_("large"),
77 'x-large' => N_("x-large"), 'xx-large' => N_("xx-large"),
78 'original' => N_("original") }
79 sizename = sizenames[key] || key
81 return utf8(_(sizename))
87 SUPPORTED_LANGUAGES = %w(en de fr ja eo)
90 langnames = { 'en' => _("english"), 'de' => _("german"), 'fr' => _("french"), 'ja' => _("japanese"), 'eo' => _("esperanto") }
91 return langnames[lang]
95 return GLib.convert(string, $CURRENT_CHARSET, 'UTF-8')
98 def from_utf8_safe(string)
100 return GLib.convert(string, $CURRENT_CHARSET, 'UTF-8')
101 rescue Iconv::IllegalSequence
106 def make_dest_filename_old(orig_filename)
107 #- we remove non alphanumeric characters but need to do that
108 #- cleverly to not end up with two similar dest filenames. we won't
109 #- urlencode because urldecode might happen in the browser.
110 return orig_filename.unpack("C*").collect { |v| v.chr =~ /[a-zA-Z\-_0-9\.\/]/ ? v.chr : sprintf("%2X", v) }.join
113 def make_dest_filename(orig_filename)
114 #- we remove non alphanumeric characters but need to do that
115 #- cleverly to not end up with two similar dest filenames. we won't
116 #- urlencode because urldecode might happen in the browser.
117 return orig_filename.unpack("C*").collect { |v| v.chr =~ /[a-zA-Z\-_0-9\.\/]/ ? v.chr : sprintf("~%02X", v) }.join
120 def msg(verbose_level, msg)
121 if verbose_level <= $verbose_level
122 if verbose_level == 0
123 warn _("\t***ERROR***: %s\n") % msg
124 elsif verbose_level == 1
125 warn _("\tWarning: %s\n") % msg
132 def msg_(verbose_level, msg)
133 if verbose_level <= $verbose_level
134 if verbose_level == 0
135 warn _("\t***ERROR***: %s") % msg
136 elsif verbose_level == 1
137 warn _("\tWarning: %s") % msg
149 def select_theme(name, limit_sizes, optimizefor32, nperrow)
151 msg 3, _("Selecting theme '%s'") % $theme
152 $themedir = "#{$FPATH}/themes/#{$theme}"
153 if !File.directory?($themedir)
154 themedir2 = File.expand_path("~/.booh-themes/#{$theme}")
155 if !File.directory?(themedir2)
156 die_ _("Theme was not found (tried %s and %s directories).") % [ $themedir, themedir2 ]
158 $themedir = themedir2
160 eval File.open("#{$themedir}/metadata/parameters.rb").readlines.join
163 if limit_sizes != 'all'
164 sizes = limit_sizes.split(/,/)
165 $images_size = $images_size.find_all { |e| sizes.include?(e['name']) }
166 if $images_size.length == 0
167 die_ _("Can't carry on, no valid size selected.")
171 $images_size = $images_size.find_all { |e| !e['optional'] }
175 $images_size.each { |e|
176 e['thumbnails'].gsub!(/(\d+x)(\d+)/) { $1 + ($2.to_f*8/9).to_i.to_s } #- 4/3 / 3/2
180 if nperrow && nperrow != $default_N
181 ratio = nperrow.to_f / $default_N.to_f
182 $images_size.each { |e|
183 e['thumbnails'].gsub!(/(\d+)x(\d+)/) { ($1.to_f/ratio).to_i.to_s + 'x' + ($2.to_f/ratio).to_i.to_s }
187 $default_size = $images_size.detect { |sizeobj| sizeobj['default'] }
188 if $default_size == nil
189 $default_size = $images_size[0]
193 def entry2type(entry)
194 #- /usr/lib/gdk-pixbuf/loaders/libpixbufloader-bmp.so
195 #- /usr/lib/gdk-pixbuf/loaders/libpixbufloader-gif.so
196 #- /usr/lib/gdk-pixbuf/loaders/libpixbufloader-ico.so
197 #- /usr/lib/gdk-pixbuf/loaders/libpixbufloader-jpeg.so
198 #- /usr/lib/gdk-pixbuf/loaders/libpixbufloader-png.so
199 #- /usr/lib/gdk-pixbuf/loaders/libpixbufloader-pnm.so
200 #- /usr/lib/gdk-pixbuf/loaders/libpixbufloader-ras.so
201 #- /usr/lib/gdk-pixbuf/loaders/libpixbufloader-tiff.so
202 #- /usr/lib/gdk-pixbuf/loaders/libpixbufloader-xbm.so
203 #- /usr/lib/gdk-pixbuf/loaders/libpixbufloader-xpm.so
204 if entry =~ /\.(bmp|gif|ico|jpg|jpe|jpeg|png|pnm|tif|xbm|xpm)$/i && entry !~ /['"\[\]]/
206 elsif !$ignore_videos && entry =~ /\.(mov|avi|mpg|mpeg|mpe|wmv|asx|3gp|mp4|ogm|ogv|flv|f4v|f4p|dv)$/i && entry !~ /['"\[\]]/
207 #- might consider using file magic later..
220 finished = Process.wait2
221 $pids.delete(finished[0])
222 $pids = $pids.find_all { |pid| Process.waitpid(pid, Process::WNOHANG) == nil }
226 while $pids && $pids.length > 0
231 #- parallelizable sys
241 if $pids.length == $mproc
249 def get_image_size(fullpath)
254 if $sizes_cache[fullpath].nil?
255 #- identify is slow, try with gdk if available (negligible vs 35ms)
257 if `identify '#{fullpath}'` =~ / JPEG (\d+)x(\d+) /
258 $sizes_cache[fullpath] = { :x => $1.to_i, :y => $2.to_i }
261 format, width, height = GdkPixbuf::Pixbuf.get_file_info(fullpath)
263 $sizes_cache[fullpath] = { :x => width, :y => height }
267 return $sizes_cache[fullpath]
273 #- commify from http://pleac.sourceforge.net/ (pleac rulz)
275 n.to_s =~ /([^\.]*)(\..*)?/
276 int, dec = $1.reverse, $2 ? $2 : ""
278 while int.gsub!(/(#{Regexp.quote(sep)}|\.|^)(\d{3})(\d)/, '\1\2' + sep + '\3')
283 def guess_rotate(filename)
284 #- identify is slow, try with libexiv2 if available (4ms vs 35ms)
289 orientation = `identify -format "%[EXIF:orientation]" '#{filename}'`.chomp.to_i
291 orientation = Exif.orientation(filename)
296 elsif orientation == 8
302 #- remove rotate if image is obviously already in portrait (situation can come from gthumb)
303 size = get_image_size(filename)
304 if size && size[:x] < size[:y]
311 def angle_to_exif_orientation(angle)
314 elsif angle == 270 || angle == -90
321 def rotate_pixbuf(pixbuf, angle)
322 return pixbuf.rotate(angle == 90 ? Gdk::Pixbuf::ROTATE_CLOCKWISE :
323 angle == 180 ? Gdk::Pixbuf::ROTATE_UPSIDEDOWN :
324 (angle == 270 || angle == -90) ? Gdk::Pixbuf::ROTATE_COUNTERCLOCKWISE :
325 Gdk::Pixbuf::ROTATE_NONE)
328 def gen_thumbnails_element(orig, xmldirorelem, allow_background, dests)
329 rexml_thread_protect {
330 if xmldirorelem.name == 'dir'
331 xmldirorelem = xmldirorelem.elements["*[@filename='#{utf8(File.basename(orig))}']"]
334 gen_thumbnails(orig, allow_background, dests, xmldirorelem, '')
337 def gen_thumbnails_subdir(orig, xmldirorelem, allow_background, dests, type)
338 #- type can be `subdirs' or `thumbnails'
339 gen_thumbnails(orig, allow_background, dests, xmldirorelem, type + '-')
342 $video_thumbnail_directory_lock = Monitor.new
344 def gen_video_thumbnail(orig, colorswap, seektime)
346 #- ignored for the moment. is mplayer subject to blue faces problem?
348 #- it's not possible to specify a basename for the output jpeg file with mplayer (file will be named 00000001.jpg); as this can
349 #- be called from multiple threads, we must come up with a unique directory where to put the file
350 tmpfile = Tempfile.new("boohvideotmp")
352 $video_thumbnail_directory_lock.synchronize {
353 tmpdirname = tmpfile.path
356 Dir.mkdir(tmpdirname)
358 raise "Tmp directory #{tmpdirname} already exists"
361 cmd = "mplayer '#{orig}' -nosound -vo jpeg:outdir='#{tmpdirname}' -frames 1 -ss #{seektime} -slave >/dev/null 2>/dev/null"
363 if ! File.exists?("#{tmpdirname}/00000001.jpg")
364 msg 0, _("specified seektime too large? that may also be another probleme. try another value.")
365 Dir.rmdir(tmpdirname)
371 def gen_thumbnails(orig, allow_background, dests, felem, attributes_prefix)
372 if !dests.detect { |dest| !File.exists?(dest['filename']) }
377 dest_dir = make_dest_filename(File.dirname(dests[0]['filename']))
379 if entry2type(orig) == 'image'
381 if whitebalance = rexml_thread_protect { felem.attributes["#{attributes_prefix}white-balance"] }
382 neworig = "#{dest_dir}/#{File.basename(orig)}-whitebalance#{whitebalance}.jpg"
383 cmd = "booh-fix-whitebalance '#{orig}' '#{neworig}' #{whitebalance}"
385 if File.exists?(neworig)
389 if gammacorrect = rexml_thread_protect { felem.attributes["#{attributes_prefix}gamma-correction"] }
390 neworig = "#{dest_dir}/#{File.basename(orig)}-gammacorrect#{gammacorrect}.jpg"
391 cmd = "booh-gamma-correction '#{orig}' '#{neworig}' #{gammacorrect}"
393 if File.exists?(neworig)
397 rotate = rexml_thread_protect { felem.attributes["#{attributes_prefix}rotate"] }
399 rexml_thread_protect { felem.add_attribute("#{attributes_prefix}rotate", rotate = guess_rotate(orig).to_s) }
401 convert_options += "-rotate #{rotate} "
402 if rexml_thread_protect { felem.attributes["#{attributes_prefix}enhance"] }
403 convert_options += ($config['convert-enhance'] || $convert_enhance) + " "
407 if !File.exists?(dest['filename'])
409 cmd ||= "#{$convert} #{convert_options}-size #{dest['size']} -resize '#{dest['size']}>' '#{orig}' '#{dest['filename']}'"
424 #- can happen on race conditions for generating multiple times a thumbnail for a given image. for the moment,
425 #- silently ignore, it is not a so big deal.
430 elsif entry2type(orig) == 'video'
432 #- seektime is an attribute that allows to specify where the frame to use for the thumbnail must be taken
433 seektime = rexml_thread_protect { felem.attributes["#{attributes_prefix}seektime"] }
435 rexml_thread_protect { felem.add_attribute("#{attributes_prefix}seektime", seektime = "0") }
437 seektime = seektime.to_f
438 if rotate = rexml_thread_protect { felem.attributes["#{attributes_prefix}rotate"] }
439 convert_options += "-rotate #{rotate} "
441 if rexml_thread_protect { felem.attributes["#{attributes_prefix}enhance"] }
442 convert_options += ($config['convert-enhance'] || $convert_enhance) + " "
446 if ! File.exists?(dest['filename'])
447 tmpdir = gen_video_thumbnail(orig, felem && rexml_thread_protect { felem.attributes["#{attributes_prefix}color-swap"] }, seektime)
451 tmpfile = "#{tmpdir}/00000001.jpg"
452 alltmpfiles = [ tmpfile ]
453 if felem && whitebalance = rexml_thread_protect { felem.attributes["#{attributes_prefix}white-balance"] }
454 if whitebalance.to_f != 0
455 neworig = "#{tmpdir}/whitebalance#{whitebalance}.jpg"
456 cmd = "booh-fix-whitebalance '#{tmpfile}' '#{neworig}' #{whitebalance}"
458 if File.exists?(neworig)
460 alltmpfiles << neworig
464 if felem && gammacorrect = rexml_thread_protect { felem.attributes["#{attributes_prefix}gamma-correction"] }
465 if gammacorrect.to_f != 0
466 neworig = "#{tmpdir}/gammacorrect#{gammacorrect}.jpg"
467 cmd = "booh-gamma-correction '#{tmpfile}' '#{neworig}' #{gammacorrect}"
469 if File.exists?(neworig)
471 alltmpfiles << neworig
475 sys("#{$convert} #{convert_options}-size #{dest['size']} -resize #{dest['size']} '#{tmpfile}' '#{dest['filename']}'")
476 alltmpfiles.each { |file| File.delete(file) }
484 def invornil(obj, methodname)
488 return obj.method(methodname).call
492 def find_subalbum_info_type(xmldir)
493 #- first look for subdirs info; if not, means there is no subdir
494 if xmldir.attributes['subdirs-caption']
501 def find_subalbum_caption_info(xmldir)
502 type = find_subalbum_info_type(xmldir)
503 return [ from_utf8(xmldir.attributes["#{type}-captionfile"]), xmldir.attributes["#{type}-caption"] ]
508 return File.size(path)
514 def writable(directory)
515 #- File.stat().writable? yields false on sshfs writable directories
516 randfilename = directory + '/' + rand(36**10).to_s(36)
518 File.open(randfilename, "w")
520 File.delete(randfilename)
527 puts "Unexpected error for " + directory + ": #{$!}"
537 n < a ? a : n > b ? b : n
540 def pano_amount(elem)
541 if pano_amount = elem.attributes['pano-amount']
543 return clamp(pano_amount.to_f, 1, $N_per_row.to_i)
545 return clamp(pano_amount.to_f, 1, $default_N.to_i)
552 def substInFile(name)
553 newcontent = IO.readlines(name).collect { |l| yield l }
554 ios = File.open(name, "w")
555 ios.write(newcontent.join)
559 $xmlaccesslock = Monitor.new
561 def rexml_thread_protect(&proc)
562 $xmlaccesslock.synchronize {
567 def check_multi_binaries(input)
568 #- e.g. check at least one binary from '/usr/bin/gimp-remote %f || /usr/bin/gimp %f' is available
569 for attempts in input.split('||')
570 binary = attempts.split.first
571 if binary && File.executable?(binary)
575 #- return last tried binary for error message
580 if last_failed_binary = check_multi_binaries($config['browser'])
581 show_popup($main_window, utf8(_("The configured browser seems to be unavailable.
582 You should fix this in Edit/Preferences so that you can open URLs.
584 Problem was: '%s' is not an executable file.") % last_failed_binary), { :pos_centered => true, :not_transient => true })
593 cmd = $config['browser'].gsub('%f', "'#{url}'") + ' &'
601 GNU GENERAL PUBLIC LICENSE
604 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
605 675 Mass Ave, Cambridge, MA 02139, USA
606 Everyone is permitted to copy and distribute verbatim copies
607 of this license document, but changing it is not allowed.
611 The licenses for most software are designed to take away your
612 freedom to share and change it. By contrast, the GNU General Public
613 License is intended to guarantee your freedom to share and change free
614 software--to make sure the software is free for all its users. This
615 General Public License applies to most of the Free Software
616 Foundation's software and to any other program whose authors commit to
617 using it. (Some other Free Software Foundation software is covered by
618 the GNU Library General Public License instead.) You can apply it to
621 When we speak of free software, we are referring to freedom, not
622 price. Our General Public Licenses are designed to make sure that you
623 have the freedom to distribute copies of free software (and charge for
624 this service if you wish), that you receive source code or can get it
625 if you want it, that you can change the software or use pieces of it
626 in new free programs; and that you know you can do these things.
628 To protect your rights, we need to make restrictions that forbid
629 anyone to deny you these rights or to ask you to surrender the rights.
630 These restrictions translate to certain responsibilities for you if you
631 distribute copies of the software, or if you modify it.
633 For example, if you distribute copies of such a program, whether
634 gratis or for a fee, you must give the recipients all the rights that
635 you have. You must make sure that they, too, receive or can get the
636 source code. And you must show them these terms so they know their
639 We protect your rights with two steps: (1) copyright the software, and
640 (2) offer you this license which gives you legal permission to copy,
641 distribute and/or modify the software.
643 Also, for each author's protection and ours, we want to make certain
644 that everyone understands that there is no warranty for this free
645 software. If the software is modified by someone else and passed on, we
646 want its recipients to know that what they have is not the original, so
647 that any problems introduced by others will not reflect on the original
648 authors' reputations.
650 Finally, any free program is threatened constantly by software
651 patents. We wish to avoid the danger that redistributors of a free
652 program will individually obtain patent licenses, in effect making the
653 program proprietary. To prevent this, we have made it clear that any
654 patent must be licensed for everyone's free use or not licensed at all.
656 The precise terms and conditions for copying, distribution and
660 GNU GENERAL PUBLIC LICENSE
661 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
663 0. This License applies to any program or other work which contains
664 a notice placed by the copyright holder saying it may be distributed
665 under the terms of this General Public License. The "Program", below,
666 refers to any such program or work, and a "work based on the Program"
667 means either the Program or any derivative work under copyright law:
668 that is to say, a work containing the Program or a portion of it,
669 either verbatim or with modifications and/or translated into another
670 language. (Hereinafter, translation is included without limitation in
671 the term "modification".) Each licensee is addressed as "you".
673 Activities other than copying, distribution and modification are not
674 covered by this License; they are outside its scope. The act of
675 running the Program is not restricted, and the output from the Program
676 is covered only if its contents constitute a work based on the
677 Program (independent of having been made by running the Program).
678 Whether that is true depends on what the Program does.
680 1. You may copy and distribute verbatim copies of the Program's
681 source code as you receive it, in any medium, provided that you
682 conspicuously and appropriately publish on each copy an appropriate
683 copyright notice and disclaimer of warranty; keep intact all the
684 notices that refer to this License and to the absence of any warranty;
685 and give any other recipients of the Program a copy of this License
686 along with the Program.
688 You may charge a fee for the physical act of transferring a copy, and
689 you may at your option offer warranty protection in exchange for a fee.
691 2. You may modify your copy or copies of the Program or any portion
692 of it, thus forming a work based on the Program, and copy and
693 distribute such modifications or work under the terms of Section 1
694 above, provided that you also meet all of these conditions:
696 a) You must cause the modified files to carry prominent notices
697 stating that you changed the files and the date of any change.
699 b) You must cause any work that you distribute or publish, that in
700 whole or in part contains or is derived from the Program or any
701 part thereof, to be licensed as a whole at no charge to all third
702 parties under the terms of this License.
704 c) If the modified program normally reads commands interactively
705 when run, you must cause it, when started running for such
706 interactive use in the most ordinary way, to print or display an
707 announcement including an appropriate copyright notice and a
708 notice that there is no warranty (or else, saying that you provide
709 a warranty) and that users may redistribute the program under
710 these conditions, and telling the user how to view a copy of this
711 License. (Exception: if the Program itself is interactive but
712 does not normally print such an announcement, your work based on
713 the Program is not required to print an announcement.)
716 These requirements apply to the modified work as a whole. If
717 identifiable sections of that work are not derived from the Program,
718 and can be reasonably considered independent and separate works in
719 themselves, then this License, and its terms, do not apply to those
720 sections when you distribute them as separate works. But when you
721 distribute the same sections as part of a whole which is a work based
722 on the Program, the distribution of the whole must be on the terms of
723 this License, whose permissions for other licensees extend to the
724 entire whole, and thus to each and every part regardless of who wrote it.
726 Thus, it is not the intent of this section to claim rights or contest
727 your rights to work written entirely by you; rather, the intent is to
728 exercise the right to control the distribution of derivative or
729 collective works based on the Program.
731 In addition, mere aggregation of another work not based on the Program
732 with the Program (or with a work based on the Program) on a volume of
733 a storage or distribution medium does not bring the other work under
734 the scope of this License.
736 3. You may copy and distribute the Program (or a work based on it,
737 under Section 2) in object code or executable form under the terms of
738 Sections 1 and 2 above provided that you also do one of the following:
740 a) Accompany it with the complete corresponding machine-readable
741 source code, which must be distributed under the terms of Sections
742 1 and 2 above on a medium customarily used for software interchange; or,
744 b) Accompany it with a written offer, valid for at least three
745 years, to give any third party, for a charge no more than your
746 cost of physically performing source distribution, a complete
747 machine-readable copy of the corresponding source code, to be
748 distributed under the terms of Sections 1 and 2 above on a medium
749 customarily used for software interchange; or,
751 c) Accompany it with the information you received as to the offer
752 to distribute corresponding source code. (This alternative is
753 allowed only for noncommercial distribution and only if you
754 received the program in object code or executable form with such
755 an offer, in accord with Subsection b above.)
757 The source code for a work means the preferred form of the work for
758 making modifications to it. For an executable work, complete source
759 code means all the source code for all modules it contains, plus any
760 associated interface definition files, plus the scripts used to
761 control compilation and installation of the executable. However, as a
762 special exception, the source code distributed need not include
763 anything that is normally distributed (in either source or binary
764 form) with the major components (compiler, kernel, and so on) of the
765 operating system on which the executable runs, unless that component
766 itself accompanies the executable.
768 If distribution of executable or object code is made by offering
769 access to copy from a designated place, then offering equivalent
770 access to copy the source code from the same place counts as
771 distribution of the source code, even though third parties are not
772 compelled to copy the source along with the object code.
775 4. You may not copy, modify, sublicense, or distribute the Program
776 except as expressly provided under this License. Any attempt
777 otherwise to copy, modify, sublicense or distribute the Program is
778 void, and will automatically terminate your rights under this License.
779 However, parties who have received copies, or rights, from you under
780 this License will not have their licenses terminated so long as such
781 parties remain in full compliance.
783 5. You are not required to accept this License, since you have not
784 signed it. However, nothing else grants you permission to modify or
785 distribute the Program or its derivative works. These actions are
786 prohibited by law if you do not accept this License. Therefore, by
787 modifying or distributing the Program (or any work based on the
788 Program), you indicate your acceptance of this License to do so, and
789 all its terms and conditions for copying, distributing or modifying
790 the Program or works based on it.
792 6. Each time you redistribute the Program (or any work based on the
793 Program), the recipient automatically receives a license from the
794 original licensor to copy, distribute or modify the Program subject to
795 these terms and conditions. You may not impose any further
796 restrictions on the recipients' exercise of the rights granted herein.
797 You are not responsible for enforcing compliance by third parties to
800 7. If, as a consequence of a court judgment or allegation of patent
801 infringement or for any other reason (not limited to patent issues),
802 conditions are imposed on you (whether by court order, agreement or
803 otherwise) that contradict the conditions of this License, they do not
804 excuse you from the conditions of this License. If you cannot
805 distribute so as to satisfy simultaneously your obligations under this
806 License and any other pertinent obligations, then as a consequence you
807 may not distribute the Program at all. For example, if a patent
808 license would not permit royalty-free redistribution of the Program by
809 all those who receive copies directly or indirectly through you, then
810 the only way you could satisfy both it and this License would be to
811 refrain entirely from distribution of the Program.
813 If any portion of this section is held invalid or unenforceable under
814 any particular circumstance, the balance of the section is intended to
815 apply and the section as a whole is intended to apply in other
818 It is not the purpose of this section to induce you to infringe any
819 patents or other property right claims or to contest validity of any
820 such claims; this section has the sole purpose of protecting the
821 integrity of the free software distribution system, which is
822 implemented by public license practices. Many people have made
823 generous contributions to the wide range of software distributed
824 through that system in reliance on consistent application of that
825 system; it is up to the author/donor to decide if he or she is willing
826 to distribute software through any other system and a licensee cannot
829 This section is intended to make thoroughly clear what is believed to
830 be a consequence of the rest of this License.
833 8. If the distribution and/or use of the Program is restricted in
834 certain countries either by patents or by copyrighted interfaces, the
835 original copyright holder who places the Program under this License
836 may add an explicit geographical distribution limitation excluding
837 those countries, so that distribution is permitted only in or among
838 countries not thus excluded. In such case, this License incorporates
839 the limitation as if written in the body of this License.
841 9. The Free Software Foundation may publish revised and/or new versions
842 of the General Public License from time to time. Such new versions will
843 be similar in spirit to the present version, but may differ in detail to
844 address new problems or concerns.
846 Each version is given a distinguishing version number. If the Program
847 specifies a version number of this License which applies to it and "any
848 later version", you have the option of following the terms and conditions
849 either of that version or of any later version published by the Free
850 Software Foundation. If the Program does not specify a version number of
851 this License, you may choose any version ever published by the Free Software
854 10. If you wish to incorporate parts of the Program into other free
855 programs whose distribution conditions are different, write to the author
856 to ask for permission. For software which is copyrighted by the Free
857 Software Foundation, write to the Free Software Foundation; we sometimes
858 make exceptions for this. Our decision will be guided by the two goals
859 of preserving the free status of all derivatives of our free software and
860 of promoting the sharing and reuse of software generally.
864 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
865 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
866 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
867 PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
868 OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
869 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
870 TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
871 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
872 REPAIR OR CORRECTION.
874 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
875 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
876 REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
877 INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
878 OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
879 TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
880 YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
881 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
882 POSSIBILITY OF SUCH DAMAGES.
887 Gtk::AboutDialog.set_url_hook { |dialog, url| open_url(url) }
888 Gtk::AboutDialog.show($main_window, { :name => 'booh',
889 :version => $VERSION,
890 :copyright => 'Copyright (c) 2005-2013 Guillaume Cottenceau',
891 :license => get_license,
892 :website => 'http://booh.org/',
893 :authors => [ 'Guillaume Cottenceau' ],
894 :artists => [ 'Ayo73' ],
895 :comments => utf8(_("''The Web-Album of choice for discriminating Linux users''")),
896 :translator_credits => utf8(_('Esperanto: Stephane Fillod
897 Japanese: Masao Mutoh
898 German: Roland Eckert
899 French: Guillaume Cottenceau')),
900 :logo => GdkPixbuf::Pixbuf.new(:file => "#{$FPATH}/images/logo.png") })
903 def smartsort(entries, sort_criterions)
904 #- sort "entries" according to "sort_criterions" but find a good fallback for all entries without a
905 #- criterion value (still next to the item they were next to)
906 sorted_entries = sort_criterions.keys.sort { |a,b| sort_criterions[a] <=> sort_criterions[b] }
907 for i in 0 .. entries.size - 1
908 if ! sorted_entries.include?(entries[i])
910 while j > 0 && ! sorted_entries.include?(entries[j])
913 sorted_entries[(sorted_entries.index(entries[j]) || -1 ) + 1, 0] = entries[i]
916 return sorted_entries
919 def defer_translation(msg)
925 w.icon_list = [ GdkPixbuf::Pixbuf.new(:file => "#{$FPATH}/images/booh-16x16.png"),
926 GdkPixbuf::Pixbuf.new(:file => "#{$FPATH}/images/booh-32x32.png"),
927 GdkPixbuf::Pixbuf.new(:file => "#{$FPATH}/images/booh-48x48.png") ]
935 if !self || self.to_s == 'false'
944 def File.reduce_path(path)
945 return path.gsub(/\w+\/\.\.\//, '')
950 def collect_with_index
952 each_with_index { |e,i|
962 each { |v| retval += v.to_i }
968 def previous_element_byname(name)
970 while n = n.previous_element
978 def previous_element_byname_notattr(name, attr)
980 while n = n.previous_element
981 if n.name == name && !n.attributes[attr]
988 def next_element_byname(name)
990 while n = n.next_element
998 def next_element_byname_notattr(name, attr)
1000 while n = n.next_element
1001 if n.name == name && !n.attributes[attr]
1008 def child_byname_notattr(name, attr)
1009 elements.each(name) { |element|
1010 if !element.attributes[attr]