Text Capture Miniwindow Scrollbar

Posted by Deacla on Mon 22 Feb 2010 04:17 AM — 19 posts, 64,418 views.

USA #0
I've gotten many of the problems solved but i still have a few questions. I'll use that code you wrote to capture all text to a miniwindow to showcase the scrollbar. Normally I have one miniwindow to capture all private chats and party chats, and another miniwindow to capture clan chats.



Is it possible to make a roaming hotspot that follows the scrollbar so I could drag it up and down?

How do i make the mousedown function of the scroll hotspots wait a second to verify intent then continually scroll in the correct direction until mouseup?

Is there a way to place variable x,y positions in WindowPolygon?
USA #1

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>

<muclient>
<plugin
   name="Text_Capture_Window"
   author="Deacla"
   id="6307d31253862de093c248e4"
   language="Lua"
   purpose="Redirects all output to a miniwindow"
   date_written="2010-02-21"
   requires="4.40"
   version="1.0"
   save_state="y"
   >
<description trim="y">
<![CDATA[

This window captures all text to test the scrollbar function.

The window can be dragged to a new location with the mouse.

Based on code designed by Nick Gammon.
]]>
</description>

</plugin>

<triggers>

  <trigger
   enabled="y"
   match="*"
   script="chat_handler"
   sequence="100"
  >
  </trigger>

</triggers>

<!--  Script  -->


<script>
<![CDATA[

require "copytable"



-- START OF CONFIGURATION OPTIONS

-- window size in pixels
WINDOW_WIDTH = 360
WINDOW_HEIGHT = 150 

MAX_LINES = 1000 -- how many lines to store

-- font
FONT_NAME = "Dina"
FONT_SIZE = 10

-- where to put the window if a position has not been stored
WINDOW_POSITION = 8  

-- colours
WINDOW_BACKGROUND_COLOUR = 0x000000
WINDOW_BORDER_COLOUR = 0x335533

-- offset of text from edge
TEXT_INSET = 5

-- where to store the chat line
lines = {}  -- table of recent chat lines

-- END OF CONFIGURATION OPTIONS




--START OF MOUSE FUNCTIONS 

function scroll_up (flags, hotspot_id)

	if #lines > visible_lines then 
		scrolling = scrolling + 1
		if #lines - visible_lines - scrolling < 1 then
			scrolling = scrolling - 1
		end -- if
		display_all ()
	end  -- if

    scroll_up_md = 0

end   -- scroll_up



function scroll_down (flags, hotspot_id)

	if scrolling > 0 then
		scrolling = scrolling - 1
        display_all ()
	end  -- if

end   -- scroll_down



function mousedown(flags, hotspot_id)

  -- find where mouse is so we can adjust window relative to mouse
  startx, starty = WindowInfo (win, 14), WindowInfo (win, 15)
  
  -- find where window is in case we drag it offscreen
  origx, origy = WindowInfo (win, 10), WindowInfo (win, 11)
end -- mousedown

function dragmove(flags, hotspot_id)

  -- find where it is now
  local posx, posy = WindowInfo (win, 17),
                     WindowInfo (win, 18)

  -- move the window to the new location
  WindowPosition(win, posx - startx, posy - starty, 0, 2);
  
  -- change the mouse cursor shape appropriately
  if posx < 0 or posx > GetInfo (281) or
     posy < 0 or posy > GetInfo (280) then
    check (SetCursor ( 11))   -- X cursor
  else
    check (SetCursor ( 1))   -- hand cursor
  end -- if
  
end -- dragmove

function dragrelease(flags, hotspot_id)
  local newx, newy = WindowInfo (win, 17), WindowInfo (win, 18)
  
  -- don't let them drag it out of view
  if newx < 0 or newx > GetInfo (281) or
     newy < 0 or newy > GetInfo (280) then
     -- put it back
    WindowPosition(win, origx, origy, 0, 2);
  end -- if out of bounds
  
end -- dragrelease

-- END OF MOUSE FUNTIONS





--START OF CHAT HANDLING FUNCTIONS

function chat_handler (name, line, wildcards, styles)

  
  local avail = 0
  local line_styles
  
  -- blank line? force one entry
  if #styles == 0 then
    -- remove first line if filled up
    if #lines >= MAX_LINES then
      table.remove (lines, 1)
    end -- if 
    table.insert (lines, {})
  end -- if
  
  -- keep pulling out styles and trying to fit them on the current line
  
  while #styles > 0 do
	

  
    -- no room available? start new line
    
    if avail <= 0 then
      -- remove first line if filled up
      if #lines >= MAX_LINES then
        table.remove (lines, 1)
      end -- if 
      avail = WINDOW_WIDTH - (TEXT_INSET * 2)
      line_styles = {}
      table.insert (lines, line_styles)
    end -- line full

    -- get first style, work out how long it is
    local style = table.remove (styles, 1)  -- pull out first style
    local width = WindowTextWidth (win, "f", style.text)  -- how long this style is

    -- if it fits, copy whole style in
    if width <= avail then
      table.insert (line_styles, style)
      avail = avail - width
    else -- otherwise, have to split style
    
      -- look for trailing space (work backwards)
      -- remember where space is
      
      local col = style.length - 1
      local split_col
      
      -- keep going until out of columns
      
      while col > 1 do
        width = WindowTextWidth (win, "f", style.text:sub (1, col))
        if width <= avail then
          if not split_col then
            split_col = col  -- in case no space found, this is where we can split
          end -- if
          
          -- see if space here
          if style.text:sub (col, col) == " " then
            split_col = col
            break
          end -- if space
        end -- if will now fit
        col = col - 1

      end -- while
          
      -- if we found a place to split, use old style, and make it shorter
      -- also make a copy and put the rest in that
      if split_col then
        table.insert (line_styles, style)
        local style_copy = copytable.shallow (style)
        style.text = style.text:sub (1, split_col)
        style.length = split_col 
        style_copy.text = style_copy.text:sub (split_col + 1)
        style_copy.length = #style_copy.text
        table.insert (styles, 1, style_copy)
      else
        if next (line_styles) == nil then
          table.insert (line_styles, style)
        else
          table.insert (styles, 1, style)
        end -- if
      end -- if    
    
      avail = 0  -- now we need to wrap
      
    end -- if could not fit whole thing in
          
  end -- while we still have styles over


  display_all ()

end -- chat _handler

-- END OF CHAT HANDLING FUNCTIONS


USA #2


-- START OF DISPLAY FUNCTION

function Display_Line (line, styles)  -- display one line

 
  local left = TEXT_INSET
  local top = (line - 1) * font_height
  
  for _, v in ipairs (styles) do
    left = left + WindowText (win, "f", v.text, left, top, 0, 0, v.textcolour)
  end -- for each style run                 

 
end -- Display_Line


function display_all (flags, name)

		
		-- 
		local first_visible = #lines - visible_lines - scrolling

		if first_visible < 1 then
			first_visible = 1
		end -- if
        
        local scroll_range		= WINDOW_HEIGHT - 35

		if #lines > visible_lines then 

			scroll_line_pool	= #lines  - visible_lines
			scroll_per_line 	= scroll_range / scroll_line_pool
			scroll_amount	 	= scrolling * scroll_per_line

		end -- if

		local scroll_adjust		= 0 + scroll_amount
		local scroll_position	= (WINDOW_HEIGHT - 20) - scroll_adjust
		

		local scroll_bar_top 	= scroll_position - 5
		local scroll_bar_bottom	= scroll_position + 5

		-- blank existing window contents
		WindowRectOp (win, 2, 0, 0, 0, 0, WINDOW_BACKGROUND_COLOUR)
		WindowRectOp (win, 5, 0, 0, 0, 0, 5, 15)

		--  draw scroll buttons
		WindowRectOp (win, 5,WINDOW_WIDTH - 15, 0, WINDOW_WIDTH, 15, 9, 15 + 0x0800 + 0x1000)
		WindowRectOp (win, 5,WINDOW_WIDTH - 15, WINDOW_HEIGHT - 15, WINDOW_WIDTH, WINDOW_HEIGHT, 9, 15 + 0x0800 + 0x1000)

		-- draw triangles on scroll buttons
		WindowPolygon (win, "352, 5, 355, 10, 350, 10",
						ColourNameToRGB("gray"), 0, 1,
						ColourNameToRGB("lightgray"), 0,
						true,
						false)
		WindowPolygon (win, "352, 145, 355, 140, 350, 140",
						ColourNameToRGB("gray"), 0, 1,
						ColourNameToRGB("lightgray"), 0,
						true,
						false)

		-- draw scroll bar
		WindowRectOp (win, 5, WINDOW_WIDTH - 15, scroll_bar_top, WINDOW_WIDTH, scroll_bar_bottom,
							9, 15 + 0x0800 + 0x1000) 
        
		-- 	display lines

		local count = 0

		for i = first_visible, #lines do
			count = count + 1
			Display_Line (count, lines )
		end -- for

		-- force window redisplay

		WindowShow (win,  true)  -- show it
               
end -- end function display_all

-- END OF DISPLAY FUNCTIONS



function OnPluginInstall ()
  
  win = GetPluginID ()

  local x, y, mode, flags = 
      tonumber (GetVariable ("x")) or 0,
      tonumber (GetVariable ("y")) or 0,
      tonumber (GetVariable ("mode")) or 10, -- bottom left
      tonumber (GetVariable ("flags")) or 0
    
-- make the window
  check (WindowCreate (win,
                x, y, WINDOW_WIDTH, WINDOW_HEIGHT,
                mode,
                flags,
                WINDOW_BACKGROUND_COLOUR) ) -- create window

  check (WindowFont (win, "f", FONT_NAME, FONT_SIZE)) -- define font  
  font_height = WindowFontInfo (win, "f", 1)   -- height of the font  


  visible_lines = math.floor (WINDOW_HEIGHT / font_height) - 1
  scrolling = 0
  scroll_amount = 0

  -- make a hotspot
  WindowAddHotspot(win, "hs1",  
                   0, 0, WINDOW_WIDTH - 15, WINDOW_HEIGHT,   -- whole window
                   "",   -- MouseOver
                   "",   -- CancelMouseOver
                   "mousedown",
                   "",   -- CancelMouseDown
                   "",   -- MouseUp
                   "Drag to move",  -- tooltip text
                   1, 0)  -- hand cursor

  WindowAddHotspot(win, "hs2",  
                   WINDOW_WIDTH - 15, 0, WINDOW_WIDTH, 15,   -- top left corner
                   "",   -- MouseOver
                   "",   -- CancelMouseOver
                   "",   -- MouseDown
                   "",   -- CancelMouseDown
                   "scroll_up",   -- MouseUp
                   "Click to scroll up",  -- tooltip text
                   1, 0)  -- hand cursor

  WindowAddHotspot(win, "hs3",  
                   WINDOW_WIDTH - 15, WINDOW_HEIGHT - 15, WINDOW_WIDTH, WINDOW_HEIGHT,   -- bottom right corner
                   "",   -- MouseOver
                   "",   -- CancelMouseOver
                   "",   -- MouseDown
                   "",   -- CancelMouseDown
                   "scroll_down",   -- MouseUp
                   "Click to scroll down",  -- tooltip text
                   1, 0)  -- hand cursor

                   
  WindowDragHandler(win, "hs1", "dragmove", "dragrelease", 0) 

  if GetVariable ("enabled") == "false" then
    ColourNote ("yellow", "", "Warning: Plugin " .. GetPluginName ().. " is currently disabled.")
    check (EnablePlugin(GetPluginID (), false))
    return
  end -- they didn't enable last time

end -- OnPluginInstall


function OnPluginDisable ()
  WindowShow (win, false)
end -- OnPluginDisable

function OnPluginSaveState ()
  SetVariable ("enabled", tostring (GetPluginInfo (GetPluginID (), 17)))
  SetVariable ("x", tostring (WindowInfo (win, 10)))
  SetVariable ("y", tostring (WindowInfo (win, 11)))
  SetVariable ("mode", tostring (WindowInfo (win, 7)))
  SetVariable ("flags", tostring (WindowInfo (win, 8)))
end -- OnPluginSaveState

]]>
</script>
</muclient>
USA #3
Deacla said:
Is it possible to make a roaming hotspot that follows the scrollbar so I could drag it up and down?

You need to use the WindowDragHandler function to set drag and release handlers, you can't really do this with plan mousedown/mouseup handlers alone. That's what Deacla did in his/her plugin above.

Deacla, you should probably put that through the Edit -> Convert Clipboard Forum Codes utility in MUSHclient, because most of the second half of the plugin is italicized. Or better, paste it all in one chunk to mushclient.pastebin.com, and link us the URL here! (I would not recommend doing both at once, though.)
Amended on Mon 22 Feb 2010 04:28 AM by Twisol
Australia Forum Administrator #4
Deacla said:

Is there a way to place variable x,y positions in WindowPolygon?


Yes, just string.format the string that it expects. In other words, instead of:


WindowPolygon (win, "20,50,180,50,180,20",
                        ColourNameToRGB("cyan"), 0, 3,   -- pen (solid, width 3)
                        ColourNameToRGB("yellow"), 0,    -- brush (solid)
                        true,    -- fill
                        false)   -- alternate fil


You could do:



-- set up variables
local x1, y1, x2, y2, x3, y3 = 20, 50, 180, 50, 180, 20

-- convert into a string with commas in it
points = string.format ("%i,%i,%i,%i,%i,%i", x1, y1, x2, y2, x3, y3)

WindowPolygon (win, points,
                        ColourNameToRGB("cyan"), 0, 3,   -- pen (solid, width 3)
                        ColourNameToRGB("yellow"), 0,    -- brush (solid)
                        true,    -- fill
                        false)   -- alternate fil

Amended on Mon 22 Feb 2010 04:35 AM by Nick Gammon
USA #5
Ok so now the little triangles have variable positions dependent upon WINDOW_HEIGHT and WINDOW_WIDTH, perfect. Thank you Nick.

I also posted the entire code (w/ revision) here:

http://pastebin.com/f1ab7541

Quote:

Twisol:

You need to use the WindowDragHandler function to set drag and release handlers, you can't really do this with plain mousedown/mouseup handlers alone.


I'd be happy just to have a repeat function on the mousedown handler of the hotspots "hs2" and "hs3", which call functions scroll_up and scroll_down, respectively. The scrollbar right now is only a graphical representation of position. I think i'd have to change the math quite a bit to get it to also work as a dragable button.
USA #6
I would like to briefly point out that I am clearly an idiot for not noticing that all three initial posts were by the same author.
USA #7
Deacla said:

Quote:

Twisol:

You need to use the WindowDragHandler function to set drag and release handlers, you can't really do this with plain mousedown/mouseup handlers alone.


I'd be happy just to have a repeat function on the mousedown handler of the hotspots "hs2" and "hs3", which call functions scroll_up and scroll_down, respectively. The scrollbar right now is only a graphical representation of position. I think i'd have to change the math quite a bit to get it to also work as a dragable button.


Try a timer, perhaps? Enable on mousedown, disable on mouserelease.
USA #8
How can I reference a script function within a timer?


  AddTimer ("scroll_up_timer", 0, 0, 0.5, "", 0, "scroll_up ()") 


I'm surely doing this wrong as it doesn't do anything. I want a timer that calls the scroll_up function every .5 seconds.

I already have it working where it enables on mousedown, and disables on mouseup. and i tested that using a "look" command to the world.
Amended on Mon 22 Feb 2010 06:47 AM by Deacla
USA #9
Deacla said:

How can I reference a script function within a timer?


  AddTimer ("scroll_up_timer", 0, 0, 0.5, "", 0, "scroll_up ()") 


I'm surely doing this wrong as it doesn't do anything. I want a timer that calls the scroll_up function every .5 seconds.

I already have it working where it enables on mousedown, and disables on mouseup. and i tested that using a "look" command to the world.


You're close. First though, I would recommend not doing it through script. Just like you can add a trigger through AddTrigger, yet most people opt for XML instead, as it is with timers. Write a timer in the GUI dialog, and Copy (button at the bottom of the list dialog) it to XML format, then paste into your plugin.

As for the code now, just remove the parentheses next to scroll_up:

AddTimer("scroll_up_timer", 0, 0, 0.5, "", 0, "scroll_up")
Amended on Mon 22 Feb 2010 06:56 AM by Twisol
USA #10
Yep, that got it working just fine. Thanks Twisol. Now to figure out the scroll BAR. I think that can wait for another time tho.
USA #11
as an aside, if you have multiple miniwindow plugins, what determines in which order they are drawn. Or more to the point, how do i get a specific miniwindow to always be on top of all other miniwindows?
USA #12
I recall reading somewhere that Nick said that the order is based on the names of the miniwindows themselves. Speaking simply, 'a' will be drawn before 'z', and '0' will be drawn before '9'. Technically, it's based on the ASCII values of the characters of the name. 'a' is 97 in ASCII, 'A' is 65 (so 'A' is drawn before 'a'), '0' is 48, and so on. To control the draw order, you'd have to name your windows accordingly.

To be honest, I wish there was a true z-order mechanism built in... something like the Sequence aliases, triggers, and timers have would be nice at least.
Amended on Mon 22 Feb 2010 08:17 AM by Twisol
Australia Forum Administrator #13
Twisol said:

To be honest, I wish there was a true z-order mechanism built in... something like the Sequence aliases, triggers, and timers have would be nice at least.


There *is* a z-order sequence. They are drawn in alphabetic order by name, so the lower the name is, the further to the back it is.
USA #14
Hopefully you can see it in this pic, but i changed the alphabetical order and still the windows tile in some unknown order . . .

http://i849.photobucket.com/albums/ab54/Deacla/screencap.jpg


The alpha order is:

  1. AAA_Auction_Window
  2. AAB_Tellz_Window
  3. AAD_Clan_window
  4. AAZ_Health_Bar_And_Other_Info
  5. ABA_Quest_Capture


But the windows are tiling in this order (if you can't deduce it yourself), from on_bottom to on_top:

  1. AAA_Auction_Window
  2. ABA_Quest_Capture
  3. AAZ_Health_Bar_And_Other_Info
  4. AAB_Tellz_Window
  5. AAD_Clan_window


The filenames are exactly the same as the plugin names + .xml
USA #15
Nick Gammon said:

Twisol said:

To be honest, I wish there was a true z-order mechanism built in... something like the Sequence aliases, triggers, and timers have would be nice at least.


There *is* a z-order sequence. They are drawn in alphabetic order by name, so the lower the name is, the further to the back it is.


Yes, that's the one I had just described. By "real z-order mechanism" I meant one that's not quite as static as using names which can't be changed later.
Amended on Mon 22 Feb 2010 05:55 PM by Twisol
USA #16
Deacla said:

Hopefully you can see it in this pic, but i changed the alphabetical order and still the windows tile in some unknown order . . .

http://i849.photobucket.com/albums/ab54/Deacla/screencap.jpg


The alpha order is:


*AAA_Auction_Window
*AAB_Tellz_Window
*AAD_Clan_window
*AAZ_Health_Bar_And_Other_Info
*ABA_Quest_Capture


But the windows are tiling in this order (if you can't deduce it yourself), from on_bottom to on_top:


*AAA_Auction_Window
*ABA_Quest_Capture
*AAZ_Health_Bar_And_Other_Info
*AAB_Tellz_Window
*AAD_Clan_window


The filenames are exactly the same as the plugin names + .xml


It's the windows that have the name z-order, not the plugins. After all, a plugin can have multiple windows. Name your windows with the prefixes you gave instead.
Australia Forum Administrator #17
If you want a unique ID for the window, but also control the z-order, do something like (for the Window IDs):


win1 = "001" .. GetPluginID ()
win2 = "002" .. GetPluginID ()


USA #18
Nick Gammon said:

If you want a unique ID for the window, but also control the z-order, do something like (for the Window IDs):


win1 = "001" .. GetPluginID ()
win2 = "002" .. GetPluginID ()





True, but there's no easy way to change the z-order after the fact, say if you want to implement a change-of-focus effect (for example, clicking a window brings it to the top of its stack).

EDIT: "runtime" -> "after the fact"