Updated 2013-03-30 00:12:19 by ABU

Here's a little package I wrote to do little sticky notes. It could easily be used as a stand-alone program with a little bit of saving / loading code, or it could be included as a package into any piece of software.

It requires Tcl 8.4 and has some features that are nice on Windows. With Tk 8.4.8 or above, it adds the ability to change the transparency of the sticky notes.

-- DC
 namespace eval ::stickies {
     variable stickies

     array set stickies {
         Font             "Verdana 9"
         Count            0
         Animate          1
         Padding          {2 15 2 2}
         DefaultWidth     175
         DefaultHeight    150
         MenuFont         "Tahoma 8"
         Background       "#ffff80"
         StickyPopup      ".stickyPopup"
         StickyPrefix     ".sticky"
         BorderBackground "#ffdf00"
     }
 }

 proc ::stickies::init {} {
     variable stickies

     package require Tk 8.4
     set m [menu $stickies(StickyPopup) -tearoff 0 -font $stickies(MenuFont)]

     $m add command -label "Delete Note" \
         -command ::stickies::DeleteSticky -font "$stickies(MenuFont) bold"
     if {[string equal $::tcl_platform(platform) "windows"]} {
         if {[package vsatisfies $::tk_patchLevel 8.4.8]} {
             $m add command -label "Set Transparency" \
                 -command ::stickies::SetTransparency -font "$stickies(MenuFont)"
         }
         $m add separator
         $m add checkbutton -label "Always on Top" \
             -command ::stickies::AlwaysOnTop
     }
 }

 proc ::stickies::NewSticky {} {
     variable stickies

     set top $stickies(StickyPrefix)[incr stickies(Count)]

     set stickies($top,ontop) 0

     toplevel    $top -background $stickies(BorderBackground)
     wm withdraw $top
     update idletasks
     wm geometry $top 1x1
     wm override $top 1

     bind $top <3> [list ::stickies::PostPopup $top %X %Y]

     bindtags $top [list $top Toplevel $top StickyNote all]
     bind StickyNote <1>         [list ::stickies::GrabSticky   %W %X %Y]
     bind StickyNote <Motion>    [list ::stickies::StickyMotion %W %X %Y]
     bind StickyNote <B1-Motion> [list ::stickies::DragSticky   %W %X %Y]

     set padding $stickies(Padding)

     text $top.t -relief flat -background $stickies(Background) \
         -font $stickies(Font) -wrap word
     pack $top.t -expand 1 -fill both \
         -padx [list [lindex $padding 0] [lindex $padding 2]] \
         -pady [list [lindex $padding 1] [lindex $padding 3]]

     if {$stickies(Animate)} {
         ::stickies::AnimateSticky $top
     } else {
         wm geometry $top $stickies(DefaultWidth)x$stickies(DefaultHeight)
     }

     wm deiconify $top

     focus $top.t
 }

 proc ::stickies::AnimateSticky { window {w 0} {h 0} } {
     variable stickies

     wm deiconify $window

     incr w 10
     incr h 10
     if {$w > $stickies(DefaultWidth)}  { set w $stickies(DefaultWidth)  }
     if {$h > $stickies(DefaultHeight)} { set h $stickies(DefaultHeight) }

     wm geometry $window ${w}x${h}
     update idletasks
     if {$w != $stickies(DefaultWidth) || $h != $stickies(DefaultHeight)} {
         after 5 [list ::stickies::AnimateSticky $window $w $h]
     }

     return
 }

 proc ::stickies::PostPopup { window X Y } {
     variable stickies
     set stickies(ActiveSticky) $window
     $stickies(StickyPopup) post $X $Y
 }

 proc ::stickies::DeleteSticky { {window ""} } {
     variable stickies
     if {![string length $window]} { set window $stickies(ActiveSticky) }
     destroy $window
 }

 proc ::stickies::SetTransparency { {window ""} } {
     variable stickies
     if {![string length $window]} { set window $stickies(ActiveSticky) }

     set top $stickies(StickyPrefix)__set_transparency

     toplevel     $top
     wm title     $top "Set Transparency"
     wm withdraw  $top
     wm geometry  $top +[winfo x $window]+[winfo y $window]
     wm resizable $top 0 0

     set ::stickies::alpha [wm attributes $window -alpha]

     scale $top.scale -orient horizontal -from 0.0 -to 1.0 \
         -width 10 -length 150 -resolution .01 -showvalue 0 \
         -variable ::stickies::alpha \
         -command [list ::stickies::SetWindowAlpha $window]
     pack  $top.scale -side left -expand 1 -fill both

     button $top.ok -text "OK" -width 10 -command [list destroy $top]
     pack   $top.ok -side left

     $stickies(StickyPopup) unpost
     update idletasks

     wm deiconify $top
 }

 proc ::stickies::SetWindowAlpha { window value } {
     wm attributes $window -alpha $value
     update idletasks
 }

 proc ::stickies::AlwaysOnTop { {window ""} } {
     variable stickies
     if {![string length $window]} { set window $stickies(ActiveSticky) }

     set stickies($window,ontop) [expr $stickies($window,ontop) ? 0 : 1]
     puts "$window - $stickies($window,ontop)"
     wm attributes $window -topmost $stickies($window,ontop)
     puts "$window - $stickies($window,ontop) - [wm attributes $window]"

     $stickies(StickyPopup) unpost
     update idletasks
 }

 proc ::stickies::StickyMotion { window X Y } {
     variable stickies

     set minX [winfo x $window]
     set minY [winfo y $window]
     set maxX [expr {$minX + [winfo width  $window]}]
     set maxY [expr {$minY + [winfo height $window]}]

     set x1 [expr {$X - 8}]
     set x2 [expr {$X + 8}]
     set y1 [expr {$Y - 4}]
     set y2 [expr {$Y + 4}]

     set cursor ""
     set stickies(dir) ""

     if {$x1 < $minX} {
         set cursor sb_h_double_arrow
         set stickies(dir) w
     }
         
     if {$x2 > $maxX} {
         set cursor sb_h_double_arrow
         set stickies(dir) e
     }

     if {$y1 < $minY} {
          set cursor sb_v_double_arrow
          set stickies(dir) n
      }
         
     if {$y2 > $maxY} {
          set cursor sb_v_double_arrow
          set stickies(dir) s
     }

     if {$x2 > $maxX && $y2 > $maxY} {
         set cursor size_nw_se
         set stickies(dir) se
     }

     if {$x1 < $minX && $y1 < $minY} {
         set cursor size_nw_se
         set stickies(dir) nw
     }

     if {$x2 > $maxX && $y1 < $minY} {
         set cursor size_ne_sw
         set stickies(dir) ne
     }

     if {$x1 < $minX && $y2 > $maxY} {
         set cursor size_ne_sw
         set stickies(dir) sw
     }

     $window configure -cursor $cursor
     update idletasks
 }

 proc ::stickies::GrabSticky { window X Y } {
     variable stickies

     if {![string length $stickies(dir)]} {
         set stickies(Xoffset) [expr {[winfo x $window] - $X}]
         set stickies(Yoffset) [expr {[winfo y $window] - $Y}]
     } else {
         set stickies(Xoffset) $X
         set stickies(Yoffset) $Y
     }

     set stickies(x)      [winfo x $window]
     set stickies(y)      [winfo y $window]
     set stickies(width)  [winfo width  $window]
     set stickies(height) [winfo height $window]
 }

 proc ::stickies::DragSticky { window X Y } {
     variable stickies

     if {![string length $stickies(dir)]} {
         set x [expr {$X + $stickies(Xoffset)}]
         set y [expr {$Y + $stickies(Yoffset)}]
         wm geometry $window +${x}+${y}
         return
     }

     set adjX [expr {$X - $stickies(Xoffset)}]
     set adjY [expr {$Y - $stickies(Yoffset)}]

     set x      $stickies(x)
     set y      $stickies(y)
     set width  $stickies(width)
     set height $stickies(height)

     switch -- $stickies(dir) {
         "n" {
             set y $Y
             set height [expr {$stickies(height) - $adjY}]
         }

         "e" {
             set width  [expr {$stickies(width)  + $adjX}]
         }

         "s" {
             set height [expr {$stickies(height) + $adjY}]
         }

         "se" {
             set width  [expr {$stickies(width)  + $adjX}]
             set height [expr {$stickies(height) + $adjY}]
         }
     }

     if {$width <= 0 || $height <= 0} { return }

     wm geometry $window ${width}x${height}+${x}+${y}
     update idletasks
 }

 ## A little demo code.
 ::stickies::init

 button .b -text "New Sticky" -command ::stickies::NewSticky
 pack .b -expand 1 -fill both

MG Dec 16th - Very nice, DC. This code could be really useful in a wide variety of applications, IMHO, as well as being a great example for people. One thing that would make it more useful, though, is if you could keep an entry for the sticky notes in the taskbar when it's used on Windows, so they don't get lost if -topmost isn't on. Anyone know if there's a way to do that? I tried the [wm attributes -toolwindow] option, and [wm transient], but neither worked...

ABU 5-Oct-2005 - Sticky 1.0 announce

ABU 28-Nov-2005 - Sticky 2.0 announce

Based on the original work of Damon, this is a screenshot of my little big application

Sticky allows you to stick small pieces of paper over your desktop.

  • You can use it to write down your notes.
  • You can easily move, resize, change the colors and fonts used in these sticky-notes.
  • All the sticky-notes are automatically saved on exiting (see .sticky.ini in your home directory)

Starting from version 2.0, Sticky is able to handle the logoff/shutdown event. In this way you can be sure that everything is automatically saved when user's sessions ends.

Note that the capability to handle the logoff/shutdown event (on Windows) is part of Tk-core since 8.4.14.

Download:

  • [1] sticky-2.0.1.zip
  • [2] sticky-2.0.1.kit

escargo 29 Nov 2005 - I discovered experimentally that you need to right click on the top bar to access sticky configuration options. The documentation said control was possible, but did not say how to do it.

escargo 13 Feb 2006 - I discovered that my laptop running Microsoft Windows XP Pro Service Pack 2 has an interesting issue with the individual notes. Different sets of notes appear when I boot my system connected to its docking bay and monitor than when I boot it standalone. I see that I have a .sticky.ini file and a .sticki.ini.bak file. Something must be determining which file to use. Also, since my screen geometry changes depending on which startup method I use, it's possible that the position specified in the "wm geometry" command might not be visible any more.

ABU 13 feb 2006

About your old question, I admit that it is not documented how to raise the popup menu; on the other hand, it is so simple ... right-click over a sticky mini-toolbar for changing one sticky property, or click on the "wheel" icon to open a "sample sticky" ; by changing this "sample sticky", you can set the whole look for all the next new stickies.

Now, about your second question, I have right no solution. .sticky.ini (within your home directory) saves all the geometry. .sticky.ini.bak is just the previous copy (a backup). If you logon with different users on your PC, there will be different copies of .sticky.ini (one for each Home directory). Maybe it could be useful a proc for expanding/collapsing the position of each widget, based on the current screen resolution ... let me think about it ...

escargo 14 Feb 2006 - I just found out why there is an issue. My IT admins added a startup script to my system that leaves the HOME directory on a personal network drive (W:\). If a Tcl program starts, and checks for the current directory, that's what it will find, not the directory C:\Documents and Settings\<user>\My Documents\. If I start up at home, or anywhere else detached from the network, there is no W: drive, and I'll see the file in C:\Documents and Settings\<user> instead. So there's a split personality involved with inconstant determined locations for .sticky.ini. This is one of the issues with Where to store application configuration files. I had not realized that the value of HOME could be changing without me logging in as a different user.

[GreenAsJade] 3 Jan 2007 ... TCL makes it very tempting to use $HOME by its built in provision of [file normalize "~"]. This implies that it's portable to use this to find the place to store user data on windows and unix. But as you've found, it doesn't work properly on unix because $HOME can change.

On Windows, you can use $USERPROFILE to find the right place to store application data, like this:
 set DataDir [expr { $tcl_platform(platform) eq "windows" ? \
                                        [file join $env(USERPROFILE) "Application Data" "APPNAME"] : \
                                        [file normalize "~/.APPNAME"] \
                                        }]

escargo 7 May 2007 - I started using a Windows desktop manager, Yod'm 3D. The virtual desktops appear to confuse Stickies so that it disappears. Is there an easy way to extract all the information in all the notes? I didn't find anything obvious in .sticky.ini.

ABU 9 May 2007 - It's not so difficult. Since ".sticky.ini" is not a pure data file, but it's a simple tcl-script, all we need is to redefine the "stickyNote create ..." command, and ignore all others commands ("wm ...").

Here is a script
  # dummy declaration
 namespace eval stickyNote {}

  # disable the "wm" command
 rename wm _wm
 proc wm args {}

 proc stickyNote { createCmd objID args } {
   puts "#######################################"
   foreach {op val} $args {
     # better to remove trailing blancs
     set val [string trim $val]
     puts "<$op><$val>"
   }
   puts "#######################################"
 }
 
 source [file join ~ .sticky.ini]

ABU 30 Mar 2013 All previous versions of sticky are now available at [3].