XPM Icon Viewer


Forum: Programming and Scripting
Topic: XPM Icon Viewer
started by: mikshaw

Posted by mikshaw on Oct. 31 2007,17:49
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()

Posted by mikshaw on Oct. 31 2007,19:49
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()

Posted by ^thehatsrule^ on Oct. 31 2007,20:54
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.

Posted by roberts on Oct. 31 2007,23:16
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.

Posted by mikshaw on Nov. 01 2007,02:51
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.

Posted by ^thehatsrule^ on Nov. 01 2007,05:06
Quote
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.
At first glance, it seems to be coming from Fltk (just looking at the func. call), but I haven't looked at the actual source.  But your suggestion might be valid.

Quote
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.
The script addons I posted does the following:
- check if the xpm has leading whitespace on each line
- if the above is true: make a valid copy of the xpm and display that, but editing will still point to the original file.  It's only used for the display.

If you just wanted it to ignore ones that would crash, you could just wrap the code in the for loop around a if-conditional on checkxpm.

Quote
lso an issue with checking every file is that it will probably slow things down considerably on a large number of files.
I didn't notice with the few files I had :P Mind running a test for the difference in time?

Posted by mikshaw on Nov. 01 2007,13:44
Quote
make a valid copy of the xpm and display that, but editing will still point to the original file
Oh. I didn't study your code closely enough. So editing is still done on the original. That's much better.

Here's what I did as a test last night. I used your idea of checking each file for leading spaces, condensing it as much as I could, and simply didn't display the image if space is found:
Code Sample
function find_stupid_space(file)
 local data=io.open(file)
 if data then
   for line in data:lines() do
     if string.find(line,"^%s") then return 1 end
   end
 data:close()
 end
end
[...]
--Just before building the buttons:
if not find_stupid_space(dir..images[i]) and icon[i]:w()==xpm_size and icon[i]:h()==xpm_size then

I still don't really like the idea of parsing every line of every file, though. In a directory with 714 xpm files, 280 of them 32x32, and 2 of those corrupted, the app loads in about 1 second without the check. With the check, load time is about 3-4 seconds, regardless of whether or not the loop is broken as soon as a space is found.

Posted by mikshaw on Nov. 01 2007,15:21
I think I may have found a partial solution.

The count() method apparently doesn't count these spaced lines. Since count *should* be 32 + the number of colors + 1 for a 32x32 xpm, a file with a count less than this number would be corrupted.
The problem is finding the number of colors. It shouldn't be too difficult, but I would like to make the parsing as terse as possible and haven't even attempted it yet. At this time I'm checking only xpm_size+1, which would fail if the number of colors was greater than 32 but the spaces didn't occur until later into the image data (was that clear at all?)

So I removed the text parsing entirely, and changed the final check to this:
Code Sample
if icon[i]:count()>=xpm_size+1 and icon[i]:w()==xpm_size and icon[i]:h()==xpm_size then

Later I will look into grabbing the number of colors

EDIT: It's a lot harder than I thought. So far I haven't succeeded in doing anything better than simply searching for leading spaces, since finding the number of colors still requires loading at least part of every file. This loading is what is causing the slowdown.  That most recent idea is not reliable unless I know how many lines of data (not including any comments) are in the file. A test showed that adding just one space near the end of the file will cause it to pass the test and segfault.

The best I've found so far is similar to what I had last night, except the check is done after 32x32 images have been separated instead of on all xpm files (only slightly faster). I have a feeling that unless there is a way to run a check on the loaded image data instead of on the file, I'll need to accept either the delay or the occasional segfaults.

I did add a few debug lines that make it easier to find corrupted files, though:
Code Sample
if arg[1]=="debug" then
 print(images[i])
 butt[xpm_count]:image(icon[i]:copy())
else
 butt[xpm_count]:image(icon[i])
end

The copy method immediately crashes with a corrupt file, and the last filename printed is the bad one.

Posted by mikshaw on Nov. 01 2007,19:54
Here's a compromise that I like best so far. I can't see there being a lot of people with more than a few hundred 32x32 xpm files in a single directory, so I've decided the delay of a couple of seconds isn't a huge problem. The compromise is that the script no longer just ignores the "bad" files as I had originally planned, but also does not pretend there's nothing wrong with the files as ^thehatsrule^ had coded. The script now contains a "broken image" icon that appears in place of the original icon, to let the user know something's not right with it, and the file itself can still be opened with the "edit" button.

Code Sample
#!/bin/murgaLua

-- XPM icon viewer
-- mikshaw 2007

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

broken_xpm_data=[[
/* XPM */
static char *broken_image[] = {
/* columns rows colors chars-per-pixel */
"14 16 16 1",
"  c black",
". c #800000",
"X c #008000",
"o c #808000",
"O c navy",
"+ c #800080",
"@ c #008080",
"# c #C0C0C0",
"$ c #808080",
"% c red",
"& c green",
"* c None",
"= c blue",
"- c magenta",
"; c cyan",
": c gray100",
/* pixels */
"          $***",
" :::::::::$$**",
" :########$:$*",
" :###XX###$::$",
" :##X&X ##    ",
" :##XXX ####: ",
" :###  #####: ",
" :######=== :*",
" :#%####=;= :*",
" :#-%###==O***",
" :#--%##  **: ",
" :#---%#***#: ",
" :#   *****#: ",
" :##*****###: ",
" ::*****::::: ",
"  ******      "
};
]]
xpm_tempfile=os.tmpname()
xpm_write=io.open(xpm_tempfile,"w")
if xpm_write then
 xpm_write:write(broken_xpm_data)
 xpm_write:close()
 broken_xpm=fltk:Fl_XPM_Image(xpm_tempfile)
 os.remove(xpm_tempfile)
end

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()
 if image_data then
   display:image(image_data:copy(128,128)) --show it bigger!
 else
   display:image(broken_xpm)
 end
 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 find_stupid_space(file)
 local data=io.open(file)
 if data then
   for line in data:lines() do
     if string.find(line,"^%s") then return 1 end
   end
 data:close()
 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
     if not find_stupid_space(dir..images[i]) then
       butt[xpm_count]:image(icon[i])
     else butt[xpm_count]:label("bad\nimage")
     end
       butt[xpm_count]:callback(get_image_data)
       butt[xpm_count]:tooltip(images[i])
       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()

Posted by ^thehatsrule^ on Nov. 01 2007,20:24
That compromise is fine by me, although I am still hoping for a fix on the binary engine side of things.  I like the use of the broken image though.

Some comments (on latest):

Quote
function find_stupid_space(file)
local data=io.open(file)
...
    if string.find(line,"^%s") then return 1 end
...
I'm not sure how it handles files, but shouldn't you explicity call close on data here as well? (Although I'm guessing it could be automatically taken care of when the local data var is destroyed)

- would adding an extra button to optionally "fix" a broken image be a good idea?
- does the xpm format actually allow spaces in front?  If it does, then technically it's not a "broken image"...

Posted by mikshaw on Nov. 01 2007,22:03
Quote
shouldn't you explicity call close on data here as well?
There is a file:close() after the loop. Does return cause the function to end?  I've never fully understood how return works, particularly when it is not the last command in a function.  I had a break in there at one time, and then the return, but it required four or five extra lines of code and showed no noticeable improvement.

Quote
would adding an extra button to optionally "fix" a broken image be a good idea?
I think it's a good idea.

Quote
does the xpm format actually allow spaces in front?  If it does, then technically it's not a "broken image"
I have no idea what the technical specs of the file format are, but apparently it is allowed in many applications that read XPM. That could simply be application developers making software that compensates for a common error, though, just as we are doing here.

I plan to add a couple more checks eventually. The most important will be to check whether or not a *.xpm file is actually an XPM. If a non-XPM file has a *.xpm filename, it will crash the app.

Posted by ^thehatsrule^ on Nov. 02 2007,00:24
Quote
There is a file:close() after the loop. Does return cause the function to end?  I've never fully understood how return works, particularly when it is not the last command in a function.  I had a break in there at one time, and then the return, but it required four or five extra lines of code and showed no noticeable improvement.
The return statement can be like a GOTO command or break command, it basically exits or 'returns' from the function right there with a value (if specified) back to the point where the function was called.  So the close statement would not be read at all.  Although having multiple return statements usually isn't good semantics for programming, I find that scripting usually has looser guidelines (this is just my opinion).  Also if there is any return value at all, it is cleaner to return something of the same type to explicitly tell what the author intended, and can reduce potential error.  In psuedo-code you can do something like: VALID = true; for X in Y while VALID do; VALID = check_validity(X); done; return VALID

Quote
I plan to add a couple more checks eventually. The most important will be to check whether or not a *.xpm file is actually an XPM. If a non-XPM file has a *.xpm filename, it will crash the app.
I guess the most basic check would just to look for the /* XPM */ header.  Did a quick search and found 'cxpm' which might help out on the format details (see < http://koala.ilog.fr/lehors/xpm.html > )

Posted by mikshaw on Nov. 02 2007,01:59
I have a lot to learn about programming in general.
Your suggestion is similar to what I had originally done using break to hopefully speed things up a little, but since it didn't seem to make any difference I eventually went with the smaller code. I'll look at it again later.

Quote
I guess the most basic check would just to look for the /* XPM */ header.  Did a quick search and found 'cxpm'
that's amusing
I had both of those ideas in mind as well =o)
I haven't gotten to that yet, though.

The thing about cxpm is that it is not in DSL. Its buddy sxpm is in DSL, though, and I'm currently using that to do the fix. Simply opening and resaving the troublesome files with sxpm is enough to fix them.

Code Sample
#!/bin/murgaLua

-- XPM icon viewer
-- mikshaw 2007
-- thanks to ^thehatsrule^ for help and suggestions

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

broken_xpm_data=[[
/* XPM */
static char *broken_image[] = {
/* columns rows colors chars-per-pixel */
"14 16 16 1",
"  c black",
". c #800000",
"X c #008000",
"o c #808000",
"O c navy",
"+ c #800080",
"@ c #008080",
"# c #C0C0C0",
"$ c #808080",
"% c red",
"& c green",
"* c None",
"= c blue",
"- c magenta",
"; c cyan",
": c gray100",
/* pixels */
"          $***",
" :::::::::$$**",
" :########$:$*",
" :###XX###$::$",
" :##X&X ##    ",
" :##XXX ####: ",
" :###  #####: ",
" :######=== :*",
" :#%####=;= :*",
" :#-%###==O***",
" :#--%##  **: ",
" :#---%#***#: ",
" :#   *****#: ",
" :##*****###: ",
" ::*****::::: ",
"  ******      "
};
]]
xpm_tempfile=os.tmpname()
xpm_write=io.open(xpm_tempfile,"w")
if xpm_write then
 xpm_write:write(broken_xpm_data)
 xpm_write:close()
 broken_xpm=fltk:Fl_XPM_Image(xpm_tempfile)
 os.remove(xpm_tempfile)
end

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()
 if image_data then
   display:image(image_data:copy(128,128)) --show it bigger!
   broken=nil
   i_menu:mode(3,1) -- disable "fix" item
 else
   display:image(broken_xpm)
   broken=self:tooltip()
   i_menu:mode(3,0)
 end
 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()
 local v=i_menu:value()
 if v==0 and current_file then os.execute(editor.." "..current_file.." &")
 elseif v==1 then chdir()
 elseif v==2 then refresh()
 elseif v==3 then fix_xpm()
 elseif v==4 then os.exit(0)
 end
end

function find_stupid_space(file)
 local data=io.open(file)
 if data then
   for line in data:lines() do
     if string.find(line,"^%s") then return 1 end
   end
 data:close()
 end
end

function build_list()
 local active=1
 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
     if not find_stupid_space(dir..images[i]) then
       butt[xpm_count]:image(icon[i])
     else butt[xpm_count]:label("bad\nimage")
     end
       butt[xpm_count]:callback(get_image_data)
       butt[xpm_count]:tooltip(images[i])
       if broken==images[i] then active=xpm_count end -- for refreshing display
       pack:add(butt[xpm_count])
     else icon[i]:uncache()
   end
 end
 if butt[active] then
   -- show first image
   display:show()
   butt[active]:do_callback()
 end
 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

function fix_xpm()
 if broken then
   os.execute("cd "..dir.." && sxpm -nod "..broken.." -o "..broken)
   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
images={}; icon={}; butt={}
w=fltk:Fl_Window(ww,wh,"XPM Icons")

menu_items={"&Edit","&Directory","&Refresh","&Fix","&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

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

Posted by roberts on Nov. 02 2007,05:00
This is programmed very application centric. It fine for accessing everything from within the application. But for DSL v4.x it would be nice to be able to double click on an .xpm file or drag-n-drop an .xpm file and have this application process it.
Posted by ^thehatsrule^ on Nov. 02 2007,05:45
Looking good...

I'm not sure how it works so far with hundreds of xpm's, but if you don't display all of them at the same time, you can save startup time by loading/checking the xpm's for when you actually do display them.  Either incrementally (i.e. scrolling through) or via pages would be fine, but this comes at the expense of having them load at whatever intervals.

DND would be a nice to-have for 4.x.

Posted by mikshaw on Nov. 02 2007,06:00
I think something like that would probably start by removing most of what is in here, since much of the code is made to handle displaying the directory contents.

Unless you want to have it do what it does now but add stdin as a source for the initially-displayed image, I think  the tool you describe might be a very small and very fast script in comparison.  In fact, it might simply be a wrapper for existing DSL applications...sxpm for display (would remove the troubles of XPM files that murgaLua cannot read), sxpm/xpaint/beaver/vi/nano as various editor choices, and the fact that it would accept a file as a parameter means a file manager or drag-drop can be used instead of fl_dir_chooser.  An application like that could basically just be a menu.

Posted by mikshaw on Nov. 02 2007,15:40
Oh hey...this is easy now =o)
It's not complete, and the refresh is broken, but it only took about ten minutes to turn the browser into a smaller, faster drag-drop viewer.

I'll post later today after I do the refresh and run some tests.

Posted by roberts on Nov. 02 2007,18:39
I wasn't thinking of cutting your application way down. What I had in mind, was drag a folder to your app and it opens in that folder, drag an xpm to it and it opens the folder containing this xpm with that xpm displayed. And finally double click an xpm and it works the same as dnd of it. I would like DSLv4 to support both operating modes whenever possible. If you weren't going to do it, I was. But I would rather let you complete your project. Glad to hear that you are looking further into this and thus supporting DSL v4 DND operating mode. I will wait to see what you have. This will be a nice addition to DSL.
Posted by mikshaw on Nov. 02 2007,21:17
I see where you're going. Well, I had already started a different script using basically just the image display from the old one as a base. It currently doesn't support directories at all, and in fact will give a warning message if you drag a folder onto it. It will allow you to drag an XPM file either to the app icon or onto the interface itself (that bit probably needs a decrease in the loop time to detect quick drags), or to browse for an XPM file. The double-click I assume would be controlled by the file manager.

I'll probably look into mashing the two scripts together in the future to see what I can make of them. I also will probably remove sxpm from the edit menu, since it's not an interactive app, and maybe use it elsewhere.

This is my first attempt at any sort of drag and drop, so it will need much testing and debugging.

Code Sample
#!/bin/murgaLua

-- XPM icon viewer
-- mikshaw 2007

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

broken_xpm_data=[[
/* XPM */
static char *broken_image[] = {
/* columns rows colors chars-per-pixel */
"14 16 16 1",
"  c black",
". c #800000",
"X c #008000",
"o c #808000",
"O c navy",
"+ c #800080",
"@ c #008080",
"# c #C0C0C0",
"$ c #808080",
"% c red",
"& c green",
"* c None",
"= c blue",
"- c magenta",
"; c cyan",
": c gray100",
/* pixels */
"          $***",
" :::::::::$$**",
" :########$:$*",
" :###XX###$::$",
" :##X&X ##    ",
" :##XXX ####: ",
" :###  #####: ",
" :######=== :*",
" :#%####=;= :*",
" :#-%###==O***",
" :#--%##  **: ",
" :#---%#***#: ",
" :#   *****#: ",
" :##*****###: ",
" ::*****::::: ",
"  ******      "
};
]]
xpm_tempfile=os.tmpname()
xpm_write=io.open(xpm_tempfile,"w")
if xpm_write then
 xpm_write:write(broken_xpm_data)
 xpm_write:close()
 broken_xpm=fltk:Fl_XPM_Image(xpm_tempfile)
 os.remove(xpm_tempfile)
end

myname=fltk.fl_filename_name(arg[0])..": "
edvar=os.getenv("XPM_EDITOR")
if edvar then editor=edvar end

function find_stupid_space(infile)
 local data=io.open(infile)
 if data then
   local found=0
   for line in data:lines() do
     if string.find(line,"^%s") then found=1; break end
   end
   data:close()
   return found
 end
end

function get_image_data(image)
 local old_data=display:image()
 if old_data then old_data:uncache() end
 local image_data=fltk:Fl_XPM_Image(image)
 if image_data and find_stupid_space(image)==0 then
   display:image(image_data:copy(128,128)) --show it bigger!
 else
   display:image(broken_xpm)
 end
 current_file=image -- full path
 filename=fltk.fl_filename_name(image) -- filename only
 display:label(filename.."\n"..image_data:w().."x"..image_data:h().." | "..lfs.attributes(current_file).size.." bytes")
 display:redraw()
end

function menu_cb()
 local v=i_menu:value()
 if v==1 then menu_open_newfile()
 elseif v==2 and current_file then get_image_data(current_file)
 elseif v==3 then os.exit(0)
 elseif v>=4 and current_file then os.execute(i_menu:text().." "..current_file.." &")
 end
end

function menu_open_newfile()
 local filename=fltk.fl_file_chooser("XPM file...","*.xpm",dir)
 if filename then
   get_image_data(filename)
 end
end

function dnd_check()
 if Fl:event() == fltk.FL_DND_RELEASE then
   Fl:paste(w)
   Fl:wait()
   local dropped_file=Fl:event_text()
   if dropped_file then
     cut_stupid_space=string.gsub(dropped_file,"^%s*","")
     local new_file=string.gsub(cut_stupid_space,"^~",os.getenv("HOME"))
     open_file(new_file)
   end
 end
Fl:wait()
 timer:doWait(0.05)
end

function open_file(dfile)
if lfs.attributes(dfile).mode == "file" then
 local filename=io.open(dfile)
 if filename then
   local data=filename:read("*l")
   if string.find(data,"/%*%s*XPM%s*%*/") then
     filename:close()
     get_image_data(dfile)
   end
 else print(myname.."can't open "..dfile)
 end
else print(myname..dfile..": not a regular file")
end
end

ww=200; wh=200; mh=30
images={}; icon={}; butt={}
w=fltk:Fl_Window(ww,wh,"XPM Viewer")

file_items={"&Open","&Reload","&Quit"}
edit_items={"xpaint","sxpm","beaver","aterm -e vi"}
i_menu=fltk:Fl_Menu_Bar(0,0,ww,mh)
i_menu:callback(menu_cb)
i_menu:selection_color(fltk.FL_WHITE)
for i,v in ipairs(file_items) do i_menu:add("&File/"..v) end
for i,v in ipairs(edit_items) do i_menu:add("&Edit/"..v) end

timer=murgaLua.createFltkTimer()
display=fltk:Fl_Box(0,mh,ww,ww-mh) -- big image

if arg[1] then open_file(arg[1]) end
timer:callback(dnd_check)
timer:do_callback()
w:show()
Fl:run()

Posted by roberts on Nov. 02 2007,21:53
I wasn't talking about murgaLua drag-n-drop but dfm drag-n-drop as is easily implemented by processing the command line arg(s) no matter what programming language is used. You seem to have both. Not sure how the Lua DND is supposed to work. It didn't for me.

But dragging an .xpm onto the Lua logo icon of your apps works as expected. Then it is a simple matter to associate your program name with *.xpm in .dfmext and you have the double click support.

Next you need to make or find a nifty icon for your app. We don't want to leave it a boring Lua logo.

Posted by mikshaw on Nov. 03 2007,01:00
Quote
I wasn't talking about murgaLua drag-n-drop
Well, that's good, because cross-application drag and drop in murgaLua is apparently a pain in the butt. I am pleased that I was able to do something with it, though, so the beta of the murgaLua widgets demo app will probably have some basic DND included. Personally I'm not comfortable with the way it works for me at this time...it seems to be inconsistent, and seems to require a constant loop simply to check for FL_DND_RELEASE.

Quote
But dragging an .xpm onto the Lua logo icon of your apps works as expected.
That was the easy part alluded to in the previous post. It's just checking arg[1], as you said, and using that instead of a selected file. I could see it becoming more complicated if the application supported other parameters, but for what I do I've found that either manually editing the script or using environment variables is often a simpler approach.

I'll probably go with the previous script, adding the parameter parsing. With the help of lfs we can check for a directory or regular file and determine whether to display the first icon in the directory or a particular icon as a result.

Quote
Next you need to make or find a nifty icon for your app. We don't want to leave it a boring Lua logo.
I haven't even thought about that yet, since the window manager I use most often doesn't show icons in its bar.

Quote
Not sure how the Lua DND is supposed to work. It didn't for me.
Do you mean that this particular app didn't accept files dragged onto the interface, or that you haven't gotten app-to-app drag and drop working in a murgaLua script? If it's the former, I don't know what to say. I've tested only in one window manager so far. If it's the latter, I'm not surprised. The FLTK docs are terrible with this subject. I spent most of the afternoon poking and guessing before finally something just happened to spit out a useable string.

Posted by roberts on Nov. 03 2007,04:54
Quote
I'll probably go with the previous script, adding the parameter parsing. With the help of lfs we can check for a directory or regular file and determine whether to display the first icon in the directory or a particular icon as a result.

I agree a better fit.
Quote
Do you mean that this particular app didn't accept files dragged onto the interface, or that you haven't gotten app-to-app drag and drop working in a murgaLua script? If it's the former, I don't know what to say. I've tested only in one window manager so far. If it's the latter, I'm not surprised.

murgaLua DND seems to be very dependent on the window manager in use. I get some to work others do not. I would tend to stay away from using this feature until better supported. Really for this app I did like your prior version better. Just adding the process of the arg(s) which provides the dnd via dfm would make it great.

Don't forget to provide me a nice icon to showcase your efforts!

Posted by mikshaw on Nov. 03 2007,15:53
Quote
Just adding the process of the arg(s) which provides the dnd via dfm would make it great.
I started putting that in last night, but got too "drowsy" to finish it. So far it properly loads the XPMs in a directory when a directory iconis dragged onto the script icon, but I have more work to do on dragging a specific file icon. I want to have that paticular XPM displayed, but haven't done that yet.

Ideally I'd also like to have the displayed icon be visible in the scroll, but so far it seems that the only way to accomplish it is to traverse the whole icon table again, comparing each item to the displayed item. I'mnot sure how much that might slow things down when mixed with the "stupid_space" check.

Quote
Don't forget to provide me a nice icon
That's at the back of my mind, but of course I want to make sure the app is tight before working on an icon. I have several Icons I created back in my Windows days, so maybe one of those would be appropriate. I also need to make sure the broken_xpm image is ok to use, since it was taken from firefox, otherwise I'll either make my own or find one that is explicitly free to use.

I was looking through the AIcons package the other day, and discovered that there are other potential issues with a few XPMs that cause them not to display in murgaLua and are not "fixed" by sxpm. I didn't look too closely at those images, but I'm guessing they might contain blank lines that are kept by sxpm.

Posted by mikshaw on Nov. 03 2007,23:28
You can probably consider this a beta. I'm going to try to work out some kinks, but basically I think it does what you're hoping.

I started to work out a way to check if a file is truly an xpm file in the same loop that checks for spaces, but I haven't gotten far with that yet. As it stands at this time, a non-xpm file with an xpm filename extension will crash the program.

I tested it out on a directory of over 600 images, and like the original script it chokes on such a large number of files. I'm guessing it's a limitation of FLTK.

Code Sample
#!/bin/murgaLua

-- XPM icon viewer
-- mikshaw 2007
-- thanks to ^thehatsrule^ for help and suggestions

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

broken_xpm_data=[[
/* XPM */
static char *broken_image[] = {
/* columns rows colors chars-per-pixel */
"16 16 4 1",
"  c red",
". c #C4BF91",
"X c #FFF8D6",
"o c None",
/* pixels */
"ooo..........ooo",
"ooo. XXXXX X.ooo",
"ooo.  XXX  X.ooo",
"ooo.X  X  XX.ooo",
"ooo.XX   XXX.ooo",
"ooo.X  X  XX.ooo",
"ooo.  XXX  Xoooo",
"ooo. XXXXo ooooo",
"ooo.XXXXoooooooo",
"ooo.XoXooooooooo",
"ooo.oooooooo.ooo",
"oooooooooXoX.ooo",
"ooooooooXXXX.ooo",
"oooooXoXXXXX.ooo",
"ooooXXXXXXXX.ooo",
"ooo..........ooo"
};
]]
xpm_tempfile=os.tmpname()
xpm_write=io.open(xpm_tempfile,"w")
if xpm_write then
 xpm_write:write(broken_xpm_data)
 xpm_write:close()
 broken_xpm=fltk:Fl_XPM_Image(xpm_tempfile)
 os.remove(xpm_tempfile)
end

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()
 if image_data then
   display:image(image_data:copy(128,128)) --show it bigger!
   broken=nil
   i_menu:mode(3,1) -- disable "fix" item
 else
   display:image(broken_xpm)
   broken=self:tooltip()
   i_menu:mode(3,0)
 end
 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()
 local v=i_menu:value()
 if v==0 and current_file then os.execute(editor.." "..current_file.." &")
 elseif v==1 then chdir()
 elseif v==2 then refresh()
 elseif v==3 then fix_xpm()
 elseif v==4 then os.exit(0)
 end
end

function find_stupid_space(infile)
 local data=io.open(infile)
 if data then
   local is_xpm,found=0,0
   for line in data:lines() do
     --if string.find(line,"^%s") then is_xpm=1 end
     if string.find(line,"^%s") then found=1; break end
   end
   data:close()
   --if is_xpm~=1 or found==1 then return found end
   return found
 end
end

function build_list(dfile)
print(dfile)
 local active=1
 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(string.lower(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
     if find_stupid_space(dir..images[i])==0 then
       butt[xpm_count]:image(icon[i])
     else butt[xpm_count]:label("bad\nimage")
     end
       butt[xpm_count]:callback(get_image_data)
       butt[xpm_count]:tooltip(images[i])
       if broken==images[i] or dfile==images[i] then active=xpm_count end -- for refreshing display
       pack:add(butt[xpm_count])
     else icon[i]:uncache()
   end
 end
 if butt[active] then
   -- show first image
   display:show()
   butt[active]:do_callback()
 end
 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

function fix_xpm()
 if broken then
   os.execute("cd "..dir.." && sxpm -nod "..broken.." -o "..broken)
   refresh()
 end
end

function open_file(what)
if lfs.attributes(what).mode=="directory" then
 dir=what
else
 dragfile=fltk.fl_filename_name(what)
print(dragfile)
 dir=string.gsub(what,"(.*/).*$","%1")
end
build_list(dragfile)
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
images={}; icon={}; butt={}
w=fltk:Fl_Window(ww,wh,"XPM Icons")

menu_items={"&Edit","&Directory","&Refresh","&Fix","&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

if arg[1] then open_file(arg[1]) else build_list() end -- drag and drop
w:show()
Fl:run()

Posted by mikshaw on Nov. 03 2007,23:32
post removed
Posted by jpeters on Nov. 04 2007,03:48
I'm probably not clear on how this is supposed to work. Dragging a desktop icon across the icon for the editor on the desktop opens "XPM icons" to the /.dfmdesk directory, not the app's icon. Clicking on it directly open it to the dfm icon directory. Regarding the program icon, I get an error if I try to load it into the editor, and it can't be used as a dfm icon.
Posted by ^thehatsrule^ on Nov. 04 2007,04:02
It only works for directories or xpm's so far.

Note: the suggested xpm icon shows a corrupted image in the viewer for me?

Posted by mikshaw on Nov. 04 2007,04:12
If you mean a typical desktop icon that represents an application, no it won't work. It's meant to open *.xpm files only. For example if you drag a directory containing xpm files onto it, it will display the icons in that directory. If you drag an xpm file it will display the icons in the same directory as that xpm, with the dragged xpm being shown in the main window.

You bring an interesting point to light, though. I suppose it could be possible to determine the image associated with a dragged application icon, but to be honest I have a feeling that would be a very complicated task, and I don't plan to look into it.

Quote
the suggested xpm icon shows a corrupted image in the viewer for me
It must have something to do with the way it was saved. I had no trouble with the original file in DSL4. Maybe you have a blank line in there somewhere?

Posted by jpeters on Nov. 04 2007,05:05
Quote (mikshaw @ Nov. 03 2007,23:12)
Quote
the suggested xpm icon shows a corrupted image in the viewer for me
It must have something to do with the way it was saved. I had no trouble with the original file in DSL4. Maybe you have a blank line in there somewhere?

No, there's a problem with it (formating issue for dfm and xpaint editor).  I got this to work:
(icon.xpm)

< http://www.jpeters.net/apps/ >

Posted by mikshaw on Nov. 04 2007,06:37
That icon you linked is seriously messed up. I suppose you tried to modify it in a binary editor even though the copy/paste messed it up to begin with.

The problem is the lines were somehow broken in my post. Several of the ampersands were converted to "&amp" and line breaks were inserted. I replaced the ampersands with another character and reposted but it's still screwy. It looks like some of the whitespace was truncated.

I really hate the way most message boards can't properly do code tags =op

Posted by jpeters on Nov. 04 2007,06:58
Quote (mikshaw @ Nov. 04 2007,01:37)
That icon you linked is seriously messed up. I suppose you tried to modify it in a binary editor even though the copy/paste messed it up to begin with.

..It's working fine on my desktop.
Posted by mikshaw on Nov. 04 2007,07:15
Yes, it works, but it's horribly mangled. It is not what I created.

I'm trying this again with the amps and spaces removed.  It seems to work this time.
Code Sample
/* XPM */
static char * test_xpm[] = {
"32 32 16 1",
"0      c None",
".      c #BBBBA8A89292",
"X      c #ADAD8D8D6969",
"o      c #6B6B51513636",
"O      c #949484847575",
"+      c #6E6E65655B5B",
"@      c #363632322C2C",
"#      c #181818181616",
"$      c #868675756767",
"%      c #53534D4D4747",
"x      c #A3A38F8F7777",
"*      c #CFCFC4C4B7B7",
"=      c #83836B6B4D4D",
"-      c #FEFEFFFFFCFC",
";      c #FCFCDCDC9292",
":      c #C5C5AAAA7171",
"00.XXXXXXXXXXXXXXXXXXXXXXXXXXo00",
"00..XXXXXXXXXXXXXXXXXXXXXXXXoo00",
"00..oooooooooooooooooooooooooo00",
"00..oOOO++@######@@@@@@@++$.oo00",
"00..oO$+@@######@+OOOOO$@@%.oo00",
"00..o+@@#####@%Ox.......O@@.oo00",
"00..o@#####@%$x.....*****O%.oo00",
"00..o#####%$O..........***..oo00",
"00..o@##@%O.....xx=XX=x..**.oo00",
"00..o@#%+Ox.xxO$=%@@@@o=x...oo00",
"00..o@%+Oxxx$+@@@#####@#=Xx.oo00",
"00..o%+OOOO+@@@#########%@X.oo00",
"00..o$OOOO+@@@####O-*#####$.oo00",
"00..oOOxx+@@@#####O*-*#@##+.oo00",
"00..ox.x$@%O%@###*#O*-@O%#@.oo00",
"00..oxO$@%.*+@##*-*###%-.@#.oo00",
"00..oOO@@$*-+@###*####.-*$@.oo00",
"00..oxO%%$*-.%@######%.-*O@.oo00",
"00..o.$@@%.*-.@@@##@@..*.%@.oo00",
"00..o.x%++%.**.%%%%%..*O%##.oo00",
"00..o..xx$%%x.........+@###.oo00",
"0#;;o...;:$+;;;;;++++%@###@.oo00",
"0#;;;..;;:x=;;;;;;;@@@###@%;;;00",
"0##;;;;;;xxo;;XX:;;;%%%;;%$;;;00",
"00#@;;;;o.x@;;###X;;$$;;;O#;;;00",
"00##:;;=...#;;o#o:;;xx;;;;o;;;:0",
"00.%;;;;...#;;;;;;;..=;;;;X;;;;0",
"00.;;:;;;..#;;;;;:@..X;:;;;;;;;0",
"00;;;#o;;..#;;:o##...:;=X;;;o;;:",
"0;;;o##:;;X#;;X##XXX#;;@@;;:#:;;",
"0=;=oo##;;o#;;:ooooo@;;##:;X#=;;",
"00@ooo##@oo#@%oooooo@;;o###o##;;"};

Posted by jpeters on Nov. 04 2007,07:24
I see what you mean, the letters are clearer.
Posted by roberts on Nov. 04 2007,15:01
Looks real good mikshaw.
Adding it to v4.1

Posted by mikshaw on Nov. 04 2007,15:56
Quote
Adding it to v4.1
As I said, though, the script still needs a little work.  In addition to the "true XPM" file parsing, I noticed that I left a couple of debugging print commands in there that I didn't notice until I went back to running it from terminal.

I actually spent a large part of last night searching for a decent gui XPM editor. Most of the graphics apps on my slackware system, including xpaint, gimp, and pixmap, won't save xpm files. I assume they are trying to use a library I don't have (imlib, maybe?). Even the xpaint package made specifically for my slackware version didn't work, and the gui of pixmap is quite buggy. I was able to pipe the image through sxpm from xpaint, but xpaint isn't friendly to xpm files...the trasparency was replaced with black, the color codes are messed up, and there doesn't seem to be any way to load the palette from the xpm file.

Anyway, I finally found mtpaint this morning, which isn't specifically for xpm but works a LOT better than xpaint. The color symbols are all alphanumeric (sweet!), it automatically loads the file's color palette, and actually saves the file in my slackware (directly written from mtpaint, I assume)
< http://mtpaint.sourceforge.net/ >

I'm gonna see if it works as well in DSL.

EDIT: One distinct annoyance with mtpaint is that it apparently won't open XPM files that contain more than 256 colors. I can't imagine what use a high-color XPM would be (unless it's also high resolution, which is equally as odd), but there are a surprisingly large number of them.

Posted by mikshaw on Nov. 04 2007,17:26
I think this works. It should ignore *.xpm and *.XPM files that are not actually XPMs

Code Sample
#!/bin/murgaLua

-- XPM icon viewer
-- mikshaw 2007
-- thanks to ^thehatsrule^ and jpeters for help and suggestions

-- start directory
dir="/usr/share/dfm/icons"
-- icon editor (text editors and image editors both work)
editor="xpaint"
-- maximum icon size
xpm_size=48

broken_xpm_data=[[
/* XPM */
static char *broken_image[] = {
/* columns rows colors chars-per-pixel */
"16 16 4 1",
"  c red",
". c #C4BF91",
"X c #FFF8D6",
"o c None",
/* pixels */
"ooo..........ooo",
"ooo. XXXXX X.ooo",
"ooo.  XXX  X.ooo",
"ooo.X  X  XX.ooo",
"ooo.XX   XXX.ooo",
"ooo.X  X  XX.ooo",
"ooo.  XXX  Xoooo",
"ooo. XXXXo ooooo",
"ooo.XXXXoooooooo",
"ooo.XoXooooooooo",
"ooo.oooooooo.ooo",
"oooooooooXoX.ooo",
"ooooooooXXXX.ooo",
"oooooXoXXXXX.ooo",
"ooooXXXXXXXX.ooo",
"ooo..........ooo"
};
]]
xpm_tempfile=os.tmpname()
xpm_write=io.open(xpm_tempfile,"w")
if xpm_write then
 xpm_write:write(broken_xpm_data)
 xpm_write:close()
 broken_xpm=fltk:Fl_XPM_Image(xpm_tempfile)
 os.remove(xpm_tempfile)
end

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()
 if image_data then
   display:image(image_data:copy(128,128)) --show it bigger!
   broken=nil
   i_menu:mode(3,1) -- disable "fix" item
 else
   display:image(broken_xpm)
   broken=self:tooltip()
   i_menu:mode(3,0)
 end
 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()
 local v=i_menu:value()
 if v==0 and current_file then os.execute(editor.." "..current_file.." &")
 elseif v==1 then chdir()
 elseif v==2 then refresh()
 elseif v==3 then fix_xpm()
 elseif v==4 then os.exit(0)
 end
end

function find_stupid_space(infile)
 is_xpm,space_found=0,0
 local data=io.open(infile)
 if data then
   for line in data:lines() do
     -- is it really an xpm file
     if string.find(line,"/%*%s*XPM%s*%*/") then is_xpm=1 end
     -- is it going to break murgaLua
     if string.find(line,"^%s") then space_found=1; break end
   end
   data:close()
   return is_xpm,space_found
 end
end

function build_list(dfile)
 local active=1
 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(string.lower(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
   local is_xpm,space_found=find_stupid_space(dir..images[i])
   if is_xpm==1 then --if it's a true xpm
     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
       if space_found==0 then
         butt[xpm_count]:image(icon[i])
       else butt[xpm_count]:label("bad\nimage")
       end
         butt[xpm_count]:callback(get_image_data)
         butt[xpm_count]:tooltip(images[i])
         if broken==images[i] or dfile==images[i] then active=xpm_count end -- for refreshing display
         pack:add(butt[xpm_count])
       else icon[i]:uncache()
     end
   end
 end
 if butt[active] then
   -- show first image
   display:show()
   butt[active]:do_callback()
 end
 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

function fix_xpm()
 if broken then
   os.execute("cd "..dir.." && sxpm -nod "..broken.." -o "..broken)
   refresh()
 end
end

function open_file(what)
if lfs.attributes(what).mode=="directory" then
 dir=what
else
 dragfile=fltk.fl_filename_name(what)
 dir=string.gsub(what,"(.*/).*$","%1")
end
build_list(dragfile)
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
images={}; icon={}; butt={}
w=fltk:Fl_Window(ww,wh,"XPM Icons")

menu_items={"&Edit","&Directory","&Refresh","&Fix","&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

if arg[1] then open_file(arg[1]) else build_list() end -- drag and drop
w:show()
Fl:run()

Posted by jpeters on Nov. 04 2007,18:46
I was curious where the info regarding the path for individual dfm app icons is stored (the one listed in options)
Posted by mikshaw on Nov. 04 2007,19:22
If I understand your question, you want to know why it says "/usr/share/dfm/icons/blah.xpm" when you make a new icon? There are two files involved.
The file .dfmext (which you might need to create or copy) store info for default file type icons. The file .dfminfo is a gzipped text file that contains paths and locations for user-created and user-modified icons. This file is managed by dfm itself.

I think there might be some hardwired paths in the application, but I'm not sure.

Posted by jpeters on Nov. 04 2007,19:42
Quote (mikshaw @ Nov. 04 2007,14:22)
The file .dfminfo is a gzipped text file that contains paths and locations for user-created and user-modified icons.


.. okay, so the path to the desktop icon is listed after  "$name_ICON;"  where $name is the name on the icon.  It might be a nice feature if this path (to the icon directory) could be used with DND (assuming there's easy code to get the name) vs the app's desktop directory. I can already load any directory of xpm's via the XPM_viewer's menu.

Posted by mikshaw on Nov. 04 2007,22:13
I guess I'm still not sure what you're getting at. Are you talking about this script here or only dfm?

Quote
I can already load any directory of xpm's via the XPM_viewer's menu
But this is the only purpose for dragging with this script, and likely will remain that way. The script is made for XPM image files themselves, and has no relationship to the applications associated with those files in dfm. The dfm application already has the tools necessary to deal with the connection between applications and icons. This script is intended to view your XPM files (xzgv doesn't support xpm) and optionally open up an editor for the selected icon. I have no intention of integrating it into a particular desktop environment.

Posted by jpeters on Nov. 04 2007,22:40
Quote (mikshaw @ Nov. 04 2007,17:13)
I guess I'm still not sure what you're getting at. Are you talking about this script here or only dfm?

Quote
I can already load any directory of xpm's via the XPM_viewer's menu
But this is the only purpose for dragging with this script, and likely will remain that way. The script is made for XPM image files themselves, and has no relationship to the applications associated with those files in dfm. The dfm application already has the tools necessary to deal with the connection between applications and icons. This script is intended to view your XPM files (xzgv doesn't support xpm) and optionally open up an editor for the selected icon. I have no intention of integrating it into a particular desktop environment.

No problem.....I saw the viewer as potentially a way to edit desktop icons that were dragged onto it, if it opened to the correct directory.

Posted by mikshaw on Nov. 04 2007,23:36
Ohhhhhh....i get it now =o)

Yeah, that's kind of an iffy thing. While it would be a handy thing to be able to do, I have a feeling that the code required would make the script much larger and potentially less stable (depending on how well it was implemented).

I *might* look into doing this just for the experience and education that would come from it, but don't expect it to definitely be done, and don't expect it to show up in DSL even if it is accomplished.

Posted by jpeters on Nov. 05 2007,01:54
Funny, one of the icons that doesn't load is xpaint.xpm (perhaps it feels rejected)  :D

There's actually quite a few that don't load (going through dfm's icon directory).

Posted by mikshaw on Nov. 05 2007,02:19
The script loads only 32x32 pixmaps, but the xpaint pixmap is 32x30
Maybe I need to change it to less than or equal to 32 instead of exactly 32

EDIT: yep, that worked. Thanks for the report!

EDIT2: After making the change there are still 9 files in /usr/share/dfm/icons that are ignored by the script. Maybe there are some that have dimensions larger than 32

Posted by jpeters on Nov. 06 2007,00:15
Quote (mikshaw @ Nov. 04 2007,21:19)
The script loads only 32x32 pixmaps, but the xpaint pixmap is 32x30
Maybe I need to change it to less than or equal to 32 instead of exactly 32

EDIT: yep, that worked. Thanks for the report!

EDIT2: After making the change there are still 9 files in /usr/share/dfm/icons that are ignored by the script. Maybe there are some that have dimensions larger than 32

I was experimenting, and found  by increasing the "xpm_size" (line 9),
all the files eventually load. (try xpm_size=50)

Posted by mikshaw on Nov. 06 2007,02:12
I was just thinking the same thing =o)

I had the impression that dfm was limited to 32px icons, but I realize now that it was just an assumption. RobertS probably said 32 in order to keep the desktop uniform and light.  But larger pixmaps are supported so I'm going to change the script to accept up to 48px
Anything larger is pretty silly for a desktop icon in my opinion, but everyone is free to argue.

Are any of the icons in DSL 50px?

Posted by jpeters on Nov. 06 2007,03:03
Quote (mikshaw @ Nov. 05 2007,21:12)
Are any of the icons in DSL 50px?

48 seems to be the largest in the dfm icons folder (files-somecolor.xpm).

Posted by roberts on Nov. 06 2007,03:39
Are we going with line9

xpm_size=48

as the only change?

Posted by jpeters on Nov. 06 2007,03:50
Quote (roberts @ Nov. 05 2007,22:39)
Are we going with line9

xpm_size=48

as the only change?

...well hey....this program was by mikshaw... :D
Posted by mikshaw on Nov. 06 2007,04:19
Every change that was mentioned in this thread has been applied to the last script post, including the 48px chage and the <= instead of == comparison to the sizes, so 48 is the maximum size instead of the only size.
I made one other change to the script on my own system, which displays the dimensions of the selected image, that was not posted here.

Powered by Ikonboard 3.1.2a
Ikonboard © 2001 Jarvis Entertainment Group, Inc.