mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@22784 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
		
			
				
	
	
		
			456 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			456 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
#!/usr/bin/env ruby
 | 
						|
require 'tk'
 | 
						|
 | 
						|
begin
 | 
						|
  # try to use Img extension
 | 
						|
  require 'tkextlib/tkimg'
 | 
						|
rescue Exception
 | 
						|
  # cannot use Img extention --> ignore
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
############################
 | 
						|
# scrolled_canvas
 | 
						|
class TkScrolledCanvas < TkCanvas
 | 
						|
  include TkComposite
 | 
						|
 | 
						|
  def initialize_composite(keys={})
 | 
						|
    @h_scr = TkScrollbar.new(@frame)
 | 
						|
    @v_scr = TkScrollbar.new(@frame)
 | 
						|
 | 
						|
    @canvas = TkCanvas.new(@frame)
 | 
						|
    @path = @canvas.path
 | 
						|
 | 
						|
    @canvas.xscrollbar(@h_scr)
 | 
						|
    @canvas.yscrollbar(@v_scr)
 | 
						|
 | 
						|
    TkGrid.rowconfigure(@frame, 0, :weight=>1, :minsize=>0)
 | 
						|
    TkGrid.columnconfigure(@frame, 0, :weight=>1, :minsize=>0)
 | 
						|
 | 
						|
    @canvas.grid(:row=>0, :column=>0, :sticky=>'news')
 | 
						|
    @h_scr.grid(:row=>1, :column=>0, :sticky=>'ew')
 | 
						|
    @v_scr.grid(:row=>0, :column=>1, :sticky=>'ns')
 | 
						|
 | 
						|
    delegate('DEFAULT', @canvas)
 | 
						|
    delegate('background', @canvas, @h_scr, @v_scr)
 | 
						|
    delegate('activebackground', @h_scr, @v_scr)
 | 
						|
    delegate('troughcolor', @h_scr, @v_scr)
 | 
						|
    delegate('repeatdelay', @h_scr, @v_scr)
 | 
						|
    delegate('repeatinterval', @h_scr, @v_scr)
 | 
						|
    delegate('borderwidth', @frame)
 | 
						|
    delegate('relief', @frame)
 | 
						|
 | 
						|
    delegate_alias('canvasborderwidth', 'borderwidth', @canvas)
 | 
						|
    delegate_alias('canvasrelief', 'relief', @canvas)
 | 
						|
 | 
						|
    delegate_alias('scrollbarborderwidth', 'borderwidth', @h_scr, @v_scr)
 | 
						|
    delegate_alias('scrollbarrelief', 'relief', @h_scr, @v_scr)
 | 
						|
 | 
						|
    configure(keys) unless keys.empty?
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
############################
 | 
						|
class PhotoCanvas < TkScrolledCanvas
 | 
						|
 | 
						|
USAGE = <<EOT
 | 
						|
--- WHAT IS ---
 | 
						|
You can write comments on the loaded image, and save it as a Postscipt
 | 
						|
file (original image file is not modified). Each comment is drawn as a
 | 
						|
set of an indicator circle, an arrow, and a memo text. See the following
 | 
						|
how to write comments.
 | 
						|
This can save the list of memo texts to another file. It may useful to
 | 
						|
search the saved Postscript file by the comments on them.
 | 
						|
This may not support multibyte characters (multibyte texts are broken on
 | 
						|
a Postscript file). It depends on features of canvas widgets of Tcl/Tk
 | 
						|
libraries linked your Ruby/Tk. If you use Tcl/Tk8.0-jp (Japanized Tcl/Tk),
 | 
						|
you can (possibly) get a Japanese Postscript file.
 | 
						|
 | 
						|
--- BINDINGS ---
 | 
						|
* Button-1 : draw comments by following steps
 | 
						|
    1st - Set center of a indicator circle.
 | 
						|
    2nd - Set head position of an arrow.
 | 
						|
    3rd - Set tail position of an arrow, and show an entry box.
 | 
						|
          Input a memo text and hit 'Enter' key to entry the comment.
 | 
						|
 | 
						|
* Button-2-drag : scroll the canvas
 | 
						|
 | 
						|
* Button-3 : when drawing, cancel current drawing
 | 
						|
 | 
						|
* Double-Button-3 : delete the clicked comment (text, arrow, and circle)
 | 
						|
EOT
 | 
						|
 | 
						|
  def initialize(*args)
 | 
						|
    super(*args)
 | 
						|
 | 
						|
    self.highlightthickness = 0
 | 
						|
    self.selectborderwidth = 0
 | 
						|
 | 
						|
    @photo = TkPhotoImage.new
 | 
						|
    @img = TkcImage.new(self, 0, 0, :image=>@photo)
 | 
						|
 | 
						|
    width  = self.width
 | 
						|
    height = self.height
 | 
						|
    @scr_region = [-width, -height, width, height]
 | 
						|
    self.scrollregion(@scr_region)
 | 
						|
    self.xview_moveto(0.25)
 | 
						|
    self.yview_moveto(0.25)
 | 
						|
 | 
						|
    @col = 'red'
 | 
						|
    @font = 'Helvetica -12'
 | 
						|
 | 
						|
    @memo_id_num = -1
 | 
						|
    @memo_id_head = 'memo_'
 | 
						|
    @memo_id_tag = nil
 | 
						|
    @overlap_d = 2
 | 
						|
 | 
						|
    @state = TkVariable.new
 | 
						|
    @border = 2
 | 
						|
    @selectborder = 1
 | 
						|
    @delta = @border + @selectborder
 | 
						|
    @entry = TkEntry.new(self, :relief=>:ridge, :borderwidth=>@border,
 | 
						|
                         :selectborderwidth=>@selectborder,
 | 
						|
                         :highlightthickness=>0)
 | 
						|
    @entry.bind('Return'){@state.value = 0}
 | 
						|
 | 
						|
    @mode = old_mode = 0
 | 
						|
 | 
						|
    _state0()
 | 
						|
 | 
						|
    bind('2', :x, :y){|x,y| scan_mark(x,y)}
 | 
						|
    bind('B2-Motion', :x, :y){|x,y| scan_dragto(x,y)}
 | 
						|
 | 
						|
    bind('3'){
 | 
						|
      next if (old_mode = @mode) == 0
 | 
						|
      @items.each{|item| item.delete }
 | 
						|
      _state0()
 | 
						|
    }
 | 
						|
 | 
						|
    bind('Double-3', :widget, :x, :y){|w, x, y|
 | 
						|
      next if old_mode != 0
 | 
						|
      x = w.canvasx(x)
 | 
						|
      y = w.canvasy(y)
 | 
						|
      tag = nil
 | 
						|
      w.find_overlapping(x - @overlap_d, y - @overlap_d,
 | 
						|
                         x + @overlap_d, y + @overlap_d).find{|item|
 | 
						|
        ! (item.tags.find{|name|
 | 
						|
             if name =~ /^(#{@memo_id_head}\d+)$/
 | 
						|
               tag = $1
 | 
						|
             end
 | 
						|
           }.empty?)
 | 
						|
      }
 | 
						|
      w.delete(tag) if tag
 | 
						|
    }
 | 
						|
  end
 | 
						|
 | 
						|
  #-----------------------------------
 | 
						|
  private
 | 
						|
  def _state0() # init
 | 
						|
    @mode = 0
 | 
						|
 | 
						|
    @memo_id_num += 1
 | 
						|
    @memo_id_tag = @memo_id_head + @memo_id_num.to_s
 | 
						|
 | 
						|
    @target = nil
 | 
						|
    @items = []
 | 
						|
    @mark = [0, 0]
 | 
						|
    bind_remove('Motion')
 | 
						|
    bind('ButtonRelease-1', proc{|x,y| _state1(x,y)}, '%x', '%y')
 | 
						|
  end
 | 
						|
 | 
						|
  def _state1(x,y) # set center
 | 
						|
    @mode = 1
 | 
						|
 | 
						|
    @target = TkcOval.new(self,
 | 
						|
                          [canvasx(x), canvasy(y)], [canvasx(x), canvasy(y)],
 | 
						|
                          :outline=>@col, :width=>3, :tags=>[@memo_id_tag])
 | 
						|
    @items << @target
 | 
						|
    @mark = [x,y]
 | 
						|
 | 
						|
    bind('Motion', proc{|x,y| _state2(x,y)}, '%x', '%y')
 | 
						|
    bind('ButtonRelease-1', proc{|x,y| _state3(x,y)}, '%x', '%y')
 | 
						|
  end
 | 
						|
 | 
						|
  def _state2(x,y) # create circle
 | 
						|
    @mode = 2
 | 
						|
 | 
						|
    r = Integer(Math.sqrt((x-@mark[0])**2 + (y-@mark[1])**2))
 | 
						|
    @target.coords([canvasx(@mark[0] - r), canvasy(@mark[1] - r)],
 | 
						|
                   [canvasx(@mark[0] + r), canvasy(@mark[1] + r)])
 | 
						|
  end
 | 
						|
 | 
						|
  def _state3(x,y) # set line start
 | 
						|
    @mode = 3
 | 
						|
 | 
						|
    @target = TkcLine.new(self,
 | 
						|
                          [canvasx(x), canvasy(y)], [canvasx(x), canvasy(y)],
 | 
						|
                          :arrow=>:first, :arrowshape=>[10, 14, 5],
 | 
						|
                          :fill=>@col, :tags=>[@memo_id_tag])
 | 
						|
    @items << @target
 | 
						|
    @mark = [x, y]
 | 
						|
 | 
						|
    bind('Motion', proc{|x,y| _state4(x,y)}, '%x', '%y')
 | 
						|
    bind('ButtonRelease-1', proc{|x,y| _state5(x,y)}, '%x', '%y')
 | 
						|
  end
 | 
						|
 | 
						|
  def _state4(x,y) # create line
 | 
						|
    @mode = 4
 | 
						|
 | 
						|
    @target.coords([canvasx(@mark[0]), canvasy(@mark[1])],
 | 
						|
                   [canvasx(x), canvasy(y)])
 | 
						|
  end
 | 
						|
 | 
						|
  def _state5(x,y) # set text
 | 
						|
    @mode = 5
 | 
						|
 | 
						|
    if x - @mark[0] >= 0
 | 
						|
      justify = 'left'
 | 
						|
      dx = - @delta
 | 
						|
 | 
						|
      if y - @mark[1] >= 0
 | 
						|
        anchor = 'nw'
 | 
						|
        dy = - @delta
 | 
						|
      else
 | 
						|
        anchor = 'sw'
 | 
						|
        dy = @delta
 | 
						|
      end
 | 
						|
    else
 | 
						|
      justify = 'right'
 | 
						|
      dx = @delta
 | 
						|
 | 
						|
      if y - @mark[1] >= 0
 | 
						|
        anchor = 'ne'
 | 
						|
        dy = - @delta
 | 
						|
      else
 | 
						|
        anchor = 'se'
 | 
						|
        dy = @delta
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    bind_remove('Motion')
 | 
						|
 | 
						|
    @entry.value = ''
 | 
						|
    @entry.configure(:justify=>justify, :font=>@font, :foreground=>@col)
 | 
						|
 | 
						|
    ewin = TkcWindow.new(self, [canvasx(x)+dx, canvasy(y)+dy],
 | 
						|
                         :window=>@entry, :state=>:normal, :anchor=>anchor,
 | 
						|
                         :tags=>[@memo_id_tag])
 | 
						|
 | 
						|
    @entry.focus
 | 
						|
    @entry.grab
 | 
						|
    @state.wait
 | 
						|
    @entry.grab_release
 | 
						|
 | 
						|
    ewin.delete
 | 
						|
 | 
						|
    @target = TkcText.new(self, [canvasx(x), canvasy(y)],
 | 
						|
                          :anchor=>anchor, :justify=>justify,
 | 
						|
                          :fill=>@col, :font=>@font, :text=>@entry.value,
 | 
						|
                          :tags=>[@memo_id_tag])
 | 
						|
 | 
						|
    _state0()
 | 
						|
  end
 | 
						|
 | 
						|
  #-----------------------------------
 | 
						|
  public
 | 
						|
  def load_photo(filename)
 | 
						|
    @photo.configure(:file=>filename)
 | 
						|
  end
 | 
						|
 | 
						|
  def modified?
 | 
						|
    ! ((find_withtag('all') - [@img]).empty?)
 | 
						|
  end
 | 
						|
 | 
						|
  def fig_erase
 | 
						|
    (find_withtag('all') - [@img]).each{|item| item.delete}
 | 
						|
  end
 | 
						|
 | 
						|
  def reset_region
 | 
						|
    width = @photo.width
 | 
						|
    height = @photo.height
 | 
						|
 | 
						|
    if width > @scr_region[2]
 | 
						|
      @scr_region[0] = -width
 | 
						|
      @scr_region[2] = width
 | 
						|
    end
 | 
						|
 | 
						|
    if height > @scr_region[3]
 | 
						|
      @scr_region[1] = -height
 | 
						|
      @scr_region[3] = height
 | 
						|
    end
 | 
						|
 | 
						|
    self.scrollregion(@scr_region)
 | 
						|
    self.xview_moveto(0.25)
 | 
						|
    self.yview_moveto(0.25)
 | 
						|
  end
 | 
						|
 | 
						|
  def get_texts
 | 
						|
    ret = []
 | 
						|
    find_withtag('all').each{|item|
 | 
						|
      if item.kind_of?(TkcText)
 | 
						|
        ret << item[:text]
 | 
						|
      end
 | 
						|
    }
 | 
						|
    ret
 | 
						|
  end
 | 
						|
end
 | 
						|
############################
 | 
						|
 | 
						|
# define methods for menu
 | 
						|
def open_file(canvas, fname)
 | 
						|
  if canvas.modified?
 | 
						|
    ret = Tk.messageBox(:icon=>'warning',:type=>'okcancel',:default=>'cancel',
 | 
						|
                        :message=>'Canvas may be modified. Realy erase? ')
 | 
						|
    return if ret == 'cancel'
 | 
						|
  end
 | 
						|
 | 
						|
  filetypes = [
 | 
						|
    ['GIF Files', '.gif'],
 | 
						|
    ['GIF Files', [], 'GIFF'],
 | 
						|
    ['PPM Files', '.ppm'],
 | 
						|
    ['PGM Files', '.pgm']
 | 
						|
  ]
 | 
						|
 | 
						|
  begin
 | 
						|
    if Tk::Img::package_version != ''
 | 
						|
      filetypes << ['JPEG Files', ['.jpg', '.jpeg']]
 | 
						|
      filetypes << ['PNG Files', '.png']
 | 
						|
      filetypes << ['PostScript Files', '.ps']
 | 
						|
      filetypes << ['PDF Files', '.pdf']
 | 
						|
      filetypes << ['Windows Bitmap Files', '.bmp']
 | 
						|
      filetypes << ['Windows Icon Files', '.ico']
 | 
						|
      filetypes << ['PCX Files', '.pcx']
 | 
						|
      filetypes << ['Pixmap Files', '.pixmap']
 | 
						|
      filetypes << ['SGI Files', '.sgi']
 | 
						|
      filetypes << ['Sun Raster Files', '.sun']
 | 
						|
      filetypes << ['TGA Files', '.tga']
 | 
						|
      filetypes << ['TIFF Files', '.tiff']
 | 
						|
      filetypes << ['XBM Files', '.xbm']
 | 
						|
      filetypes << ['XPM Files', '.xpm']
 | 
						|
    end
 | 
						|
  rescue
 | 
						|
  end
 | 
						|
 | 
						|
  filetypes << ['ALL Files', '*']
 | 
						|
 | 
						|
  fpath = Tk.getOpenFile(:filetypes=>filetypes)
 | 
						|
  return if fpath.empty?
 | 
						|
 | 
						|
  begin
 | 
						|
    canvas.load_photo(fpath)
 | 
						|
  rescue => e
 | 
						|
    Tk.messageBox(:icon=>'error', :type=>'ok',
 | 
						|
                  :message=>"Fail to read '#{fpath}'.\n#{e.message}")
 | 
						|
  end
 | 
						|
 | 
						|
  canvas.fig_erase
 | 
						|
  canvas.reset_region
 | 
						|
 | 
						|
  fname.value = fpath
 | 
						|
end
 | 
						|
 | 
						|
# --------------------------------
 | 
						|
def save_memo(canvas, fname)
 | 
						|
  initname = fname.value
 | 
						|
  if initname != '-'
 | 
						|
    initname = File.basename(initname, File.extname(initname))
 | 
						|
    fpath = Tk.getSaveFile(:filetypes=>[ ['Text Files', '.txt'],
 | 
						|
                                         ['ALL Files', '*'] ],
 | 
						|
                           :initialfile=>initname)
 | 
						|
  else
 | 
						|
    fpath = Tk.getSaveFile(:filetypes=>[ ['Text Files', '.txt'],
 | 
						|
                                         ['ALL Files', '*'] ])
 | 
						|
  end
 | 
						|
  return if fpath.empty?
 | 
						|
 | 
						|
  begin
 | 
						|
    fid = open(fpath, 'w')
 | 
						|
  rescue => e
 | 
						|
    Tk.messageBox(:icon=>'error', :type=>'ok',
 | 
						|
                  :message=>"Fail to open '#{fname.value}'.\n#{e.message}")
 | 
						|
  end
 | 
						|
 | 
						|
  begin
 | 
						|
    canvas.get_texts.each{|txt|
 | 
						|
      fid.print(txt, "\n")
 | 
						|
    }
 | 
						|
  ensure
 | 
						|
    fid.close
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
# --------------------------------
 | 
						|
def ps_print(canvas, fname)
 | 
						|
  initname = fname.value
 | 
						|
  if initname != '-'
 | 
						|
    initname = File.basename(initname, File.extname(initname))
 | 
						|
    fpath = Tk.getSaveFile(:filetypes=>[ ['Postscript Files', '.ps'],
 | 
						|
                                         ['ALL Files', '*'] ],
 | 
						|
                           :initialfile=>initname)
 | 
						|
  else
 | 
						|
    fpath = Tk.getSaveFile(:filetypes=>[ ['Postscript Files', '.ps'],
 | 
						|
                                         ['ALL Files', '*'] ])
 | 
						|
  end
 | 
						|
  return if fpath.empty?
 | 
						|
 | 
						|
  bbox = canvas.bbox('all')
 | 
						|
  canvas.postscript(:file=>fpath, :x=>bbox[0], :y=>bbox[1],
 | 
						|
                    :width=>bbox[2] - bbox[0], :height=>bbox[3] - bbox[1])
 | 
						|
end
 | 
						|
 | 
						|
# --------------------------------
 | 
						|
def quit(canvas)
 | 
						|
  ret = Tk.messageBox(:icon=>'warning', :type=>'okcancel',
 | 
						|
                      :default=>'cancel',
 | 
						|
                      :message=>'Realy quit? ')
 | 
						|
  exit if ret == 'ok'
 | 
						|
end
 | 
						|
 | 
						|
# --------------------------------
 | 
						|
# setup root
 | 
						|
root = TkRoot.new(:title=>'Fig Memo')
 | 
						|
 | 
						|
# create canvas frame
 | 
						|
canvas = PhotoCanvas.new(root).pack(:fill=>:both, :expand=>true)
 | 
						|
usage_frame = TkFrame.new(root, :relief=>:ridge, :borderwidth=>2)
 | 
						|
hide_btn = TkButton.new(usage_frame, :text=>'hide usage',
 | 
						|
                        :font=>{:size=>8}, :pady=>1,
 | 
						|
                        :command=>proc{usage_frame.unpack})
 | 
						|
hide_btn.pack(:anchor=>'e', :padx=>5)
 | 
						|
usage = TkLabel.new(usage_frame, :text=>PhotoCanvas::USAGE,
 | 
						|
                    :font=>'Helvetica 8', :justify=>:left).pack
 | 
						|
 | 
						|
show_usage = proc{
 | 
						|
  usage_frame.pack(:before=>canvas, :fill=>:x, :expand=>true)
 | 
						|
}
 | 
						|
 | 
						|
fname = TkVariable.new('-')
 | 
						|
f = TkFrame.new(root, :relief=>:sunken, :borderwidth=>1).pack(:fill=>:x)
 | 
						|
label = TkLabel.new(f, :textvariable=>fname,
 | 
						|
                    :font=>{:size=>-12, :weight=>:bold},
 | 
						|
                    :anchor=>'w').pack(:side=>:left, :fill=>:x, :padx=>10)
 | 
						|
 | 
						|
# create menu
 | 
						|
mspec = [
 | 
						|
  [ ['File', 0],
 | 
						|
    ['Show Usage',      proc{show_usage.call}, 5],
 | 
						|
    '---',
 | 
						|
    ['Open Image File', proc{open_file(canvas, fname)}, 0],
 | 
						|
    ['Save Memo Texts', proc{save_memo(canvas, fname)}, 0],
 | 
						|
    '---',
 | 
						|
    ['Save Postscript', proc{ps_print(canvas, fname)}, 5],
 | 
						|
    '---',
 | 
						|
    ['Quit', proc{quit(canvas)}, 0]
 | 
						|
  ]
 | 
						|
]
 | 
						|
root.add_menubar(mspec)
 | 
						|
 | 
						|
# manage wm_protocol
 | 
						|
root.protocol(:WM_DELETE_WINDOW){quit(canvas)}
 | 
						|
 | 
						|
# show usage
 | 
						|
show_usage.call
 | 
						|
 | 
						|
# --------------------------------
 | 
						|
# start eventloop
 | 
						|
Tk.mainloop
 |