Programming and Scripting :: XPM Icon Viewer



Requires murgaLua 0.5.5

A rewrite of an icon viewer I made several months ago.
This version displays only 32x32 XPM images, like those used in dfm.
Added features are the ability to change/refresh the directory from within the gui and opening the selected file in the editor of your choice (default is Beaver).

Please note that occasionally you may run into an XPM file that will crash the application. What I have found so far is files that have leading spaces in the image data, such as the mozilla_composer3* icons in the iceicons-default-0.10.0.tar.gz package, will kill it.

Code Sample

-- XPM icon viewer
-- mikshaw 2007

--dir=os.getenv("HOME").."/.dfm/" -- ending slash is vital
dir="./"
--text_editor="aterm -title Floaty -e vim"
text_editor="beaver"
xpm_size=32

function get_image_data(self)
 local old_data=display:image()
 if old_data then old_data:uncache() end
 local image_data=self:image()
 display:image(image_data:copy(128,128)) --show it bigger!
 current_file=dir..self:tooltip() -- for editor
 display:label("\n"..self:tooltip().."\n"..lfs.attributes(current_file).size.." bytes")
 display:redraw()
end

function menu_cb()
 if i_menu:text()=="&Edit" and current_file then os.execute(text_editor.." "..current_file.." &")
 elseif i_menu:text()=="&Directory" then chdir()
 elseif i_menu:text()=="&Refresh" then refresh()
 elseif i_menu:text()=="&Quit" then os.exit(0)
 end
end

function build_list()
 scroll=fltk:Fl_Scroll(5,bh,sw,sh)
 w:add(scroll)
 pack=fltk:Fl_Pack(5,bh,bw,sh)
 pack:spacing(2)
 scroll:add(pack)
 -- get all xpm files
 for file in lfs.dir(dir) do
   if string.find(file,"%.xpm$") then
     table.insert(images,file)
   end
 end
 table.sort(images)
 -- make buttons for "xpm_size" images only
 xpm_count=0
 filesize=0
 for i=1,table.getn(images) do
   icon[i]=fltk:Fl_XPM_Image(dir..images[i])
   if icon[i]:w()==xpm_size and icon[i]:h()==xpm_size then
     xpm_count=1+xpm_count
     filesize=filesize+lfs.attributes(dir..images[i]).size
     butt[xpm_count]=fltk:Fl_Button(0,0,bw,bw)
     butt[xpm_count]:box(15) --shadow
     butt[xpm_count]:image(icon[i])
     butt[xpm_count]:tooltip(images[i])
     butt[xpm_count]:callback(get_image_data)
     pack:add(butt[xpm_count])
   else icon[i]:uncache()
   end
 end
 if butt[1] then display:show(); butt[1]:do_callback() end -- show first image
 display2:label(xpm_count.." files | "..math.ceil(filesize/1024).." kb")
end

function refresh()
 local old_image=display:image()
 old_image:uncache(); display:hide()
 -- clear all tables and remove buttons
 for i=1,table.getn(images) do table.remove(images) end
 for i=1,table.getn(icon) do icon[i]:uncache() end
 for i=1,table.getn(icon) do table.remove(icon) end
 for i=1,table.getn(butt) do table.remove(butt) end
 w:remove(scroll)
 Fl:delete_widget(scroll)
 scroll=nil
 build_list()
end

function chdir()
 local dirname=fltk.fl_dir_chooser("pick a dir...",dir)
 if dirname then
   if string.find(dirname,"/$") then dir=dirname
   else dir=dirname.."/" end
   refresh()
 end
end

-- candy
Fl:set_boxtype(fltk.FL_UP_BOX,fltk.FL_THIN_UP_BOX)
Fl:set_boxtype(fltk.FL_DOWN_BOX,fltk.FL_THIN_DOWN_BOX)
fltk.fl_define_FL_SHADOW_BOX()
Fl:set_color(fltk.FL_DARK3,150,150,150) -- shadow
Fl:set_color(fltk.FL_GRAY0,128,128,128) -- frame
--Fl_Tooltip:disable()

-- some sizes and positions are determined by xpm_size
bw=xpm_size+20 -- button width
bh=30; sw=bw+20; sh=250 -- menu, scroll size
ww=sw+sh+10
wh=bh+sh+10
w=fltk:Fl_Window(ww,wh,"XPM Icons")

menu_items={"&Edit","&Directory","&Refresh","&Quit"}
i_menu=fltk:Fl_Menu_Button(0,0,sw+5,bh,"&Menu")
i_menu:align(20)
i_menu:callback(menu_cb)
i_menu:selection_color(fltk.FL_WHITE)
for i,v in ipairs(menu_items) do i_menu:add(v) end

display=fltk:Fl_Box(sw+5,0,sh,sh) -- big image
display2=fltk:Fl_Box(sw+5,sh,sh,bh) -- dir info

xpm_count=0 -- set global variables
filesize=0
images={}; icon={}; butt={}

build_list()
w:show()
Fl:run()

Minor changes and bug fix:

Fixed refresh crash if initial directory is empty
Ending slash on dir variable is not required
Changed start dir to /usr/share/dfm/icons (note: it's read-only)
Changed default editor to xpaint
Variable name "editor" instead of "text_editor"
Environment variable "XPM_EDITOR" overrides script variable "editor"

Code Sample

-- XPM icon viewer
-- mikshaw 2007

dir="/usr/share/dfm/icons"
editor="xpaint"
xpm_size=32

edvar=os.getenv("XPM_EDITOR")
if edvar then editor=edvar end

function get_image_data(self)
 local old_data=display:image()
 if old_data then old_data:uncache() end
 local image_data=self:image()
 display:image(image_data:copy(128,128)) --show it bigger!
 current_file=dir..self:tooltip() -- for editor
 display:label("\n"..self:tooltip().."\n"..lfs.attributes(current_file).size.." bytes")
 display:redraw()
end

function menu_cb()
 if i_menu:text()=="&Edit" and current_file then os.execute(editor.." "..current_file.." &")
 elseif
i_menu:text()=="&Directory" then chdir()
 elseif i_menu:text()=="&Refresh" then refresh()
 elseif i_menu:text()=="&Quit" then os.exit(0)
 end
end

function build_list()
 if not string.find(dir,"/$") then dir=dir.."/" end
 scroll=fltk:Fl_Scroll(5,bh,sw,sh)
 w:add(scroll)
 pack=fltk:Fl_Pack(5,bh,bw,sh)
 pack:spacing(2)
 scroll:add(pack)
 -- get all xpm files
 for file in lfs.dir(dir) do
   if string.find(file,"%.xpm$") then
     table.insert(images,file)
   end
 end
 table.sort(images)
 -- make buttons for "xpm_size" images only
 xpm_count=0
 filesize=0
 for i=1,table.getn(images) do
   icon[i]=fltk:Fl_XPM_Image(dir..images[i])
   if icon[i]:w()==xpm_size and icon[i]:h()==xpm_size then
     xpm_count=1+xpm_count
     filesize=filesize+lfs.attributes(dir..images[i]).size
     butt[xpm_count]=fltk:Fl_Button(0,0,bw,bw)
     butt[xpm_count]:box(15) --shadow
     butt[xpm_count]:image(icon[i])
     butt[xpm_count]:tooltip(images[i])
     butt[xpm_count]:callback(get_image_data)
     pack:add(butt[xpm_count])
   else icon[i]:uncache()
   end
 end
 if butt[1] then display:show(); butt[1]:do_callback() end -- show first image
 display2:label(xpm_count.." files | "..math.ceil(filesize/1024).." kb")
end

function refresh()
 local old_image=display:image()
 if old_image then old_image:uncache(); display:hide() end
 -- clear all tables and remove buttons
 for i=1,table.getn(images) do table.remove(images) end
 for i=1,table.getn(icon) do icon[i]:uncache() end
 for i=1,table.getn(icon) do table.remove(icon) end
 for i=1,table.getn(butt) do table.remove(butt) end
 w:remove(scroll)
 Fl:delete_widget(scroll)
 scroll=nil
 build_list()
end

function chdir()
 local dirname=fltk.fl_dir_chooser("pick a dir...",dir)
 if dirname then
   dir=dirname
   refresh()
 end
end

-- candy
Fl:set_boxtype(fltk.FL_UP_BOX,fltk.FL_THIN_UP_BOX)
Fl:set_boxtype(fltk.FL_DOWN_BOX,fltk.FL_THIN_DOWN_BOX)
fltk.fl_define_FL_SHADOW_BOX()
Fl:set_color(fltk.FL_DARK3,150,150,150) -- shadow
Fl:set_color(fltk.FL_GRAY0,128,128,128) -- frame
--Fl_Tooltip:disable()

-- some sizes and positions are determined by xpm_size
bw=xpm_size+20 -- button width
bh=30; sw=bw+20; sh=250 -- menu, scroll size
ww=sw+sh+10
wh=bh+sh+10
w=fltk:Fl_Window(ww,wh,"XPM Icons")

menu_items={"&Edit","&Directory","&Refresh","&Quit"}
i_menu=fltk:Fl_Menu_Button(0,0,sw+5,bh,"&Menu")
i_menu:align(20)
i_menu:callback(menu_cb)
i_menu:selection_color(fltk.FL_WHITE)
for i,v in ipairs(menu_items) do i_menu:add(v) end

display=fltk:Fl_Box(sw+5,0,sh,sh) -- big image
display2=fltk:Fl_Box(sw+5,sh,sh,bh) -- dir info

xpm_count=0 -- set global variables
filesize=0
images={}; icon={}; butt={}

build_list()
w:show()
Fl:run()

I see you've already had some changes that I was attempting on your first revision.  In any case it looks like the xpm loader code is causing the segfaulting... wonder if that'll get a bugfix?

The following is part of a semi-workaround, although I know it's not ideal.
Code Sample
function checkxpm(fname)
 local file = io.open(fname, "r")
 local line = file:read("*l")
 while (line) do
   local start = string.find(line, "^ ")
   if start == 1 then
      file:close()
     return false
   end
   line = file:read("*l")
 end
 file:close()
 return true
end

function makenewxpm(fname)
 local fname2 = fname .. "-tmpiconviewer"
 local fin = io.open(fname, "r")
 local fout = io.open(fname2, "w")

 local line = fin:read("*l")
 while (line) do
   line = string.gsub(line, "^ +" , "")
   fout:write(line .. "\n")
   line = fin:read("*l")
 end
 fin:close()
 fout:close()
 return fname2
end


So basically you can use checkxpm first, and if it returns false, call makenewxpm.  Of course, to make this useable, you'd have to have it so that it shows the temp image, yet edits the original (and refresh the temp image based on the temp image).  I guess using another array would be the simplest to add...

Code Sample
<snip>
...
for i=1,table.getn(images) do
  -- new stuff
  if checkxpm(dir .. "/" .. images[i]) == false then
    images2[i] = makenewxpm(dir .. "/" .. images[i])
  else
    images2[i] = images[i]
  end
  --icon[i]=fltk:Fl_XPM_Image(dir..images[i])
  icon[i]=fltk:Fl_XPM_Image(dir..images2[i])
  -- end of new stuff
  if icon[i]:w()==xpm_size and icon[i]:h()==xpm_size then
...
<snip>


And then add "images2={};" and "for i=1,table.getn(images2) do table.remove(images2) end".
Probably would be a good idea to make a better temporary name, and delete them after.

Looking good. Would like this script for v4.1.
Maybe we have some artistic types who can create some new icons or suggest some improved ones.
With this tool it will certainly be alot easier to edit/create xpm icons.

Quote
it looks like the xpm loader code is causing the segfaulting... wonder if that'll get a bugfix?
I suppose we could ask Murga if it is a murgaLua issue or if it's an FLTK bug. It might be as simple as rebuilding FLTK using a different xpm version.

I had no idea how I would address the issue through script, and it looks like your solution is definitely useable in some situations. What happens, though, if the icon is not writeable by the user? I think I would prefer to check the file as you suggested, but then just not load it if it's going to cause a crash.

Also an issue with checking every file is that it will probably slow things down considerably on a large number of files. The current script already takes a second or two on a 1.8ghz machine to read a directory with a couple hundred files.

Next Page...
original here.