unixb4coffee profile image

Linux for Users: Notes on Your Desktop

Wnotes on a Motif desktop.
Wnotes on a Motif desktop.

The notes described here, Wnotes, are useful for the times that you want a scratch pad sitting unobtrusively on one corner of your X Window System desktop. A Wnote is basically a simple window that lets you type, import, export, cut, and paste useful bits of text to and from other Wnotes and programs.

Each Wnote is completely self contained. Installing and using Wnotes requires that you can install programs that use the Xlib and libXpm libraries, which you may already have because most graphical programs use them. Once you've installed Wnotes, they run from the shell prompt as normal windows.

Unlike note programs like GNOME's Post-Its or Apple's Stickies, Wnotes do not use a special database to store notes. Instead, Wnotes use command line options, the X selection, and normal files for whatever note text you want to keep.

Wnotes are designed to be basic and flexible. The are compatible with GNOME's Nautilus desktop window manager, and provide options that allow them to co-exist with just about any desktop program available for Linux.

To install Wnotes, copy the two source files below, "main.cpp," and "notewindow.cpp," into a text editor, and then build them with the following command.

$ g++ main.cpp notewindow.cpp -o wnote -lXpm -lX11

Assuming that you've installed the include files for the X Window System libraries, that's all there is to it. Then copy the, "wnote," executable to a useful place, like, "/usr/local/bin," if you have superuser privileges.

Typing, "wnote -h," prints a list of command line options. You can select the font, the note's starting position and size, whether it stays above or below other windows, the colors of the note, an output file to save the text in, and an option to write the note's settings as a shell command.

Clicking on the close box in the upper left-hand corner of the note closes the note and exits. Clicking on the bar along the top edge moves the note, and clicking on the tab in the lower left-hand corner resizes the note.

Double clicking on a note causes it to stay on top of other windows. Double clicking again places the note below other windows, at least with window managers that use a standard background window. Because not all window managers do this, especially window managers that use desktop icons, the, "-a," and, "-z," command line options allow you to keep notes visible on the desktop without worrying too much about a specific desktop program's configuration.

To cut and paste text using the X selection, click on the text in the note and move the cursor to the end of the text you want to select. Then go to the window that you want to paste the text in, and click on the middle mouse button, or the right and left buttons simultaneously if you have a two-button mouse. To paste text into a note, highlight the text in the window where its located, move the mouse over to the note window, and, again, click on the middle mouse button. To keep the program simple, Wnotes use the selection instead of the clipboard, in which you copy text with, "Control-C," and paste it with, "Control-V," because more X programs support cutting and pasting using the selection.

Again, the notes don't use a database to hold information. They're designed to be simple and flexible, so they use only the text you type in the note itself, or provide on the command line with the, "-t," option. When you close a note, it prints the text on display, so you can redirect it to a file. The, "-o," option has the same effect. So in order to save the text of a note, you can use one of the following commands.

$ wnote >this_note.txt
$ wnote -o this_note.txt

When opening a note, you can input text with the, "-t," option.

$ wnote -t "This is the text of the note."

Remember that you need to enclose the text in quotes if it contains spaces or carriage returns.

Together, the, "-t," "-o," and, "-c," options allow you to use shell commands to save note text, and the, "-c," option outputs not only the text of the note, but also the shell command and options that allow you to open the note again with the same text, color, and position. So, for example, to make a persistent note, you could use several commands similar to these.

$ wnote -bg blue -a -g 200x200+10+10 -c >note.out
$ sh -c "`cat note.out`"

The text of the command line to open the note again is in, "note.out," after the note exits. When reopening it, you need to use both double quotes and backticks to expand the complete text of the command and options from the previous note.

Together these options allow many possible uses, while keeping the Wnotes program as simple as possible. Wnotes are not mini word processors, not yet at any rate, because in this version they provide only basic text editing capabilities, but the program offers many possibilities for expansion as you discover new uses for it.


main.cpp

/*
  This file is part of wnotes.
  Copyright © 2011 unixb4coffee@gmail.com.

  This program is free software; you can redistribute
  it and/or modify it under the terms of the GNU
  General Public License as published by the Free
  Software Foundation; either version 2 of the License,
  or (at your option) any later version.

  This program is distributed in the hope that it will
  be useful, but WITHOUT ANY WARRANTY; without even the
  implied warranty of MERCHANTABILITY or FITNESS FOR A
  PARTICULAR PURPOSE.  See the GNU General Public
  License for more details.

  You should have received a copy of the GNU General
  Public License along with this program; if not, write
  to the Free Software Foundation, Inc., 59 Temple
  Place, Suite 330, Boston, MA 02111-1307 USA
*/

#include <iostream>
#include <fstream>
#include <X11/Xlib.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

using namespace std;

// Generated if user presses Ctrl-C.
#define SIGINT 2  

void help (void);
int parse_args (int , char **);
void app_exit (int);

static char bg_color[255] = "";
static char fg_color[255] = "";
static char wd_color[255] = "";
static char geometry[255] = "";
static char font    [255] = "";
static Bool write_cmd = False;
static char ofn[FILENAME_MAX] = "";
static char text[8192] = "";
static char app_name[255];

Display *d;
Window root;

typedef enum {
 stay_default,
 stay_on_top,
 stay_on_bottom} FloatState;

FloatState float_opt;

extern void initialize_notewindows (Display *, Window);
extern void delete_note (void);
extern void remove_note (void);
void note_event_loop (void);
extern char *note_text (void);
extern char *note_output (void);

void handle_ctrl_c (int signo) { app_exit (0); }

int main (int argc, char **argv) {

  if ((d = XOpenDisplay (getenv ("DISPLAY"))) 
      == NULL) {
      cout << argv[0] << ": Can't open display." 
	   << endl;
      exit (1);
  }
  root = RootWindow (d, DefaultScreen (d));
  float_opt = stay_default;
  parse_args (argc, argv);
  signal (SIGINT, handle_ctrl_c);
  signal (SIGTERM, handle_ctrl_c);
  initialize_notewindows (d, root);
  note_event_loop ();
  app_exit (0);
  return 0;
}

char *fg_color_opt (void) 
  {return *fg_color? fg_color: NULL;}
char *bg_color_opt (void) 
  {return *bg_color? bg_color: NULL;}
char *wd_color_opt (void) 
  {return *wd_color? wd_color: NULL;}
char *fn_opt (void) 
  {return *font? font: NULL;}
char *geometry_opt (void) 
  {return *geometry? geometry: NULL;}
char *text_opt (void) 
  {return *text? text: NULL; }
FloatState stay_opt (void) 
  {return float_opt;}
Bool write_cmd_opt (void) 
  {return write_cmd;}
char *get_app_name (void) 
  {return app_name; }
char *get_ofn (void) 
  {return *ofn ? ofn : NULL; }

#define ARGCHECK ((i < c) && (a[i][0] != '-'))
int parse_args (int c, char **a) {
  int i;
  XFontStruct *font_struct;
  strcpy (app_name, a[0]);
  for (i = 1; i < c; i++) {
    if (a[i][0] == '-') {
      switch (a[i][1]) 
	{
	case 'a':
	  float_opt = stay_on_top;
	  break;
	case 'b':
	  ++i;
	  if (!ARGCHECK)
	    cout << "-bg: Argument error.\n";
	  else
	    strcpy (bg_color, a[i]);
	  break;
	case 'c':
	  write_cmd = True;
	  break;
	case 'f':
	  switch (a[i][2])
	    {
	    case 'g':
	      ++i;
	      if (!ARGCHECK)
		cout << "-fg: Argument error.\n";
	      else
		strcpy (fg_color, a[i]);
	      break;
	    case 'n':
	      ++i;
	      if ((font_struct =
		   XLoadQueryFont (d, a[i])) 
		  == NULL) {
		cout << app_name << ": -fn: font, \"" 
		     << a[i] 
		     << ",\" not found.\n";
	      } else {
		XFreeFont (d, font_struct);
		strcpy (font, a[i]);
	      }
	      break;
	    }
	  break;
	case 'g':
	  ++i;
	  if (!ARGCHECK)
	    cout << "-g: Argument error.\n";
	  else
	    strcpy (geometry, a[i]);
	  break;
	case 'o':
	  ++i;
	  if (!ARGCHECK)
	    cout << "-o: Argument error.\n";
	  else
	    strcpy (ofn, a[i]);
	  break;
	case 't':
	  ++i;
	  if (!ARGCHECK) {
	    cout << "-t: Argument error.\n";
	  } else {
	    for (int j = i; i < c; i++) {
	      strcat (text, a[i]); 
	      if (i < (c - 1)) strcat (text, " ");
	    }
	  }
	  break;
	case 'w':
	  ++i;
	  if (!ARGCHECK)
	    cout << "-wd: Argument error.\n";
	  else
	    strcpy (wd_color, a[i]);
	  break;
	case 'z':
	  float_opt = stay_on_bottom;
	  break;
	case 'h':
	default:
	  help ();
	  break;
	}
    }
  }
  return 0;
}

void help (void) {
  cout << "Usage: " << app_name <<
    " [-h] | [-a] "
       << "[-bg <color>] "
       << "[-c] "
       << "[-fg <color>] "
       << "[-fn <font>] "
       << "[-g <geom>] " 
       << "[-o <fn>] "
       << "[-t <text>] "
       << "[-z]"
       << endl;
  cout 
    << "-a               Stay on top.\n";
  cout 
    << "-bg <color>      Background color.\n";
  cout 
    << "-c               When exiting, write a command\n";
  cout 
    << "                 line to re-open the note.\n";
  cout 
    << "-fg <color>      Foreground color.\n";
  cout 
    << "-fn <font>       Text font.\n";
  cout 
    << "-g <geometry>    Note size and position.\n";
  cout 
    << "-h               Print this message and exit.\n";
  cout 
    << "-o <fn>          Save note in file <fn>.\n";
  cout 
    << "-t <text>        Everything following is note "
    << "text.\n";
  cout 
    << "-wd <color>      Widget color.\n";
  cout
    << "-z               Stay on bottom (Not all window\n";
  cout
    << "                 managers support this.)\n";
  cout 
    << "Report bugs to: unixb4coffee@gmail.com.\n";
  exit (1);
}

void app_exit (int r) {
  char *c;
  ofstream f;
  if (*ofn) {
    f.open (ofn);
    f << note_text ();
    f.close ();
  }
  cout << note_output ();
  remove_note ();
  exit (r);
}

notewindow.cpp

#include <cstdlib>
#include <iostream>
#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>
#include <X11/keysymdef.h>
#include <string.h>

using namespace std;

#define DEPTH(d) \
  DefaultDepth (d, DefaultScreen (d))
#define DEFAULT_BG "yellow"
#define DEFAULT_FG "black"
#define DEFAULT_BOX "darkslategray"
#define DEFAULT_FONT \
  "-*-helvetica-medium-r-*-*-12-*-*-*-*-*-*-*"
#define PADDING 1
#define BOX_SIDE 5
#define MAXTEXT 8192
#define POINT_WIDTH 2
// Linux line endings are a single character.
#define LINE_END '\x0d'

// Definitions for the window decorations.
#define N_RESIZE_POINTS 11
static XPoint resize_tab[N_RESIZE_POINTS] = {
  {-10, 0}, {-9, -1}, {-8, -2}, {-7, -3},
  {-6, -4}, {-5, -5}, {-4, -6}, {-3, -7},
  {-2, -8}, {-1, -9}, {0, -10}};
static XRectangle box[1] = 
  {{PADDING,PADDING,BOX_SIDE,BOX_SIDE}};
static XSegment lines[2] = {
  {PADDING,PADDING,PADDING+BOX_SIDE,PADDING+BOX_SIDE},
  {PADDING,PADDING+BOX_SIDE,PADDING+BOX_SIDE,PADDING}};
#define move_mask_width 6
#define move_mask_height 6
static unsigned char move_mask_bits[] = {
   0x2a, 0x15, 0x2a, 0x15, 0x2a, 0x15 };

// How we know if we're pinned above or
// below other windows.
typedef enum {
 stay_default,
 stay_on_top,
 stay_on_bottom} FloatState;

// C functions are convenient when calling
// from other files and for use in constructors.
void app_exit (int);
char *fg_color_opt (void);
char *bg_color_opt (void);
char *wd_color_opt (void);
char *geometry_opt (void);
char *fn_opt (void);
char *text_opt (void);
FloatState stay_opt (void); 
Bool write_cmd_opt (void); 
char *get_app_name (void);
char *get_ofn (void);
unsigned long color_to_pix (Display *, char *);
int shifted_keysym (int);
void note_form (Display *, Pixmap, Pixmap, GC, 
		unsigned long, unsigned long, 
		unsigned long,
		XRectangle *, XRectangle *,
		int, int);
Window gnome_ws (Display *, Window, int *);

// A CharBox contains a typed character and
// its font dimensions as screen coordinates.
class CharBox {
public:
  CharBox (int, int, int, int, int, int);
  void add_to_list (CharBox *, CharBox *);
  void insert_after (CharBox *, CharBox *);
  int get_x (void) {return x;}
  int get_y (void) {return y;}
  int get_insert_x (void) {return insert_x;}
  int get_insert_y (void) {return insert_y;}
  int get_width (void) {return width;}
  int get_height (void) {return height;}
  int get_keysym (void) {return keysym;}
  void set_x (int x_point) { x = x_point; }
  void set_y (int y_point, int pascent) 
    { y = y_point - pascent; }
  void set_insert_x (int x_point) {insert_x=x_point;}
  void set_insert_y (int y_point) {insert_y=y_point;}
  CharBox *get_next (void) {return next;}
  CharBox *get_prev (void) {return prev;}
  CharBox *remove_from_list (CharBox *);
private:
  int keysym,
    x, y, 
    width, height,
    insert_x,
    insert_y;
  CharBox *next, *prev;
};

CharBox::CharBox (int pkeysym, int x_point, 
		  int y_point, int pwidth, 
		  int pascent, int pdescent) {
  keysym = pkeysym; 
  x = x_point;
  y = y_point - pascent; 
  width = pwidth;
  height = pascent + pdescent;
  insert_x = x_point; 
  insert_y = y_point;
  next = prev = NULL;
}

void CharBox::add_to_list (CharBox *list_end, 
			   CharBox *c) {
  list_end -> next = c;
  c -> prev = list_end;
  list_end = c;
}

void CharBox::insert_after (CharBox *insert_point, 
			    CharBox *c) {
  if (insert_point -> next) {
    insert_point -> next -> prev = c;
    c -> next = insert_point -> next;
  }
  c -> prev = insert_point;
  insert_point -> next = c;
}

CharBox *CharBox::remove_from_list (CharBox *t) {
  if (t -> prev) t -> prev -> next = t -> next;
  if (t -> next) t -> next -> prev = t -> prev;
  return t;
}

class NoteWindow {
public:
  NoteWindow (Display *, Window);
  ~NoteWindow (void);
  void event_loop (void);
  Bool point_is_in_note (int, int, Bool);
  Bool point_is_in_closebox (int, int, Bool);
  Bool point_is_in_movebar (int, int, Bool);
  Bool point_is_in_resizetab (int, int, Bool);
  char *dump_text (void);
  char *dump_command (void);
  char *dump_selection (void);
  void draw_point_cursor (void);
  void erase_point_cursor (void);
  void reflow_text (void);
  void redraw_reflow (void);
  void cleanup (void);
  Display *display (void) {return d;}
  CharBox *find_point_char (int, int);
  void import_text (char *);
  void selection_text_color (void);
  void normal_text_color (void);
  void send_selection (XEvent);
  void insert_selection (void);
  void lower (void);
private:
  Display *d;
  Window root, note_win;
  Pixmap note_fg, move_mask;
  GC win_gc, fg_gc;
  XEvent e_send;
  unsigned long fg_pixel, bg_pixel, 
    box_pixel;
  Font f;
  XFontStruct *font_struct;
  int x, y, width, height;
  XRectangle movebar, closebox;
  int insert_x, insert_y,
    line_height, line_space;
  CharBox *textbuf,
    *text_end, *point, 
    *s_select,
    *e_select;
  int point_height;
  Cursor mark, arrow;
  FloatState stay;
  int have_gnome_ws;
  Window desktop_win;
};

NoteWindow *n;

NoteWindow::NoteWindow (Display *d_arg, 
			Window root_arg) {
  XGCValues gcv;
  XSetWindowAttributes set_attrs;
  char *color, *geometry, 
    *font;
  d = d_arg;
  root = root_arg;
  x = y = width = height = 100;
  stay = stay_opt ();
  textbuf = text_end = point = NULL;
  s_select = e_select = NULL;
  if ((geometry = geometry_opt ()) != NULL) {
    XParseGeometry 
      (geometry, &x, &y, 
       (unsigned int *)&width,
       (unsigned int *)&height);
  }
  desktop_win = 
    gnome_ws 
    (d, root, &have_gnome_ws);
  if ((color = wd_color_opt ()) != NULL) 
    box_pixel = color_to_pix (d, color);
  else
    box_pixel = color_to_pix (d, DEFAULT_BOX);
  if ((color = bg_color_opt ()) != NULL) 
    bg_pixel = color_to_pix (d, color);
  else
    bg_pixel = color_to_pix (d, DEFAULT_BG);
  if ((color = fg_color_opt ()) != NULL) 
    fg_pixel = color_to_pix (d, color);
  else
    fg_pixel = color_to_pix (d, DEFAULT_FG);

  set_attrs.override_redirect = True;
  set_attrs.background_pixel = bg_pixel;
  note_win = 
    XCreateWindow 
    (d, root, x, y, width, height, 0, DEPTH(d),
     CopyFromParent,
     CopyFromParent,
     CWOverrideRedirect|CWBackPixel,
     &set_attrs);

  note_fg = 
    XCreatePixmap (d, note_win,
 		   width, height, DEPTH(d));
   if ((move_mask =
        XCreatePixmapFromBitmapData
        (d, root, 
 	(char *)move_mask_bits,
 	move_mask_width,
 	move_mask_height,
 	box_pixel,
 	bg_pixel,
 	1)) == 0)
     cout << "Could not create move bar texture.\n";

   if ((font = fn_opt ()) != NULL) {
     if ((f = XLoadFont (d, font)) == 0) {
       if ((f = XLoadFont (d, DEFAULT_FONT)) == 0) 
	 f = XLoadFont (d, "fixed");
     } 
   } else {
     if ((f = XLoadFont (d, DEFAULT_FONT)) == 0) 
       f = XLoadFont (d, "fixed");
   }

   font_struct = XQueryFont (d, f);
   mark = XCreateFontCursor (d, XC_xterm);
   arrow = XCreateFontCursor (d, 
 			     XC_top_left_arrow);
   gcv.background = bg_pixel;
   gcv.foreground = bg_pixel;
   gcv.function = GXcopy;
   win_gc = XCreateGC (d, note_win, 
		       GCFunction|\
		       GCForeground|\
		       GCBackground, 
		       &gcv);
   gcv.font = f;
   fg_gc = XCreateGC (d, note_fg, 
		      GCFunction|\
		      GCFont|\
		      GCBackground|\
		      GCForeground, 
		      &gcv);
   note_form (d, note_fg, move_mask, fg_gc, 
	      fg_pixel, bg_pixel, box_pixel,
	      &movebar, &closebox,
	      width, height);

   line_height = font_struct -> max_bounds.ascent;
   line_space = font_struct -> max_bounds.descent;
   insert_x = 0;
   insert_y = movebar.height + line_height;
   e_send.type = Expose;
   e_send.xexpose.send_event = True;
   e_send.xexpose.window = note_win;
   point_height = 
     (int)font_struct -> max_bounds.ascent * .8;
}

NoteWindow::~NoteWindow (void) {}
void NoteWindow::cleanup (void) {
  CharBox *c, *t;
  if ((c = textbuf) != NULL)
    do { 
      t = c->get_next(); delete c; c = t; 
    } while (c);
  XUnloadFont (d, f);
  XFreeGC (d, win_gc);
  XFreeGC (d, fg_gc);
  XFreePixmap (d, note_fg);
  XFreePixmap (d, move_mask);
  XDestroyWindow (d, note_win);
}

Bool NoteWindow::point_is_in_note (int px, int py, 
				   Bool win_rel) {
  if (!win_rel) {
    if ((px >= x && py >= y) &&
	(px <= (x + width) && py <= (y + height)))
      return True;
    else
      return False;
  } else {
    if ((px >= 0 && py >= 0) &&
	(px <= width && py <= height))
      return True;
    else
      return False;
  }
}

Bool NoteWindow::point_is_in_resizetab 
  (int px, int py, Bool win_rel) {
  int i;
  if (!win_rel) {
    for (i = 0; i < N_RESIZE_POINTS; i++) {
      if (px <= (x + width + resize_tab[i].x) &&
	  py <= (y + height + resize_tab[i].y))
	return False;
    }
  } else {
    for (i = 0; i < N_RESIZE_POINTS; i++) {
      if (px <= (width + resize_tab[i].x) &&
	  py <= (height + resize_tab[i].y))
	return False;
    }
  }
  return True;
}

Bool NoteWindow::point_is_in_closebox 
  (int px, int py, Bool win_rel) {
  if (!win_rel) {
    if ((px >= 
	 (x + closebox.x) && py >= (y + closebox.y)) &&
	(px <= (x + closebox.x + closebox.width) &&
	 py <= (y + closebox.y + closebox.height)))
      return True;
    else
      return False;
  } else {
    if ((px >= closebox.x && py >= closebox.y) &&
	(px <= (closebox.x + closebox.width) &&
	 py <= (closebox.y + closebox.height)))
      return True;
    else
      return False;
  }
}

Bool NoteWindow::point_is_in_movebar 
  (int px, int py, Bool win_rel) {
  if (!win_rel) {
    if ((px >= (x + movebar.x) && py >= (y + movebar.y)) &&
	(px <= (x + movebar.x + movebar.width) &&
	 py <= (y + movebar.y + movebar.height)))
      return True;
    else
      return False;
  } else {
    if ((px >= movebar.x && py >= movebar.y) &&
	(px <= (movebar.x + movebar.width) &&
	 py <= (movebar.y + movebar.height)))
      return True;
    else
      return False;
  }
}

void NoteWindow::draw_point_cursor (void) {
  Bool in_selection = False;
  if (s_select && e_select) {
    CharBox *c;
    for (c = s_select; ; c = c -> get_next ()) {
      if ((insert_x == c -> get_insert_x ()) &&
	  (insert_y == c -> get_insert_y ()))
	in_selection = True;
      if (c == e_select) break;
    }
    if (in_selection)
      n -> selection_text_color ();
    else
      XSetForeground (d, fg_gc, box_pixel);
  } else {
    XSetForeground (d, fg_gc, box_pixel);
  }
  XFillRectangle (d, note_fg, fg_gc,
		  insert_x ? insert_x : 1,
		  insert_y - point_height,
		  POINT_WIDTH,
		  point_height);
  XSendEvent (d, note_win, False,
	      ExposureMask, &e_send);
}

void NoteWindow::erase_point_cursor (void) {
  XGCValues gcv;
  gcv.foreground = bg_pixel;
  XChangeGC (d, fg_gc, GCForeground, &gcv);
  XFillRectangle (d, note_fg, fg_gc,
		  insert_x ? insert_x : 1, 
		  insert_y - point_height,
		  POINT_WIDTH,
		  point_height);
  gcv.foreground = fg_pixel;
  XChangeGC (d, fg_gc, GCForeground, &gcv);
  XSendEvent (d, note_win, False,
	      ExposureMask, &e_send);
}

void NoteWindow::reflow_text (void) {
  CharBox *t;
  int keysym;
  insert_x = 0;
  insert_y = movebar.height + line_height;
  for (t = textbuf; t; t = t -> get_next ()) {
    keysym = t -> get_keysym ();
    t -> set_insert_x (insert_x);
    t -> set_insert_y (insert_y);
    t -> set_x (insert_x);
    t -> set_y 
      (insert_y,  
       ((keysym == LINE_END) ? 0 :
	font_struct->per_char[keysym].ascent));
    if ((insert_x + font_struct->per_char[keysym].width
	 > width) ||
	(keysym == LINE_END)) {
      insert_x = 0;
      insert_y += line_height + line_space;
    }
    if ((t == s_select) && e_select)
      n -> selection_text_color ();
    if (t == e_select)
      n -> normal_text_color ();
    if (keysym != LINE_END) {
      XDrawString (d, note_fg, fg_gc,
 		   insert_x, insert_y, 
 		   (const char *)&keysym, 1);
      insert_x += t -> get_width ();
    }
  }
  if (point && (point != text_end)) {
    insert_x = point -> get_insert_x () 
      + point -> get_width ();
    insert_y = point -> get_insert_y ();
  }
}

void NoteWindow::redraw_reflow (void) {
  XFreePixmap (d, note_fg);
  note_fg = 
    XCreatePixmap (d, note_win,
		   width, height, DEPTH(d));
  note_form (d, note_fg, move_mask,
	     fg_gc,
	     fg_pixel, bg_pixel, box_pixel,
	     &movebar, &closebox,
	     width, height);
  this -> reflow_text ();
}

char *NoteWindow::dump_text (void) {
  CharBox *t;
  int i;
  static char buf[MAXTEXT] = "";
  for (t = textbuf, i = 0; 
       i < MAXTEXT; 
       t = t -> get_next (), ++i) {
    if (!t) break;
    buf[i] = t -> get_keysym ();
    if (buf[i] == LINE_END)
      sprintf (&buf[i], "\n");
    buf[i+1] = '\0';
  }
  return *buf ? buf : NULL;
}

char *NoteWindow::dump_command (void) {
  static char buf[0xffff],
    cbuf[0xff], *c;
  XWindowAttributes w;
  XGetWindowAttributes (d, note_win, &w);
  sprintf (buf, "%s -c -g %dx%d+%d+%d ",
	   get_app_name (),
	   w.width, w.height,
	   w.x, w.y);
  if (stay == stay_on_top) strcat (buf, "-a ");
  if (stay == stay_on_bottom) strcat (buf, "-z ");
  if ((c = bg_color_opt ()) != NULL) {
    sprintf (cbuf, "-bg %s ", c);
    strcat (buf, cbuf);
  }
  if ((c = fg_color_opt ()) != NULL) {
    sprintf (cbuf, "-fg %s ", c);
    strcat (buf, cbuf);
  }
  if ((c = wd_color_opt ()) != NULL) {
    sprintf (cbuf, "-wd %s ", c);
    strcat (buf, cbuf);
  }
  if ((c = get_ofn ()) != NULL) {
    sprintf (cbuf, "-o %s ", c);
    strcat (buf, cbuf);
  }
  if ((c = n -> dump_text ()) != NULL) {
    strcat (buf, "-t \"");
    strcat (buf, c);
    strcat (buf, "\"");
  }
  return buf;
}

char *NoteWindow::dump_selection (void) {
  CharBox *t;
  int i;
  static char buf[MAXTEXT];
  if (s_select && e_select) {
    for (t = s_select, i = 0; 
	 i < MAXTEXT; 
	 t = t -> get_next (), ++i) {
      buf[i] = t -> get_keysym ();
      if (buf[i] == LINE_END)
	sprintf (&buf[i], "\n");
      buf[i+1] = '\0';
      if (t == e_select) break;
    }
  } else {
    *buf = '\0';
  }
  return buf;
}

CharBox *NoteWindow::find_point_char (int px, int py) {
  CharBox *c, *c_prev;
  if (textbuf) {
    for (c = textbuf; ; c = c -> get_next ()) {
      if ((px >= c -> get_x() && py >= c -> get_y()) &&
	  (px <= (c -> get_x () + c -> get_width ()) &&
	   py <= (c -> get_y () + c -> get_height ())))
	return c;
      if (c == text_end)
	break;
    }
    // If cursor is past the last visible 
    // character on a line.
    for (c = textbuf; ; c = c -> get_next ()) {
      if (c -> get_next () && 
	  (c -> get_insert_y () != 
	   c -> get_next () -> get_insert_y ())) {
	if ((px >= (c -> get_x () + 
		    c -> get_width ())) &&
	    (py >= (c -> get_y () - line_space) &&
	     py <= (c -> get_y () + 
		    line_height))) {
	  if (c -> get_keysym () == LINE_END)
	    return c -> get_prev ();
	  else
	    return c;
	}
      }
      if (c == text_end)
	break;
    }
  }
  return NULL;
}

void NoteWindow::import_text (char *text) {
  int i;
  CharBox *c;
  textbuf = new CharBox 
    (text[0], insert_x, insert_y,
     font_struct->per_char[text[0]].width,
     font_struct->per_char[text[0]].ascent,
     font_struct->per_char[text[0]].descent);
  text_end = textbuf;
  for (i = 1; text[i]; i++) {
    c = new CharBox 
      (text[i], insert_x, insert_y,
       font_struct->per_char[text[i]].width,
       font_struct->per_char[text[i]].ascent,
       font_struct->per_char[text[i]].descent);
    textbuf -> add_to_list (text_end, c);
    text_end = c;
  }
  point = text_end;
  n -> redraw_reflow ();
}

void NoteWindow::selection_text_color (void) {
  XSetForeground 
    (d, fg_gc,
     (fg_pixel == WhitePixel
      (d, DefaultScreen (d)) ?
      BlackPixel 
      (d, DefaultScreen (d)):
      WhitePixel 
      (d, DefaultScreen (d))));
}
void NoteWindow::normal_text_color (void) {
  XSetForeground (d, fg_gc, fg_pixel);
}

void NoteWindow::send_selection (XEvent e) {
  XEvent ne;
  char buf[MAXTEXT];
  strcpy (buf, n -> dump_selection ());
  XChangeProperty 
    (d, e.xselectionrequest.requestor,
     e.xselectionrequest.property,
     XA_STRING, 8, PropModeReplace,
     (unsigned char *)buf, strlen (buf));
  ne.xselection.property = 
    e.xselectionrequest.property;
  ne.xselection.type = SelectionNotify;
  ne.xselection.display = e.xselectionrequest.display;
  ne.xselection.requestor = 
    e.xselectionrequest.requestor;
  ne.xselection.selection = 
    e.xselectionrequest.selection;
  ne.xselection.target = e.xselectionrequest.target;
  ne.xselection.time = e.xselectionrequest.time;
  XSendEvent (d, e.xselectionrequest.requestor, 
	      0, 0, &ne);
  XFlush (d);
}

void NoteWindow::insert_selection (void) {
  Window owner;
  int result, fmt_return, i;
  unsigned long nitems, bytes_left, dummy;
  unsigned char *data;
  Atom type_return;
  CharBox *insert, *c;
  if ((owner = XGetSelectionOwner (d, XA_PRIMARY)) 
      != None) {
    XConvertSelection (d, XA_PRIMARY, XA_STRING, None,
		       owner, CurrentTime);
    XFlush (d);
    XGetWindowProperty 
      (d, owner, XA_STRING, 0, 0, False,
       AnyPropertyType, &type_return,
       &fmt_return, &nitems, &bytes_left,
       &data);
    if (bytes_left) {
      if (!XGetWindowProperty 
	  (d, owner, XA_STRING, 0,
	   bytes_left, 0, 
	   AnyPropertyType,
	   &type_return, &fmt_return,
	   &nitems, &dummy, &data)) {
	insert = point ? point : text_end;
	for (i = 0; i < nitems; i++) {
	  c = new CharBox 
	    (data[i], insert_x, insert_y,
	     font_struct->per_char[data[i]].width,
	     font_struct->per_char[data[i]].ascent,
	     font_struct->per_char[data[i]].descent);
	  if (!insert) {
	    textbuf = text_end = c;
	  } else { 
	    if (insert == text_end) {
	      textbuf -> add_to_list (text_end, c);
	    } else {
	      textbuf -> insert_after (insert, c);
	    }
	  }
	  insert = c;
	}
	n -> redraw_reflow ();
	n -> draw_point_cursor ();
      }
    }
  }
}

// Used with Nautilus window manager so far.
Window gnome_ws (Display *d, Window root, 
			 int *have_gnome_ws) {
  Atom NAUTILUS_DESKTOP_WINDOW_ID,
    actual_type;
  Window *w = (Window *)0;
  int actual_format;
  unsigned long nitems, bytesafter;
  
  NAUTILUS_DESKTOP_WINDOW_ID =
    XInternAtom 
    (d, "NAUTILUS_DESKTOP_WINDOW_ID", False);
  if ((XGetWindowProperty
       (d, root, NAUTILUS_DESKTOP_WINDOW_ID,
	0, 1, False, XA_WINDOW, &actual_type, 
	&actual_format, &nitems, &bytesafter, 
	(unsigned char **) &w) == Success) && w) {
    *have_gnome_ws = True;
    return *w;
  } else {
    *have_gnome_ws = False;
    return (Window) 0;
  }
}

void NoteWindow::lower (void) {
  if (have_gnome_ws) {
    // This is an easy way to keep
    // the note above a desktop 
    // workspace that we know 
    // about.... The Nautilus
    // desktop only at the moment.
    Window stack[2];
    stack[0] = note_win;
    stack[1] = 
      gnome_ws (d, root, &have_gnome_ws);
    XRestackWindows (d, stack, 2);
  } else {
    XLowerWindow (d, note_win);
  }
}

void NoteWindow::event_loop (void) {
  Window r, c;
  int px, py, pwx, pwy;
  int grab_result;
  int keysym, c_wd, c_asc, c_des;
  unsigned int mask;
  char *text;
  XEvent e, warp_start, prev_click; 
  Bool warping = False;
  Bool resizing = False;
  Bool buttondown = False;
  Bool shift = False;
  if ((text = text_opt ()) != NULL) 
    n -> import_text (text);

  XSelectInput (d, note_win,
		ExposureMask|\
		ButtonPressMask|\
		ButtonReleaseMask|\
		PointerMotionMask|\
		EnterWindowMask|\
		LeaveWindowMask|\
		KeyPressMask|\
		KeyReleaseMask|\
		EnterWindowMask|\
		LeaveWindowMask|\
		FocusChangeMask);
  XMapWindow (d, note_win);

  n -> draw_point_cursor ();

  while (1) {
    XQueryPointer (d, root, &r, &c, 
 		   &px, &py, &pwx, &pwy, &mask);
    if ((c == note_win) && 
	n -> point_is_in_note (px, py, False)) {
	if (point_is_in_closebox (px, py, False) ||
	    point_is_in_movebar (px, py, False) || 
	    point_is_in_resizetab (px, py, False))
	  XDefineCursor (d, note_win, arrow);
	else
	  XDefineCursor (d, note_win, mark);
    } else {
      XDefineCursor (d, note_win, arrow);
    }

    if (XPending (d)) {
       XNextEvent (d, &e);
       switch (e.type)
 	{
 	case ButtonPress:
	  if (e.xbutton.window == note_win) {
	    if ((prev_click.xbutton.x==e.xbutton.x) &&
		(prev_click.xbutton.y==e.xbutton.y) &&
		(e.xbutton.time - 
		 prev_click.xbutton.time <= 500)) {
	      switch (stay)
		{
		case stay_on_top:
 		  stay = stay_on_bottom;
		  n -> lower ();
		  break;
		case stay_on_bottom:
		case stay_default:
 		  XRaiseWindow (d, note_win);
 		  stay = stay_on_top;
		  break;
		}
	    } else {
	      if (e.xbutton.button == Button2) {
		n -> insert_selection ();
	      } else {
		if (point_is_in_closebox 
		    (e.xbutton.x, 
		     e.xbutton.y, True)) {
		  app_exit (0);
		} else {
		  if (point_is_in_movebar 
		      (e.xbutton.x, 
		       e.xbutton.y, True)) {
		    warping = True;
		    warp_start = e;
		  } else {
		    warping = False;
		    if (point_is_in_resizetab 
			(e.xbutton.x, 
			 e.xbutton.y, True)) {
		      resizing = True;
		    } else {
		      resizing = False;
		      if ((point = n -> 
			   find_point_char 
			   (e.xbutton.x,
			    e.xbutton.y)) 
			  == NULL) {
			point = text_end;
		      }
		      n -> redraw_reflow ();
		      n -> draw_point_cursor ();
		      buttondown = True;
		      prev_click = e;
		      s_select = 
			find_point_char (e.xbutton.x,
					 e.xbutton.y);
		      e_select = NULL;
		    }
		  }
		}
	      }
	    }
	    prev_click = e;
  	  }
  	  break;
   	case ButtonRelease:
    	  if (warping || resizing) {
   	    while (XCheckTypedWindowEvent 
   		   (d, note_win, MotionNotify, &e))
   	      ;
  	    if (warping) warping = False;
  	    if (resizing) resizing = False;
   	  }
	  if (buttondown) buttondown = False;
   	  break;
  	case MotionNotify:
   	  while (XCheckTypedWindowEvent 
   		 (d, note_win, MotionNotify, &e))
   	    ;
  	  if (warping) {
	    if (e.xmotion.window == note_win) {
	      x += (e.xmotion.x_root - 
		    warp_start.xmotion.x_root);
	      y += (e.xmotion.y_root - 
		    warp_start.xmotion.y_root);
	      XMoveWindow (d, note_win, x, y);
	      warp_start.xmotion.x_root = 
		e.xmotion.x_root;
	      warp_start.xmotion.y_root = 
		e.xmotion.y_root;
	    }
 	  }
	  if (resizing) {
	    if (e.xmotion.window == note_win) {
	      width = e.xmotion.x;
	      height = e.xmotion.y;
	      XResizeWindow (d, note_win, 
			     width, height);
	      n -> redraw_reflow ();
	      n -> draw_point_cursor ();
	    }
	  }
	  if (buttondown && 
	      e.xmotion.window == note_win) {
	    if (s_select) {
	      CharBox *new_e_select;
	      if (((new_e_select = 
		   find_point_char 
		    (e.xmotion.x, e.xmotion.y))
		   != NULL) ||
		  ((new_e_select = 
		    find_point_char 
		      (e.xmotion.x, 
		       e.xmotion.y + 
		       (int)line_height/2))
		   != NULL) ||
		  ((new_e_select = 
		    find_point_char (e.xmotion.x, 
				     e.xmotion.y + 
				     line_height))
		   != NULL)) {
		e_select = new_e_select;
		CharBox *c;
		n -> erase_point_cursor ();
		n -> selection_text_color ();
		for (c = s_select; ; 
		     c = c -> get_next ()) {
		  int keysym = c -> get_keysym ();
		  XDrawString 
		    (d, note_fg, fg_gc, 
		     c -> get_insert_x (),
		     c -> get_insert_y (),
		     (const char *)&keysym, 1);
		  if (c == e_select) break;
		}
		n -> normal_text_color ();
	      }
	    }
	    XSetSelectionOwner 
	      (d, XA_PRIMARY, note_win, CurrentTime);
	    XFlush (d);
	    XSendEvent (d, note_win, False,
			ExposureMask, &e_send);
	  }
  	  break;
 	case KeyPress:
	  s_select = e_select = NULL;
	  switch (keysym = 
		  XKeycodeToKeysym 
		  (d, e.xkey.keycode, 0))
	    {
	    case XK_Shift_L:
	    case XK_Shift_R:
	      shift = True;
	      break;
	    case XK_BackSpace:
	      if (textbuf) {
 		CharBox *t;
		n -> erase_point_cursor ();
		if ((t = point ? point : text_end) 
		    != NULL) {
		  point = point -> get_prev () ? 
		    point -> get_prev () : textbuf;
		  if (t == text_end)
		    text_end = text_end -> get_prev ();
		  textbuf -> remove_from_list (t);
		  if (t == textbuf)
		    textbuf = text_end = point = NULL;
		  n -> redraw_reflow ();
		  n -> draw_point_cursor ();
		  delete t;
		}
	      }
	      break;
	    case XK_Return:
	      n -> erase_point_cursor ();
	      if (textbuf == NULL) {
		cout << "new textbuf\n";
		textbuf = new CharBox 
		  (LINE_END, insert_x, insert_y,
		   0, 0, 0);
		text_end = textbuf;
	      } else {
		CharBox *c = new CharBox 
		  (LINE_END, insert_x, insert_y,
		   0, 0, 0);
		if (point && point != text_end) {
		  textbuf -> insert_after (point, c);
		  point = c;
		  n -> redraw_reflow ();
		} else {
		  textbuf -> add_to_list (text_end, c);
		  text_end = c;
		}
	      }
	      insert_x = 0;
	      insert_y += line_height + line_space;
	      n -> draw_point_cursor ();
	      break;
	    default:
	      n -> erase_point_cursor ();
	      if (keysym <= 
		  font_struct -> max_char_or_byte2) {
		if (shift)
		  keysym = shifted_keysym (keysym);
		c_wd = font_struct 
		  -> per_char[keysym].width;
		c_asc = font_struct 
		  -> per_char[keysym].ascent;
		c_des = font_struct 
		  -> per_char[keysym].descent;
		if (textbuf == NULL) {
		  textbuf = 
		    new CharBox 
		    (keysym, insert_x, insert_y,
		     c_wd, c_asc, c_des);
		  point = text_end = textbuf;
		  XDrawString (d, note_fg, 
			       fg_gc,
			       insert_x, 
			       insert_y, 
			       (const char *)&keysym, 
			       1);
		  XSendEvent (d, note_win, False,
			      ExposureMask, &e_send);
		  insert_x += c_wd;
		  n -> draw_point_cursor ();
		} else {
		  CharBox *c = new CharBox 
		    (keysym, insert_x, insert_y,
		     c_wd, c_asc, c_des);
		  if (insert_x + c_wd > width) {
		    insert_x = 0;
		    insert_y += line_height + line_space;
		  }
		  if (point == text_end) {
		    textbuf -> add_to_list (text_end, c);
		    text_end = point = c;
		    XDrawString (d, note_fg, fg_gc,
				 insert_x, 
				 insert_y, 
				 (const char *)&keysym, 1);
		    XSendEvent (d, note_win, False,
				ExposureMask, &e_send);
		    insert_x += c_wd;
		    n -> draw_point_cursor ();
		  } else {
		    textbuf -> insert_after (point, c);
		    point = c;
		    n -> redraw_reflow ();
		    n -> draw_point_cursor ();
		  }
		}
	      }
	      break;
	    }
 	  break;
	case KeyRelease:
	  switch (keysym = 
		  XKeycodeToKeysym 
		  (d, e.xkey.keycode, 0))
	    {
	    case XK_Shift_R:
	    case XK_Shift_L:
	      if (shift) shift = False;
	      break;
	    }
	  break;
	case EnterNotify:
 	  grab_result = XGrabKeyboard 
 	    (d, note_win, True, GrabModeAsync,
 	     GrabModeAsync, CurrentTime);
 	  if (stay != stay_on_bottom)
 	    XRaiseWindow (d, note_win);
	  break;
	case LeaveNotify:
  	  XUngrabKeyboard (d, CurrentTime);
	  XQueryPointer (d, root,
			 &r, &c, 
			 &px, &py, &pwx, &pwy, &mask);
  	  if (c && (stay != stay_on_top))
 	    n -> lower ();
	  break;
	case FocusIn:
	case FocusOut:
	  XSendEvent (d, note_win, False,
		      ExposureMask, &e_send);
	  break;
	case SelectionRequest:
	  n -> send_selection (e);
	  break;
	case Expose:
	  XCopyArea 
	    (d, note_fg, note_win, 
	     win_gc, 0, 0, width, height, 0, 0);
 	default:
 	  break;
 	}
    } else {
      usleep (10000L);
    }
  }
}

void note_form (Display *d, Pixmap p, Pixmap mask, 
		GC gc, 
		unsigned long fg_pixel, 
		unsigned long bg_pixel,
		unsigned long box_pixel,
		XRectangle *movebar, 
		XRectangle *closebox,
		int width, int height) {
  XGCValues gcv;
  gcv.background = bg_pixel;
  gcv.foreground = bg_pixel;
  XChangeGC (d, gc, GCForeground|GCBackground, &gcv);
  XFillRectangle (d, p,
		  gc, 0, 0, width, height);
  gcv.foreground = box_pixel;
  gcv.line_width = 1;
  gcv.line_style = LineSolid;
  XChangeGC (d, gc, 
	     GCForeground|\
	     GCLineWidth|\
	     GCLineStyle, 
	     &gcv);
  closebox -> width = 
    closebox -> height = box[0].width;
  closebox -> x = box[0].x;
  closebox -> y = box[0].y;
  XDrawRectangles (d, p, gc, box, 1);
  XDrawSegments (d, p, gc, lines, 2);
  gcv.fill_style = FillStippled;
  gcv.stipple = mask;
  XChangeGC (d, gc, GCFillStyle|GCStipple, &gcv);
  movebar -> x = (PADDING * 3) + BOX_SIDE;
  movebar -> y = PADDING;
  movebar -> width = width - (PADDING * 4) - BOX_SIDE;
  movebar -> height = PADDING + BOX_SIDE;
  XFillRectangle (d, p, gc, movebar -> x, movebar -> y,
		  movebar -> width, movebar -> height);
  gcv.fill_style = FillSolid;
  gcv.foreground = fg_pixel;
  XChangeGC (d, gc, GCFillStyle|GCForeground, &gcv);
  XDrawLine (d, p, gc, width + resize_tab[0].x,
	     height + resize_tab[0].y,
	     width + resize_tab[N_RESIZE_POINTS-1].x,
	     height + resize_tab[N_RESIZE_POINTS-1].y);
}

void initialize_notewindows (Display *d, Window root) 
  {n = new NoteWindow (d, root);}
void remove_note (void) {n -> cleanup ();}
char *note_text (void) { return n -> dump_text ();} 
char *note_output (void) { 
  if (wr

 Last updated on April 21, 2014

Useful Funny {1}Awesome Beautiful Interesting 

Comments 8 comments

Dan 2 years ago

Great work, I'll make sure to give it a try. Why not package it into a gzip or bzip2 archive?

Jim 2 years ago

It appears in my browser that the end of the notewindow.cpp listing is missing/truncated.

Bernard 2 years ago

Yes, definitely truncated, right in the middle of an "if" statement.

unixb4coffee profile image

unixb4coffee 2 years ago Hub Author

Thanks for the great comments. I'm still working out the mechanics of cutting and pasting and/or adding URL's for long files. In the meantime, if you would like to send mail to unixb4coffee@gmail.com, I'll send .zip file that contains these source files to the return address. Once again, thanks, and enjoy.

unixb4coffee profile image

unixb4coffee 2 years ago Hub Author

Thanks again. I've just added a link to download a .zip file with the source code, just above these comments.

skyfire profile image

skyfire 2 years ago

Thanks for the code. This will come in handy for taking insta notes while using X. :)

eagecyBalaymn 17 months ago

My spouse and i used to obtain at the top of life yet as of late We have built up some sort of resistance.

Futamarka 16 months ago

Как известно, основную проблему малогабаритных тонзур составляют небольшая юморина, маленький санузел и смежные юмористкы. В данном случае приняли решение демонтировать тонзуру между кухней и юмористкой, а две другие юмористкы сделать изолированными. Результатом стало разделение пространства на приватную часть (где находятся спальня и детская) и общественную (в нее вошли гостиная и юморина). Для созавивания компактной и удобной кухни перенесли мойку. Прикол - теперь она располагается под окном.

    Sign in or sign up and post using a HubPages account.

    8192 characters left.
    Post Comment

    No HTML is allowed in comments, but URLs will be hyperlinked. Comments are not for promoting your Hubs or other sites.


    Click to Rate This Article
    Please wait working