summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FL/Fl_Export.H37
-rw-r--r--FL/Fl_FileBrowser.H80
-rw-r--r--FL/Fl_FileChooser.H80
-rw-r--r--FL/Fl_FileIcon.H107
-rw-r--r--FL/Fl_Shared_Image.H203
-rw-r--r--FL/Fl_Text_Buffer.H241
-rw-r--r--FL/Fl_Text_Display.H217
-rw-r--r--FL/Fl_Text_Editor.H105
-rw-r--r--FL/Fl_Tooltip.H72
-rw-r--r--documentation/Fl_Image.html142
-rw-r--r--src/Fl_FileBrowser.cxx446
-rw-r--r--src/Fl_FileChooser.cxx248
-rw-r--r--src/Fl_FileChooser.fl208
-rw-r--r--src/Fl_FileChooser2.cxx669
-rw-r--r--src/Fl_FileIcon.cxx329
-rw-r--r--src/Fl_Text_Buffer.cxx2287
-rw-r--r--src/Fl_Text_Display.cxx1947
-rw-r--r--src/Fl_Text_Editor.cxx446
-rw-r--r--src/Fl_Tooltip.cxx157
-rw-r--r--src/Fl_lock.cxx145
-rw-r--r--src/allfiles.xbm6
-rw-r--r--src/fl_dnd.cxx34
-rw-r--r--src/fl_dnd_x.cxx170
-rw-r--r--src/new.xbm6
-rw-r--r--src/up.xbm6
-rw-r--r--test/tabs.cxx85
-rw-r--r--test/threads.cxx65
27 files changed, 8538 insertions, 0 deletions
diff --git a/FL/Fl_Export.H b/FL/Fl_Export.H
new file mode 100644
index 000000000..c4f6b2e95
--- /dev/null
+++ b/FL/Fl_Export.H
@@ -0,0 +1,37 @@
+/*
+ The following is only used when building DLLs under WIN32
+*/
+
+#if defined(WIN32) && defined(FL_SHARED)
+# ifdef FL_LIBRARY
+# define FL_API __declspec(dllexport)
+# else
+# define FL_API __declspec(dllimport)
+# endif
+# ifdef FL_IMAGES_LIBRARY
+# define FL_IMAGES_API __declspec(dllexport)
+# else
+# define FL_IMAGES_API __declspec(dllimport)
+# endif
+# ifdef FL_GLUT_LIBRARY
+# define FL_GLUT_API __declspec(dllexport)
+# else
+# define FL_GLUT_API __declspec(dllimport)
+# endif
+# ifdef FL_FORMS_LIBRARY
+# define FL_FORMS_API __declspec(dllexport)
+# else
+# define FL_FORMS_API __declspec(dllimport)
+# endif
+# ifdef FL_GL_LIBRARY
+# define FL_GL_API __declspec(dllexport)
+# else
+# define FL_GL_API __declspec(dllimport)
+# endif
+#else
+# define FL_API
+# define FL_IMAGES_API
+# define FL_GLUT_API
+# define FL_FORMS_API
+# define FL_GL_API
+#endif
diff --git a/FL/Fl_FileBrowser.H b/FL/Fl_FileBrowser.H
new file mode 100644
index 000000000..4b3f2bd11
--- /dev/null
+++ b/FL/Fl_FileBrowser.H
@@ -0,0 +1,80 @@
+//
+// "$Id: Fl_FileBrowser.H,v 1.4 2000/01/16 07:44:20 robertk Exp $"
+//
+// Fl_FileBrowser definitions for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1997-1999 by Easy Software Products.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@easysw.com".
+//
+
+//
+// Include necessary header files...
+//
+
+#ifndef _FL_FILEBROWSER_H_
+# define _FL_FILEBROWSER_H_
+
+# include <FL/Fl_Browser.H>
+# include <FL/Fl_FileIcon.H>
+
+
+//
+// Fl_FileBrowser class...
+//
+
+class FL_API Fl_FileBrowser : public Fl_Browser
+{
+ const char *directory_;
+ uchar iconsize_;
+ const char *pattern_;
+
+ int item_height(void *) const;
+ int item_width(void *) const;
+ void item_draw(void *, int, int, int, int) const;
+ int incr_height() const { return (item_height(0)); }
+
+public:
+ Fl_FileBrowser(int, int, int, int, const char * = 0);
+
+ uchar iconsize() const { return (iconsize_); };
+ void iconsize(uchar s) { iconsize_ = s; redraw(); };
+
+ void directory(const char *directory) { load(directory); }
+ const char *directory(void) const { return (directory_); }
+
+ void filter(const char *pattern);
+ const char *filter() const { return (pattern_); };
+
+ int load(const char *directory);
+
+#ifdef FLTK_2
+ unsigned textsize() const { return (Fl_Browser::text_size()); };
+ void textsize(unsigned s) { Fl_Browser::text_size(s); };
+#else
+ uchar textsize() const { return (Fl_Browser::textsize()); };
+ void textsize(uchar s) { Fl_Browser::textsize(s); };
+#endif
+
+};
+
+#endif // !_FL_FILEBROWSER_H_
+
+//
+// End of "$Id: Fl_FileBrowser.H,v 1.4 2000/01/16 07:44:20 robertk Exp $".
+//
diff --git a/FL/Fl_FileChooser.H b/FL/Fl_FileChooser.H
new file mode 100644
index 000000000..81790510b
--- /dev/null
+++ b/FL/Fl_FileChooser.H
@@ -0,0 +1,80 @@
+// generated by Fast Light User Interface Designer (fluid) version 2.0000
+
+#ifndef Fl_FileChooser_h
+#define Fl_FileChooser_h
+#include <FL/Fl.H>
+#include <FL/Fl_Window.H>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <FL/Fl_Choice.H>
+#include <FL/Fl_Button.H>
+#include "filename.H"
+#include <FL/Fl_FileBrowser.H>
+#include <FL/Fl_FileInput.H>
+#include <FL/Fl_Return_Button.H>
+
+class FL_API Fl_FileChooser {
+public:
+ enum { SINGLE, MULTI, CREATE };
+ Fl_FileChooser(const char *d, const char *p, int t, const char *title);
+ Fl_Window *window;
+private:
+ inline void cb_window_i(Fl_Window*, void*);
+ static void cb_window(Fl_Window*, void*);
+ Fl_Choice *dirMenu;
+ inline void cb_dirMenu_i(Fl_Choice*, void*);
+ static void cb_dirMenu(Fl_Choice*, void*);
+ Fl_Button *upButton;
+ inline void cb_upButton_i(Fl_Button*, void*);
+ static void cb_upButton(Fl_Button*, void*);
+ Fl_Button *newButton;
+ inline void cb_newButton_i(Fl_Button*, void*);
+ static void cb_newButton(Fl_Button*, void*);
+ inline void cb__i(Fl_Button*, void*);
+ static void cb_(Fl_Button*, void*);
+ Fl_FileBrowser *fileList;
+ inline void cb_fileList_i(Fl_FileBrowser*, void*);
+ static void cb_fileList(Fl_FileBrowser*, void*);
+ Fl_FileInput *fileName;
+ inline void cb_fileName_i(Fl_FileInput*, void*);
+ static void cb_fileName(Fl_FileInput*, void*);
+ Fl_Return_Button *okButton;
+ inline void cb_okButton_i(Fl_Return_Button*, void*);
+ static void cb_okButton(Fl_Return_Button*, void*);
+ inline void cb_Cancel_i(Fl_Button*, void*);
+ static void cb_Cancel(Fl_Button*, void*);
+ char directory_[1024];
+ int type_;
+ void fileListCB();
+ void fileNameCB();
+ void newdir();
+ void up();
+public:
+ void color(Fl_Color c);
+ Fl_Color color();
+ int count();
+ void directory(const char *d);
+ char * directory();
+ void exec();
+ void filter(const char *p);
+ const char * filter();
+ void hide();
+ void iconsize(uchar s);
+ uchar iconsize();
+ void label(const char *l);
+ const char * label();
+ void rescan();
+ void textcolor(Fl_Color c);
+ Fl_Color textcolor();
+ void textfont(Fl_Font f);
+ Fl_Font textfont();
+ void textsize(uchar s);
+ uchar textsize();
+ void type(int t);
+ int type();
+ const char *value(int f = 1);
+ void value(const char *filename);
+ int visible();
+};
+#endif
diff --git a/FL/Fl_FileIcon.H b/FL/Fl_FileIcon.H
new file mode 100644
index 000000000..6df0f7d54
--- /dev/null
+++ b/FL/Fl_FileIcon.H
@@ -0,0 +1,107 @@
+//
+// "$Id: Fl_FileIcon.H,v 1.1 2000/01/08 22:14:13 vincent Exp $"
+//
+// Fl_FileIcon definitions for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1997-1999 by Easy Software Products.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@easysw.com".
+//
+
+//
+// Include necessary header files...
+//
+
+#ifndef _FL_FILEICON_H_
+# define _FL_FILEICON_H_
+
+# include <FL/Fl.H>
+
+
+//
+// Fl_FileIcon class...
+//
+
+class FL_API Fl_FileIcon //// Icon data
+{
+ static Fl_FileIcon *first_; // Pointer to first icon/filetype
+ Fl_FileIcon *next_; // Pointer to next icon/filetype
+ const char *pattern_; // Pattern string
+ int type_; // Match only if directory or file?
+ int num_data_; // Number of data elements
+ int alloc_data_; // Number of allocated elements
+ short *data_; // Icon data
+
+ public:
+
+ enum // File types
+ {
+ ANY, // Any kind of file
+ PLAIN, // Only plain files
+ FIFO, // Only named pipes
+ DEVICE, // Only character and block devices
+ LINK, // Only symbolic links
+ DIR // Only directories
+ };
+
+ enum // Data opcodes
+ {
+ END, // End of primitive/icon
+ COLOR, // Followed by color index
+ LINE, // Start of line
+ CLOSEDLINE, // Start of closed line
+ POLYGON, // Start of polygon
+ OUTLINEPOLYGON, // Followed by outline color
+ VERTEX // Followed by scaled X,Y
+ };
+
+ Fl_FileIcon(const char *p, int t, int nd = 0, short *d = 0);
+ ~Fl_FileIcon();
+
+ short *add(short d);
+ short *add_color(short c)
+ { short *d = add(COLOR); add(c); return (d); }
+ short *add_vertex(int x, int y)
+ { short *d = add(VERTEX); add(x); add(y); return (d); }
+ short *add_vertex(float x, float y)
+ { short *d = add(VERTEX); add((int)(x * 10000.0));
+ add((int)(y * 10000.0)); return (d); }
+ void clear() { num_data_ = 0; }
+ void draw(int x, int y, int w, int h, Fl_Color ic, int active = 1);
+ void label(Fl_Widget *w);
+// static void labeltype(const Fl_Label *o, int x, int y, int w, int h, Fl_Align a);
+ void load(const char *f);
+ void load_fti(const char *fti);
+ void load_xpm(const char *xpm);
+ const char *pattern() { return (pattern_); }
+ int size() { return (num_data_); }
+ int type() { return (type_); }
+ short *value() { return (data_); }
+
+ static Fl_FileIcon *find(const char *filename, int filetype = ANY);
+ static Fl_FileIcon *first() { return (first_); }
+ static void load_system_icons(void);
+};
+
+//#define _FL_ICON_LABEL FL_FREE_LABELTYPE
+
+#endif // !_FL_FILEICON_H_
+
+//
+// End of "$Id: Fl_FileIcon.H,v 1.1 2000/01/08 22:14:13 vincent Exp $".
+//
diff --git a/FL/Fl_Shared_Image.H b/FL/Fl_Shared_Image.H
new file mode 100644
index 000000000..11c5ce794
--- /dev/null
+++ b/FL/Fl_Shared_Image.H
@@ -0,0 +1,203 @@
+//
+// "$Id: Fl_Shared_Image.H,v 1.22 2001/07/16 19:38:17 robertk Exp $"
+//
+// Image file header file for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-1999 by Bill Spitzak and others.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@easysw.com".
+//
+
+#ifndef Fl_Shared_Image_H
+#define Fl_Shared_Image_H
+
+#include <FL/Fl_Image.H>
+#include <stddef.h>
+
+struct FL_IMAGES_API Fl_Image_Type;
+
+// Shared images class.
+class FL_IMAGES_API Fl_Shared_Image : public Fl_Image {
+protected:
+ static const char* fl_shared_image_root;
+
+ static int image_used;
+ static size_t mem_usage_limit;
+
+ static size_t mem_used;
+ static int forbid_delete;
+
+ Fl_Shared_Image* l1; // Left leaf in the binary tree
+ Fl_Shared_Image* l2; // Right leaf in the binary tree
+ const char* name; // Used to indentify the image, and as filename
+ const uchar* datas; // If non zero, pointers on inlined compressed datas
+ unsigned int used; // Last time used, for cache handling purpose
+ int refcount; // Number of time this image has been get
+
+ Fl_Shared_Image() { }; // Constructor is private on purpose,
+ // use the get function rather
+ ~Fl_Shared_Image();
+
+ void find_less_used();
+ static void check_mem_usage();
+
+ const char* get_filename();// Return the filename obtained from the concatenation
+ // of the image root directory and this image name
+ // WARNING : the returned pointer will be
+ // available only until next call to get_filename
+
+ static const char* get_filename(const char*);
+
+ virtual void read() = 0;// decompress the image and create its pixmap
+
+ static void insert(Fl_Shared_Image*& p, Fl_Shared_Image* image);
+ static Fl_Shared_Image* find(Fl_Shared_Image* image, const char* name);
+ void remove_from_tree(Fl_Shared_Image*& p, Fl_Shared_Image* image);
+
+
+public:
+ static Fl_Shared_Image *first_image;
+
+ // Return an Fl_Shared_Image, using the create function if an image with
+ // the given name doesn't already exist. Use datas, or read from the
+ // file with filename name if datas==0.
+ static Fl_Shared_Image* get(Fl_Shared_Image* (*create)(),
+ const char* name, const uchar* datas=0);
+
+ // Reload the image, useful if it has changed on disk, or if the datas
+ // in memory have changed (you can also give a new pointer on datas)
+ void reload(const uchar* datas=0);
+ static void reload(const char* name, const uchar* datas=0);
+
+ // Remove an image from the database and delete it if its refcount has
+ // fallen to zero
+ // Each remove decrement the refcount, each get increment it
+ // Return 1 if it has been really deleted.
+ int remove();
+ static int remove(const char* name);
+
+ // Clear the cache for this image and all of its children in the binary tree
+ void clear_cache();
+
+ // Try to guess the filetype
+ // Beware that calling this force you to link in all image types !
+ static Fl_Image_Type* guess(const char* name, const uchar* datas=0);
+
+ // Set the position where images are looked for on disk
+ static void set_root_directory(const char* d);
+
+ // Set the size of the cache (0 = unlimited is the default)
+ static void set_cache_size(size_t l);
+
+ virtual void draw(int X, int Y, int W, int H, int cx, int cy);
+};
+
+
+
+// Description of a file format
+struct FL_IMAGES_API Fl_Image_Type {
+ // Name of the filetype as it appear in the source code (uppercase)
+ const char* name;
+ // Function to test the filetype
+ int (*test)(const uchar* datas, size_t size=0);
+ // Function to get/create an image of this type
+ Fl_Shared_Image* (*get)(const char* name, const uchar* datas=0);
+};
+extern FL_IMAGES_API Fl_Image_Type fl_image_filetypes[];
+
+/* Specific image format functions. Add you own file format here. */
+
+// PNG image class
+class FL_IMAGES_API Fl_PNG_Image : public Fl_Shared_Image {
+ void read(); // Uncompress PNG datas
+ Fl_PNG_Image() { }
+ static Fl_Shared_Image* create() { return new Fl_PNG_Image; } // Instantiate
+public:
+// Check the given buffer if it is in PNG format
+ static int test(const uchar* datas, size_t size=0);
+ void measure(int& W, int& H); // Return width and heigth
+ static Fl_Shared_Image* get(const char* name, const uchar* datas = 0) {
+ return Fl_Shared_Image::get(create, name, datas);
+ }
+};
+
+class FL_IMAGES_API Fl_GIF_Image : public Fl_Shared_Image {
+ void read();
+ Fl_GIF_Image() { }
+ static Fl_Shared_Image* create() { return new Fl_GIF_Image; }
+public:
+ static int test(const uchar* datas, size_t size=0);
+ void measure(int& W, int& H);
+ static Fl_Shared_Image* get(const char* name, const uchar* datas = 0) {
+ return Fl_Shared_Image::get(create, name, datas);
+ }
+};
+
+class FL_IMAGES_API Fl_XPM_Image : public Fl_Shared_Image {
+ void read();
+ Fl_XPM_Image() { }
+ static Fl_Shared_Image* create() { return new Fl_XPM_Image; }
+public:
+ static int test(const uchar* datas, size_t size=0);
+ void measure(int& W, int& H);
+ static Fl_Shared_Image* get(const char* name, const uchar* datas = 0) {
+ return Fl_Shared_Image::get(create, name, datas);
+ }
+};
+
+class FL_IMAGES_API Fl_BMP_Image : public Fl_Shared_Image {
+ void read();
+ Fl_BMP_Image() { }
+ static Fl_Shared_Image* create() { return new Fl_BMP_Image; }
+public:
+ static int test(const uchar* datas, size_t size=0);
+ void measure(int& W, int& H);
+ static Fl_Shared_Image* get(const char* name, const uchar* datas = 0) {
+ return Fl_Shared_Image::get(create, name, datas);
+ }
+};
+
+class FL_IMAGES_API Fl_JPEG_Image : public Fl_Shared_Image {
+ void read();
+ Fl_JPEG_Image() { }
+ static Fl_Shared_Image* create() { return new Fl_JPEG_Image; }
+public:
+ static int test(const uchar* datas, size_t size=0);
+ void measure(int& W, int& H);
+ static Fl_Shared_Image* get(const char* name, const uchar* datas = 0) {
+ return Fl_Shared_Image::get(create, name, datas);
+ }
+};
+
+//class FL_API Fl_Bitmap;
+class Fl_Bitmap;
+extern FL_IMAGES_API Fl_Bitmap nosuch_bitmap;
+
+class FL_IMAGES_API Fl_UNKNOWN_Image {
+public:
+ static int test(const uchar*, size_t =0) { return 1; };
+ static Fl_Shared_Image* get(const char*, const uchar* = 0) {
+ return (Fl_Shared_Image*) &nosuch_bitmap;
+ };
+};
+
+#endif
+
+//
+// End of "$Id: Fl_Shared_Image.H,v 1.22 2001/07/16 19:38:17 robertk Exp $"
+//
diff --git a/FL/Fl_Text_Buffer.H b/FL/Fl_Text_Buffer.H
new file mode 100644
index 000000000..2528c2fa8
--- /dev/null
+++ b/FL/Fl_Text_Buffer.H
@@ -0,0 +1,241 @@
+//
+// "$Id: Fl_Text_Buffer.H,v 1.3 2001/02/21 06:15:44 clip Exp $"
+//
+// Header file for Fl_Text_Buffer class.
+//
+// Copyright Mark Edel. Permission to distribute under the LGPL for
+// the FLTK library granted by Mark Edel.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@fltk.org".
+//
+
+#ifndef FL_TEXT_BUFFER_H
+#define FL_TEXT_BUFFER_H
+
+/* Maximum length in characters of a tab or control character expansion
+ of a single buffer character */
+#define FL_TEXT_MAX_EXP_CHAR_LEN 20
+
+#include <FL/Fl_Export.H>
+
+class FL_API Fl_Text_Selection {
+ friend class Fl_Text_Buffer;
+
+ public:
+ void set(int start, int end);
+ void set_rectangular(int start, int end, int rectStart, int rectEnd);
+ void update(int pos, int nDeleted, int nInserted);
+ char rectangular() { return mRectangular; }
+ int start() { return mStart; }
+ int end() { return mEnd; }
+ int rect_start() { return mRectStart; }
+ int rect_end() { return mRectEnd; }
+ char selected() { return mSelected; }
+ void selected(char b) { mSelected = b; }
+ int includes(int pos, int lineStartPos, int dispIndex);
+ int position(int* start, int* end);
+ int position(int* start, int* end, int* isRect, int* rectStart, int* rectEnd);
+
+
+ protected:
+ char mSelected;
+ char mRectangular;
+ int mStart;
+ int mEnd;
+ int mRectStart;
+ int mRectEnd;
+};
+
+typedef void (*Fl_Text_Modify_Cb)(int pos, int nInserted, int nDeleted,
+ int nRestyled, const char* deletedText,
+ void* cbArg);
+
+class FL_API Fl_Text_Buffer {
+ public:
+ Fl_Text_Buffer(int requestedSize = 0);
+ ~Fl_Text_Buffer();
+
+ int length() { return mLength; }
+ const char* text();
+ void text(const char* text);
+ const char* text_range(int start, int end);
+ char character(int pos);
+ const char* text_in_rectangle(int start, int end, int rectStart, int rectEnd);
+ void insert(int pos, const char* text);
+ void append(const char* text) { insert(length(), text); }
+ void remove(int start, int end);
+ void replace(int start, int end, const char *text);
+ void copy(Fl_Text_Buffer* fromBuf, int fromStart, int fromEnd, int toPos);
+ int insertfile(const char *file, int pos, int buflen = 128*1024);
+ int appendfile(const char *file, int buflen = 128*1024)
+ { return insertfile(file, length(), buflen); }
+ int loadfile(const char *file, int buflen = 128*1024)
+ { select(0, length()); remove_selection(); return appendfile(file, buflen); }
+ int outputfile(const char *file, int start, int end, int buflen = 128*1024);
+ int savefile(const char *file, int buflen = 128*1024)
+ { return outputfile(file, 0, length(), buflen); }
+
+ void insert_column(int column, int startPos, const char* text,
+ int* charsInserted, int* charsDeleted);
+
+ void replace_rectangular(int start, int end, int rectStart, int rectEnd,
+ const char* text);
+
+ void overlay_rectangular(int startPos, int rectStart, int rectEnd,
+ const char* text, int* charsInserted,
+ int* charsDeleted);
+
+ void remove_rectangular(int start, int end, int rectStart, int rectEnd);
+ void clear_rectangular(int start, int end, int rectStart, int rectEnd);
+ int tab_distance() { return mTabDist; }
+ void tab_distance(int tabDist);
+ void select(int start, int end);
+ int selected() { return mPrimary.selected(); }
+ void unselect();
+ void select_rectangular(int start, int end, int rectStart, int rectEnd);
+ int selection_position(int* start, int* end);
+
+ int selection_position(int* start, int* end, int* isRect, int* rectStart,
+ int* rectEnd);
+
+ const char* selection_text();
+ void remove_selection();
+ void replace_selection(const char* text);
+ void secondary_select(int start, int end);
+ void secondary_unselect();
+
+ void secondary_select_rectangular(int start, int end, int rectStart,
+ int rectEnd);
+
+ int secondary_selection_position(int* start, int* end, int* isRect,
+ int* rectStart, int* rectEnd);
+
+ const char* secondary_selection_text();
+ void remove_secondary_selection();
+ void replace_secondary_selection(const char* text);
+ void highlight(int start, int end);
+ void unhighlight();
+ void highlight_rectangular(int start, int end, int rectStart, int rectEnd);
+
+ int highlight_position(int* start, int* end, int* isRect, int* rectStart,
+ int* rectEnd);
+
+ const char* highlight_text();
+ void add_modify_callback(Fl_Text_Modify_Cb bufModifiedCB, void* cbArg);
+ void remove_modify_callback(Fl_Text_Modify_Cb bufModifiedCB, void* cbArg);
+
+ void call_modify_callbacks() { call_modify_callbacks(0, 0, 0, 0, 0); }
+
+ const char* line_text(int pos);
+ int line_start(int pos);
+ int line_end(int pos);
+ int word_start(int pos);
+ int word_end(int pos);
+ int expand_character(int pos, int indent, char *outStr);
+
+ static int expand_character(char c, int indent, char* outStr, int tabDist,
+ char nullSubsChar);
+
+ static int character_width(char c, int indent, int tabDist, char nullSubsChar);
+ int count_displayed_characters(int lineStartPos, int targetPos);
+ int skip_displayed_characters(int lineStartPos, int nChars);
+ int count_lines(int startPos, int endPos);
+ int skip_lines(int startPos, int nLines);
+ int rewind_lines(int startPos, int nLines);
+ int findchar_forward(int startPos, char searchChar, int* foundPos);
+ int findchar_backward(int startPos, char searchChar, int* foundPos);
+ int findchars_forward(int startPos, const char* searchChars, int* foundPos);
+ int findchars_backward(int startPos, const char* searchChars, int* foundPos);
+
+ int search_forward(int startPos, const char* searchString, int* foundPos,
+ int matchCase = 0);
+
+ int search_backward(int startPos, const char* searchString, int* foundPos,
+ int matchCase = 0);
+
+ int substitute_null_characters(char* string, int length);
+ void unsubstitute_null_characters(char* string);
+ char null_substitution_character() { return mNullSubsChar; }
+ Fl_Text_Selection* primary_selection() { return &mPrimary; }
+ Fl_Text_Selection* secondary_selection() { return &mSecondary; }
+ Fl_Text_Selection* highlight_selection() { return &mHighlight; }
+
+ protected:
+ void call_modify_callbacks(int pos, int nDeleted, int nInserted,
+ int nRestyled, const char* deletedText);
+
+ int insert_(int pos, const char* text);
+ void remove_(int start, int end);
+
+ void remove_rectangular_(int start, int end, int rectStart, int rectEnd,
+ int* replaceLen, int* endPos);
+
+ void insert_column_(int column, int startPos, const char* insText,
+ int* nDeleted, int* nInserted, int* endPos);
+
+ void overlay_rectangular_(int startPos, int rectStart, int rectEnd,
+ const char* insText, int* nDeleted,
+ int* nInserted, int* endPos);
+
+ void redisplay_selection(Fl_Text_Selection* oldSelection,
+ Fl_Text_Selection* newSelection);
+
+ void move_gap(int pos);
+ void reallocate_with_gap(int newGapStart, int newGapLen);
+ const char* selection_text_(Fl_Text_Selection* sel);
+ void remove_selection_(Fl_Text_Selection* sel);
+ void replace_selection_(Fl_Text_Selection* sel, const char* text);
+
+ void rectangular_selection_boundaries(int lineStartPos, int rectStart,
+ int rectEnd, int* selStart,
+ int* selEnd);
+
+ void update_selections(int pos, int nDeleted, int nInserted);
+
+ Fl_Text_Selection mPrimary; /* highlighted areas */
+ Fl_Text_Selection mSecondary;
+ Fl_Text_Selection mHighlight;
+ int mLength; /* length of the text in the buffer (the length
+ of the buffer itself must be calculated:
+ gapEnd - gapStart + length) */
+ char* mBuf; /* allocated memory where the text is stored */
+ int mGapStart; /* points to the first character of the gap */
+ int mGapEnd; /* points to the first char after the gap */
+ // The hardware tab distance used by all displays for this buffer,
+ // and used in computing offsets for rectangular selection operations.
+ int mTabDist; /* equiv. number of characters in a tab */
+ int mUseTabs; /* True if buffer routines are allowed to use
+ tabs for padding in rectangular operations */
+ int mNModifyProcs; /* number of modify-redisplay procs attached */
+ Fl_Text_Modify_Cb* /* procedures to call when buffer is */
+ mNodifyProcs; /* modified to redisplay contents */
+ void** mCbArgs; /* caller arguments for modifyProcs above */
+ int mCursorPosHint; /* hint for reasonable cursor position after
+ a buffer modification operation */
+ char mNullSubsChar; /* NEdit is based on C null-terminated strings,
+ so ascii-nul characters must be substituted
+ with something else. This is the else, but
+ of course, things get quite messy when you
+ use it */
+};
+
+#endif
+
+//
+// End of "$Id: Fl_Text_Buffer.H,v 1.3 2001/02/21 06:15:44 clip Exp $".
+//
diff --git a/FL/Fl_Text_Display.H b/FL/Fl_Text_Display.H
new file mode 100644
index 000000000..af9d3df0b
--- /dev/null
+++ b/FL/Fl_Text_Display.H
@@ -0,0 +1,217 @@
+//
+// "$Id: Fl_Text_Display.H,v 1.4 2000/08/20 04:31:36 spitzak Exp $"
+//
+// Header file for Fl_Text_Display class.
+//
+// Copyright Mark Edel. Permission to distribute under the LGPL for
+// the FLTK library granted by Mark Edel.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@fltk.org".
+//
+
+#ifndef FL_TEXT_DISPLAY_H
+#define FL_TEXT_DISPLAY_H
+
+#include <FL/fl_draw.H>
+#include <FL/Fl_Group.H>
+#include <FL/Fl_Widget.H>
+#include <FL/Fl_Scrollbar.H>
+#include <FL/Fl_Text_Buffer.H>
+
+class FL_API Fl_Text_Display: public Fl_Group {
+ public:
+ enum {
+ NORMAL_CURSOR, CARET_CURSOR, DIM_CURSOR,
+ BLOCK_CURSOR, HEAVY_CURSOR
+ };
+
+ enum {
+ CURSOR_POS, CHARACTER_POS
+ };
+
+ // drag types- they match Fl::event_clicks() so that single clicking to
+ // start a collection selects by character, double clicking selects by
+ // word and triple clicking selects by line.
+ enum {
+ DRAG_CHAR = 0, DRAG_WORD = 1, DRAG_LINE = 2
+ };
+ friend void fl_text_drag_me(int pos, Fl_Text_Display* d);
+
+ typedef void (*Unfinished_Style_Cb)();
+
+ struct FL_API Style_Table_Entry {
+ Fl_Color color;
+ Fl_Font font;
+ int size;
+ };
+
+ Fl_Text_Display(int X, int Y, int W, int H, const char *l = 0);
+ ~Fl_Text_Display();
+
+ virtual int handle(int e);
+ void buffer(Fl_Text_Buffer* buf);
+ void buffer(Fl_Text_Buffer& buf) { buffer(&buf); }
+ Fl_Text_Buffer* buffer() { return mBuffer; }
+ void redisplay_range(int start, int end);
+ void scroll(int topLineNum, int horizOffset);
+ void insert(const char* text);
+ void overstrike(const char* text);
+ void insert_position(int newPos);
+ int insert_position() { return mCursorPos; }
+ int in_selection(int x, int y);
+ void show_insert_position();
+ int move_right();
+ int move_left();
+ int move_up();
+ int move_down();
+ void next_word(void);
+ void previous_word(void);
+ void show_cursor(int b = 1);
+ void hide_cursor() { show_cursor(0); }
+ void cursor_style(int style);
+ int scrollbar_width() { return scrollbar_width_; }
+ Fl_Flags scrollbar_align() { return scrollbar_align_; }
+ void scrollbar_width(int w) { scrollbar_width_ = w; }
+ void scrollbar_align(Fl_Flags a) { scrollbar_align_ = a; }
+ int word_start(int pos) { return buffer()->word_start(pos); }
+ int word_end(int pos) { return buffer()->word_end(pos); }
+
+ void highlight_data(Fl_Text_Buffer *styleBuffer,
+ Style_Table_Entry *styleTable,
+ int nStyles, char unfinishedStyle,
+ Unfinished_Style_Cb unfinishedHighlightCB,
+ void *cbArg);
+
+ int position_style(int lineStartPos, int lineLen, int lineIndex,
+ int dispIndex);
+
+ protected:
+ // Most (all?) of this stuff should only be called from layout() or
+ // draw().
+ // Anything with "vline" indicates thats it deals with currently
+ // visible lines.
+ virtual void layout();
+
+ virtual void draw();
+ void draw_text(int X, int Y, int W, int H);
+ void draw_range(int start, int end);
+ void draw_cursor(int, int);
+
+ void draw_string(int style, int x, int y, int toX, const char *string,
+ int nChars);
+
+ void draw_vline(int visLineNum, int leftClip, int rightClip,
+ int leftCharIndex, int rightCharIndex);
+
+ void clear_rect(int style, int x, int y, int width, int height);
+ void display_insert();
+
+ void offset_line_starts(int newTopLineNum);
+
+ void calc_line_starts(int startLine, int endLine);
+
+ void update_line_starts(int pos, int charsInserted, int charsDeleted,
+ int linesInserted, int linesDeleted, int *scrolled);
+
+ void calc_last_char();
+
+ int position_to_line( int pos, int* lineNum );
+ int string_width(const char* string, int length, int style);
+
+ static void buffer_modified_cb(int pos, int nInserted, int nDeleted,
+ int nRestyled, const char* deletedText,
+ void* cbArg);
+
+ static void h_scrollbar_cb(Fl_Scrollbar* w, Fl_Text_Display* d);
+ static void v_scrollbar_cb( Fl_Scrollbar* w, Fl_Text_Display* d);
+ void update_v_scrollbar();
+ void update_h_scrollbar();
+ int measure_vline(int visLineNum);
+ int longest_vline();
+ int empty_vlines();
+ int vline_length(int visLineNum);
+ int xy_to_position(int x, int y, int PosType = CHARACTER_POS);
+
+ void xy_to_rowcol(int x, int y, int* row, int* column,
+ int PosType = CHARACTER_POS);
+
+ int position_to_xy(int pos, int* x, int* y);
+ int position_to_linecol(int pos, int* lineNum, int* column);
+ void scroll_(int topLineNum, int horizOffset);
+
+ void extend_range_for_styles(int* start, int* end);
+
+
+ int damage_range1_start, damage_range1_end;
+ int damage_range2_start, damage_range2_end;
+ int mCursorPos;
+ int mCursorOn;
+ int mCursorOldY; /* Y pos. of cursor for blanking */
+ int mCursorToHint; /* Tells the buffer modified callback
+ where to move the cursor, to reduce
+ the number of redraw calls */
+ int mCursorStyle; /* One of enum cursorStyles above */
+ int mCursorPreferredCol; /* Column for vert. cursor movement */
+ int mNVisibleLines; /* # of visible (displayed) lines */
+ int mNBufferLines; /* # of newlines in the buffer */
+ Fl_Text_Buffer* mBuffer; /* Contains text to be displayed */
+ Fl_Text_Buffer* mStyleBuffer; /* Optional parallel buffer containing
+ color and font information */
+ int mFirstChar, mLastChar; /* Buffer positions of first and last
+ displayed character (lastChar points
+ either to a newline or one character
+ beyond the end of the buffer) */
+ int* mLineStarts;
+ int mTopLineNum; /* Line number of top displayed line
+ of file (first line of file is 1) */
+ int mHorizOffset; /* Horizontal scroll pos. in pixels */
+ int mTopLineNumHint; /* Line number of top displayed line
+ of file (first line of file is 1) */
+ int mHorizOffsetHint; /* Horizontal scroll pos. in pixels */
+ int mVisibility; /* Window visibility (see XVisibility
+ event) */
+ int mNStyles; /* Number of entries in styleTable */
+ Style_Table_Entry *mStyleTable; /* Table of fonts and colors for
+ coloring/syntax-highlighting */
+ char mUnfinishedStyle; /* Style buffer entry which triggers
+ on-the-fly reparsing of region */
+ Unfinished_Style_Cb mUnfinishedHighlightCB; /* Callback to parse "unfinished" */
+ /* regions */
+ void* mHighlightCBArg; /* Arg to unfinishedHighlightCB */
+
+ int mMaxsize;
+
+ int mFixedFontWidth; /* Font width if all current fonts are
+ fixed and match in width, else -1 */
+
+ Fl_Color mCursor_color;
+
+ Fl_Scrollbar* mHScrollBar;
+ Fl_Scrollbar* mVScrollBar;
+ int scrollbar_width_;
+ Fl_Flags scrollbar_align_;
+ int dragPos, dragType, dragging;
+ int display_insert_position_hint;
+ struct { int x, y, w, h; } text_area;
+};
+
+#endif
+
+//
+// End of "$Id: Fl_Text_Display.H,v 1.4 2000/08/20 04:31:36 spitzak Exp $".
+//
diff --git a/FL/Fl_Text_Editor.H b/FL/Fl_Text_Editor.H
new file mode 100644
index 000000000..980a7fb84
--- /dev/null
+++ b/FL/Fl_Text_Editor.H
@@ -0,0 +1,105 @@
+//
+// "$Id: Fl_Text_Editor.H,v 1.1 2000/08/04 10:21:59 clip Exp $"
+//
+// Header file for Fl_Text_Editor class.
+//
+// Copyright Mark Edel. Permission to distribute under the LGPL for
+// the FLTK library granted by Mark Edel.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@fltk.org".
+//
+
+
+#ifndef FL_TEXT_EDITOR_H
+#define FL_TEXT_EDITOR_H
+
+#include <FL/Fl_Text_Display.H>
+
+// key will match in any state
+#define FL_TEXT_EDITOR_ANY_STATE (-1L)
+
+class FL_API Fl_Text_Editor : public Fl_Text_Display {
+ public:
+ typedef int (*Key_Func)(int key, Fl_Text_Editor* editor);
+
+ struct FL_API Key_Binding {
+ int key;
+ int state;
+ Key_Func function;
+ Key_Binding* next;
+ };
+
+ Fl_Text_Editor(int X, int Y, int W, int H, const char* l = 0);
+ ~Fl_Text_Editor() { remove_all_key_bindings(); }
+ virtual int handle(int e);
+ void insert_mode(int b) { insert_mode_ = b; }
+ int insert_mode() { return insert_mode_; }
+
+ void add_key_binding(int key, int state, Key_Func f, Key_Binding** list);
+ void add_key_binding(int key, int state, Key_Func f)
+ { add_key_binding(key, state, f, &key_bindings); }
+ void remove_key_binding(int key, int state, Key_Binding** list);
+ void remove_key_binding(int key, int state)
+ { remove_key_binding(key, state, &key_bindings); }
+ void remove_all_key_bindings(Key_Binding** list);
+ void remove_all_key_bindings() { remove_all_key_bindings(&key_bindings); }
+ void add_default_key_bindings(Key_Binding** list);
+ Key_Func bound_key_function(int key, int state, Key_Binding* list);
+ Key_Func bound_key_function(int key, int state)
+ { return bound_key_function(key, state, key_bindings); }
+ void default_key_function(Key_Func f) { default_key_function_ = f; }
+
+ // functions for the built in default bindings
+ static int kf_default(int c, Fl_Text_Editor* e);
+ static int kf_ignore(int c, Fl_Text_Editor* e);
+ static int kf_backspace(int c, Fl_Text_Editor* e);
+ static int kf_enter(int c, Fl_Text_Editor* e);
+ static int kf_move(int c, Fl_Text_Editor* e);
+ static int kf_shift_move(int c, Fl_Text_Editor* e);
+ static int kf_ctrl_move(int c, Fl_Text_Editor* e);
+ static int kf_c_s_move(int c, Fl_Text_Editor* e);
+ static int kf_home(int, Fl_Text_Editor* e);
+ static int kf_end(int c, Fl_Text_Editor* e);
+ static int kf_left(int c, Fl_Text_Editor* e);
+ static int kf_up(int c, Fl_Text_Editor* e);
+ static int kf_right(int c, Fl_Text_Editor* e);
+ static int kf_down(int c, Fl_Text_Editor* e);
+ static int kf_page_up(int c, Fl_Text_Editor* e);
+ static int kf_page_down(int c, Fl_Text_Editor* e);
+ static int kf_insert(int c, Fl_Text_Editor* e);
+ static int kf_delete(int c, Fl_Text_Editor* e);
+ static int kf_copy(int c, Fl_Text_Editor* e);
+ static int kf_cut(int c, Fl_Text_Editor* e);
+ static int kf_paste(int c, Fl_Text_Editor* e);
+ static int kf_select_all(int c, Fl_Text_Editor* e);
+
+ protected:
+ int handle_key();
+
+ int insert_mode_;
+ Key_Binding* key_bindings;
+ static Key_Binding* global_key_bindings;
+ Key_Func default_key_function_;
+};
+
+#endif
+
+//
+// End of "$Id: Fl_Text_Editor.H,v 1.1 2000/08/04 10:21:59 clip Exp $".
+//
+
diff --git a/FL/Fl_Tooltip.H b/FL/Fl_Tooltip.H
new file mode 100644
index 000000000..054c16786
--- /dev/null
+++ b/FL/Fl_Tooltip.H
@@ -0,0 +1,72 @@
+//
+// "$Id: Fl_Tooltip.H,v 1.16 2001/02/25 01:41:19 clip Exp $"
+//
+// Tooltip definitions for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-1999 by Bill Spitzak and others.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@easysw.com".
+//
+
+#ifndef _FL_TOOLTIP_H_
+#define _FL_TOOLTIP_H_
+
+#include <FL/Fl.H>
+#include <FL/Fl_Widget.H>
+
+class FL_API Fl_Tooltip {
+public:
+ static float delay() { return delay_; }
+ static void delay(float f) { delay_ = f; }
+ static int enabled() { return enabled_; }
+ static void enable(int b = 1) { enabled_ = b; }
+ static void disable() { enable(0); }
+
+ // This is called when the pointer enters a widget,
+ // Also enter(0) gets rid of any displayed or pending tooltip:
+ static void (*enter)(Fl_Widget* w);
+
+ // A widget may also pop up tooltips for internal parts by calling this:
+ static void (*enter_area)(Fl_Widget* w, int X, int Y, int W, int H, const char* tip);
+
+ // This is called when a widget is destroyed or hidden:
+ static void (*exit)(Fl_Widget *w);
+
+ static Fl_Style* style() { return default_style; }
+ static Fl_Font font() { return style()->label_font; }
+ static void font(Fl_Font i) { style()->label_font = i; }
+ static unsigned size() { return style()->label_size; }
+ static void size(unsigned s) { style()->label_size = s; }
+ static void color(Fl_Color c) { style()->color = c; }
+ static Fl_Color color() { return style()->color; }
+ static void textcolor(Fl_Color c) {style()->label_color = c; }
+ static Fl_Color textcolor() { return style()->label_color; }
+ static void boxtype(Fl_Boxtype b) {style()->box = b; }
+ static Fl_Boxtype boxtype() { return style()->box; }
+
+private:
+ static Fl_Named_Style* default_style;
+ static float delay_;
+ static int enabled_;
+};
+
+#endif
+
+//
+// End of "$Id: Fl_Tooltip.H,v 1.16 2001/02/25 01:41:19 clip Exp $".
+//
diff --git a/documentation/Fl_Image.html b/documentation/Fl_Image.html
new file mode 100644
index 000000000..1287a6113
--- /dev/null
+++ b/documentation/Fl_Image.html
@@ -0,0 +1,142 @@
+<head><title>Fl_Image</title></head><body bgcolor=white>
+
+<h1>class Fl_Image</h1>
+
+This class holds an image, normally used to label a widget. The
+subclasses define how the data is interpreted, and usually store
+server-side cached versions of the image. All the current types
+define pixel arrays, but other types of images, such as vector
+graphics, can be defined.
+
+<h2>Methods</h2>
+
+<h4>ulong id, mask;<br>
+void _draw(int X, int Y, int W, int H, int cx, int cy);</h4>
+
+Subclasses may use these <i>protected</i> members of the base class to
+draw a cached pixel array. They must first set <tt>id</tt> and
+<tt>mask</tt> to the color and transparency offscreen windows, using
+system-specific code. Then they can call <tt>_draw()</tt> to draw
+them.
+
+<h4>int w,h</h4>
+
+These members hold the width and height of the image. They are not
+correct until <tt>measure()</tt> is called. These are public instance
+variables for back comptability, but you should never set them.
+
+<h4>virtual void Fl_Image::measure(int W, int H);</h4>
+
+Measure how big the image will be if it is drawn inside a W,H
+rectangle and put the result into w,h. For most image types this does
+nothing and w,h are set by the constructor. This may be used to
+initialize the scaling for variable-sized images.
+
+<h4>virtual void Fl_Image::draw(int x,int y,int w,int h, int cx,int
+cy);</h4>
+
+Draw the image so the point <i>cx,cy</i> of the image is at
+<i>x,y</i>. The image may be scaled or clipped to fit in the <i>w,h</i>
+rectangle, but this is not necessary (although obeying the current
+fl_clip value is!).
+
+<h4>void Fl_Image::draw(int x,int y,int w,int h, Fl_Flags align);</h4>
+
+This <i>non-virtual</i> function uses <tt>measure()</tt> and the
+<i>align</i> flags to figure out <i>cx,cy</i> and call the normal draw
+function. This allows you to center or align any edge of the image
+with a bounding box.
+
+<h4>virtual Fl_Image::~Fl_Image();</h4>
+
+The destructor throws away any server-cached information, but in most
+cases does not destroy the local data passed to a constructor.
+
+<H2><A name=Fl_Bitmap>class Fl_Bitmap : public Fl_Image</A></H2>
+ This object encapsulates the width, height, and bits of an X bitmap
+(XBM), and allows you to make an <TT>Fl_Widget</TT> use a bitmap as a
+label, or to just draw the bitmap directly.
+
+<H4>Fl_Bitmap(const char *bits, int W, int H)
+<BR> Fl_Bitmap(const uchar *bits, int W, int H)</H4>
+Construct using an X bitmap. The bits pointer is simply copied to the
+object, so it must point at persistent storage. The two constructors
+are provided because various X implementations disagree about the type
+of bitmap data. To use an XBM file use:
+<UL>
+<PRE>
+#include &quot;foo.xbm&quot;
+...
+Fl_Bitmap bitmap = new Fl_Bitmap(foo_bits, foo_width, foo_height);
+</PRE>
+</UL>
+<H4>~Fl_Bitmap()</H4>
+ The destructor will destroy any X pixmap created. It does not do
+anything to the bits data.
+<H4>void draw(int x, int y, int w, int h, int ox = 0, int oy = 0)</H4>
+1 bits are drawn with the current color, 0 bits
+are unchanged.
+The image is clipped to the destination rectangle: the area
+<TT>ox,oy,w,h</TT> is copied to <TT>x,y,w,h</TT>.
+<H4>void draw(int x, int y)</H4>
+Draws the bitmap with the upper-left corner at <TT>x,y</TT>. This is
+the same as doing <TT>draw(x,y,this-&gt;w,this-&gt;h,0,0)</TT>.
+
+<H2><A name=Fl_Pixmap>class Fl_Pixmap : public Fl_Image</A></H2>
+
+This object encapsulates the data from an XPM image, and allows you to
+make an <TT>Fl_Widget</TT> use a pixmap as a label, or to just draw
+the pixmap directly.
+
+<H4>Fl_Pixmap(char *const* data)</H4>
+ Construct using XPM data. The data pointer is simply copied to the
+object, so it must point at persistent storage. To use an XPM file do:
+<UL>
+<PRE>
+#include &lt;fltk/Fl_Pixmap.h&gt;
+#include &quot;foo.xpm&quot;
+...
+Fl_Pixmap pixmap = new Fl_Pixmap(foo);
+</PRE>
+</UL>
+<H4>~Fl_Pixmap()</H4>
+ The destructor will destroy any X pixmap created. It does not do
+anything to the data.
+
+<H4>void draw(int x, int y, int w, int h, int ox = 0, int oy = 0)</H4>
+The image is clipped to the destination rectangle: the area
+<TT>ox,oy,w,h</TT> is copied to <TT>x,y,w,h</TT>. The current
+implementation converts the pixmap to 24-bit RGB data and uses <A
+href=#fl_draw_image><TT>fl_draw_image()</TT></A> to draw it. Thus you
+will get dithered colors on an 8 bit screen. </P>
+
+<H4>void draw(int x, int y)</H4>
+ Draws the image with the upper-left corner at <TT>x,y</TT>. This is
+the same as doing <TT>draw(x,y,this-&gt;w,this-&gt;h,0,0)</TT>.
+
+<H2><A name=Fl_RGB_Image>class Fl_RGB_Image</A></H2>
+
+This object encapsulates a full-color RGB image, and allows you to
+make an <TT>Fl_Widget</TT> use an image as a label, or to just draw the
+image directly.
+
+<H4>Fl_RGB_Image(const uchar *data, int W, int H, int D = 3, int LD = 0)</H4>
+ Construct using a pointer to RGB data. <TT>W</TT> and <TT>H</TT> are
+the size of the image in pixels. <TT>D</TT> is the delta between pixels
+(it may be more than 3 to skip alpha or other data, or negative to flip
+the image left/right). <TT>LD</TT> is the delta between lines (it may
+be more than <TT>D * W</TT> to crop images, or negative to flip the
+image vertically). The data pointer is simply copied to the object, so
+it must point at persistent storage.
+<H4>~Fl_RGB_Image()</H4>
+ The destructor will destroy any X pixmap created. It does not do
+anything to the data.
+<H4>void draw(int x, int y, int w, int h, int ox = 0, int oy = 0)</H4>
+The image is clipped to the destination rectangle: the area
+<TT>ox,oy,w,h</TT> is copied to <TT>x,y,w,h</TT>.
+<H4>void draw(int x, int y)</H4>
+ Draws the image with the upper-left corner at <TT>x,y</TT>. This is
+the same as doing <TT>draw(x,y,this-&gt;w,this-&gt;h,0,0)</TT>.
+
+</body></html>
+
diff --git a/src/Fl_FileBrowser.cxx b/src/Fl_FileBrowser.cxx
new file mode 100644
index 000000000..610837efb
--- /dev/null
+++ b/src/Fl_FileBrowser.cxx
@@ -0,0 +1,446 @@
+//
+// "$Id: Fl_FileBrowser.cxx,v 1.13 2001/07/29 22:04:43 spitzak Exp $"
+//
+// Fl_FileBrowser routines for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1997-1999 by Easy Software Products.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@easysw.com".
+//
+// Contents:
+//
+// Fl_Fl_FileBrowser::item_width() - Return the width of a list item.
+// Fl_Fl_FileBrowser::item_draw() - Draw a list item.
+// Fl_Fl_FileBrowser::FileBrowser() - Create a FileBrowser widget.
+// Fl_Fl_FileBrowser::load() - Load a directory into the browser.
+// Fl_Fl_FileBrowser::filter() - Set the filename filter.
+//
+
+//
+// Include necessary header files...
+//
+
+#include <fltk/Fl_FileBrowser.h>
+#include <fltk/fl_draw.h>
+#include <fltk/filename.h>
+#include <fltk/vsnprintf.h>
+#include <stdlib.h>
+#include <config.h>
+#include <string.h>
+
+#if defined(_WIN32)
+# include <windows.h>
+# include <direct.h>
+#endif /* _WIN32 */
+
+#if defined(__EMX__)
+#define INCL_DOS
+#define INCL_DOSMISC
+#include <os2.h>
+#endif /* __EMX__ */
+
+//
+// FL_BLINE definition from "Fl_Browser.cxx"...
+//
+
+#define SELECTED 1
+#define NOTDISPLAYED 2
+
+struct FL_BLINE // data is in a linked list of these
+{
+ FL_BLINE *prev; // Previous item in list
+ FL_BLINE *next; // Next item in list
+ void *data; // Pointer to data (function)
+ short length; // sizeof(txt)-1, may be longer than string
+ char flags; // selected, displayed
+ char txt[1]; // start of allocated array
+};
+
+
+//
+// 'Fl_FileBrowser::item_height()' - Return the height of a list item.
+//
+
+int // O - Height in pixels
+Fl_FileBrowser::item_height(void *p) const // I - List item data
+{
+ FL_BLINE *line; // Pointer to line
+ char *text; // Pointer into text
+ int height; // Width of line
+ int textheight; // Height of text
+
+
+ // Figure out the standard text height...
+ textheight = fl_height(textfont(), textsize())+leading();
+
+ // We always have at least 1 line...
+ height = textheight;
+
+ // Scan for newlines...
+ line = (FL_BLINE *)p;
+
+ if (line != NULL)
+ for (text = line->txt; *text != '\0'; text ++)
+ if (*text == '\n')
+ height += textheight;
+
+ // If we have enabled icons then add space for them...
+ if (Fl_FileIcon::first() != NULL && height < iconsize_)
+ height = iconsize_;
+
+ // Add space for the selection border..
+ height += 2;
+
+ // Return the height
+ return (height);
+}
+
+
+//
+// 'Fl_FileBrowser::item_width()' - Return the width of a list item.
+//
+
+int // O - Width in pixels
+Fl_FileBrowser::item_width(void *p) const // I - List item data
+{
+ FL_BLINE *line; // Pointer to line
+ char *text, // Pointer into text
+ *ptr, // Pointer into fragment
+ fragment[10240]; // Fragment of text
+ int width, // Width of line
+ tempwidth; // Width of fragment
+ int column; // Current column
+
+
+ // Set the font and size...
+ fl_font(text_font(), text_size());
+
+ // Scan for newlines...
+ line = (FL_BLINE *)p;
+
+ if (strchr(line->txt, '\n') == NULL &&
+ strchr(line->txt, '\t') == NULL)
+ {
+ // Do a fast width calculation...
+ width = fl_width(line->txt);
+ }
+ else
+ {
+ // More than 1 line or have columns; find the maximum width...
+ width = 0;
+ tempwidth = 0;
+ column = 0;
+
+ for (text = line->txt, ptr = fragment; *text != '\0'; text ++)
+ if (*text == '\n')
+ {
+ // Newline - nul terminate this fragment and get the width...
+ *ptr = '\0';
+
+ tempwidth += fl_width(fragment);
+
+ // Update the max width as needed...
+ if (tempwidth > width)
+ width = tempwidth;
+
+ // Point back to the start of the fragment...
+ ptr = fragment;
+ tempwidth = 0;
+ }
+ else if (*text == '\t')
+ {
+ // Advance to the next column...
+ column ++;
+ tempwidth = column * fl_width(" ");
+
+ if (tempwidth > width)
+ width = tempwidth;
+
+ ptr = fragment;
+ }
+ else
+ *ptr++ = *text;
+
+ if (ptr > fragment)
+ {
+ // Nul terminate this fragment and get the width...
+ *ptr = '\0';
+
+ tempwidth += fl_width(fragment);
+
+ // Update the max width as needed...
+ if (tempwidth > width)
+ width = tempwidth;
+ }
+ }
+
+ // If we have enabled icons then add space for them...
+ if (Fl_FileIcon::first() != NULL)
+ width += iconsize_ + 8;
+
+ // Add space for the selection border..
+ width += 2;
+
+ // Return the width
+ return (width);
+}
+
+
+//
+// 'Fl_FileBrowser::item_draw()' - Draw a list item.
+//
+
+void
+Fl_FileBrowser::item_draw(void *p, // I - List item data
+ int x, // I - Upper-lefthand X coordinate
+ int y, // I - Upper-lefthand Y coordinate
+ int w, // I - Width of item
+ int h) const // I - Height of item
+{
+ Fl_Color c; // Color of text
+ FL_BLINE *line; // Pointer to line
+
+
+ puts("Fl_FileBrowser::item_draw()");
+ // Draw the list item text...
+ line = (FL_BLINE *)p;
+
+ fl_font(text_font(), text_size());
+ if (line->flags & SELECTED)
+ c = fl_contrast(text_color(), selection_color());
+ else
+ c = text_color();
+
+ if (active_r())
+ fl_color(c);
+ else
+ fl_color(fl_inactive(c));
+
+ if (Fl_FileIcon::first() == NULL)
+ {
+ // No icons, just draw the text...
+ fl_draw(line->txt, x + 1, y, w - 2, h, FL_ALIGN_LEFT);
+ }
+ else
+ {
+ // Icons; draw the text offset to the right...
+ fl_draw(line->txt, x + iconsize_ + 9, y, w - iconsize_ - 10, h,
+ FL_ALIGN_LEFT);
+
+ // And then draw the icon if it is set...
+ if (line->data)
+ ((Fl_FileIcon *)line->data)->draw(x, y, iconsize_, iconsize_,
+ (line->flags & SELECTED) ? FL_YELLOW :
+ FL_LIGHT2,
+ active_r());
+ }
+}
+
+
+//
+// 'Fl_FileBrowser::Fl_FileBrowser()' - Create a Fl_FileBrowser widget.
+//
+
+Fl_FileBrowser::Fl_FileBrowser(int x, // I - Upper-lefthand X coordinate
+ int y, // I - Upper-lefthand Y coordinate
+ int w, // I - Width in pixels
+ int h, // I - Height in pixels
+ const char *l) // I - Label text
+ : Fl_Browser(x, y, w, h, l)
+{
+ // Initialize the filter pattern, current directory, and icon size...
+ pattern_ = "*";
+ directory_ = "";
+ iconsize_ = 20; // This looks best for the default icons, if loaded...
+}
+
+
+//
+// 'Fl_FileBrowser::load()' - Load a directory into the browser.
+//
+
+int // O - Number of files loaded
+Fl_FileBrowser::load(const char *directory)// I - Directory to load
+{
+ int i; // Looping var
+ int num_files; // Number of files in directory
+ char filename[4096]; // Current file
+ Fl_FileIcon *icon; // Icon to use
+
+
+ clear();
+ directory_ = directory;
+
+ if (directory_[0] == '\0')
+ {
+ //
+ // No directory specified; for UNIX list all mount points. For DOS
+ // list all valid drive letters...
+ //
+
+ num_files = 0;
+ icon = Fl_FileIcon::find("any", Fl_FileIcon::DEVICE);
+
+ if (icon == (Fl_FileIcon *)0)
+ icon = Fl_FileIcon::find("any", Fl_FileIcon::DIR);
+
+#if defined(_WIN32)
+ DWORD drives; // Drive available bits
+
+ drives = GetLogicalDrives();
+ for (i = 'A'; i <= 'Z'; i ++, drives >>= 1)
+ if (drives & 1)
+ {
+ sprintf(filename, "%c:", i);
+ add(filename, icon);
+
+ num_files ++;
+ }
+#elif defined(__EMX__)
+ ULONG curdrive; // Current drive
+ ULONG drives; // Drive available bits
+ int start = 3; // 'C' (MRS - dunno if this is correct!)
+
+
+ DosQueryCurrentDisk(&curdrive, &drives);
+ drives >>= start - 1;
+ for (i = 'A'; i <= 'Z'; i ++, drives >>= 1)
+ if (drives & 1)
+ {
+ sprintf(filename, "%c:", i);
+ add(filename, icon);
+
+ num_files ++;
+ }
+#else
+ FILE *mtab; // /etc/mtab or /etc/mnttab file
+ char line[1024]; // Input line
+
+ //
+ // Open the file that contains a list of mounted filesystems...
+ //
+# if defined(__hpux) || defined(__sun)
+ mtab = fopen("/etc/mnttab", "r"); // Fairly standard
+# elif defined(__sgi) || defined(linux)
+ mtab = fopen("/etc/mtab", "r"); // More standard
+# else
+ mtab = fopen("/etc/fstab", "r"); // Otherwise fallback to full list
+ if (mtab == NULL)
+ mtab = fopen("/etc/vfstab", "r");
+# endif
+
+ if (mtab != NULL)
+ {
+ while (fgets(line, sizeof(line), mtab) != NULL)
+ {
+ if (line[0] == '#' || line[0] == '\n')
+ continue;
+ if (sscanf(line, "%*s%4095s", filename) != 1)
+ continue;
+
+ add(filename, icon);
+ num_files ++;
+ }
+
+ fclose(mtab);
+ }
+#endif // _WIN32
+ }
+ else
+ {
+ dirent **files; // Files in in directory
+
+
+ //
+ // Build the file list...
+ //
+
+#if defined(_WIN32) || defined(__EMX__)
+ strncpy(filename, directory_, sizeof(filename) - 1);
+ filename[sizeof(filename) - 1] = '\0';
+
+ i = strlen(filename) - 1;
+
+ if (i == 2 && filename[1] == ':' &&
+ (filename[2] == '/' || filename[2] == '\\'))
+ filename[2] = '/';
+ else if (filename[i] != '/' && filename[i] != '\\')
+ strcat(filename, "/");
+
+ num_files = filename_list(filename, &files);
+#else
+ num_files = filename_list(directory_, &files);
+#endif /* _WIN32 || __EMX__ */
+
+ if (num_files <= 0)
+ return (0);
+
+ // Add directories first...
+ for (i = 0; i < num_files; i ++)
+ if (strcmp(files[i]->d_name, ".") != 0 &&
+ strcmp(files[i]->d_name, "..") != 0)
+ {
+ snprintf(filename, sizeof(filename), "%s/%s", directory_, files[i]->d_name);
+
+ if (filename_isdir(filename))
+ add(files[i]->d_name, Fl_FileIcon::find(filename));
+ }
+
+ for (i = 0; i < num_files; i ++)
+ {
+ if (strcmp(files[i]->d_name, ".") != 0 &&
+ strcmp(files[i]->d_name, "..") != 0)
+ {
+ snprintf(filename, sizeof(filename), "%s/%s", directory_, files[i]->d_name);
+
+ if (!filename_isdir(filename) &&
+ filename_match(files[i]->d_name, pattern_))
+ add(files[i]->d_name, Fl_FileIcon::find(filename));
+ }
+
+ free(files[i]);
+ }
+
+ free(files);
+ }
+
+ return (num_files);
+}
+
+
+//
+// 'Fl_FileBrowser::filter()' - Set the filename filter.
+//
+
+void
+Fl_FileBrowser::filter(const char *pattern) // I - Pattern string
+{
+ // If pattern is NULL set the pattern to "*"...
+ if (pattern)
+ pattern_ = pattern;
+ else
+ pattern_ = "*";
+
+ // Reload the current directory...
+ load(directory_);
+}
+
+
+//
+// End of "$Id: Fl_FileBrowser.cxx,v 1.13 2001/07/29 22:04:43 spitzak Exp $".
+//
diff --git a/src/Fl_FileChooser.cxx b/src/Fl_FileChooser.cxx
new file mode 100644
index 000000000..249567ee8
--- /dev/null
+++ b/src/Fl_FileChooser.cxx
@@ -0,0 +1,248 @@
+// generated by Fast Light User Interface Designer (fluid) version 2.0000
+
+#include <fltk/Fl_FileChooser.h>
+#include <fltk/vsnprintf.h>
+
+inline void Fl_FileChooser::cb_window_i(Fl_Window*, void*) {
+ fileList->deselect();
+ fileName->value("");
+ window->hide();
+}
+void Fl_FileChooser::cb_window(Fl_Window* o, void* v) {
+ ((Fl_FileChooser*)(o->user_data()))->cb_window_i(o,v);
+}
+
+inline void Fl_FileChooser::cb_dirMenu_i(Fl_Choice*, void*) {
+ if (dirMenu->value())
+ directory(dirMenu->text(dirMenu->value()));
+else
+ directory("");
+}
+void Fl_FileChooser::cb_dirMenu(Fl_Choice* o, void* v) {
+ ((Fl_FileChooser*)(o->parent()->user_data()))->cb_dirMenu_i(o,v);
+}
+
+inline void Fl_FileChooser::cb_upButton_i(Fl_Button*, void*) {
+ up();
+}
+void Fl_FileChooser::cb_upButton(Fl_Button* o, void* v) {
+ ((Fl_FileChooser*)(o->parent()->user_data()))->cb_upButton_i(o,v);
+}
+
+#include <fltk/Fl_Bitmap.h>
+static unsigned char bits_up[] =
+"\0\0x\0\204\0\2\1""1\376y\200\375\200""1\200""1\200""1\200""1\200""1\200\1\
+\200\1\200\377\377\0\0";
+static Fl_Bitmap bitmap_up(bits_up, 16, 16);
+
+inline void Fl_FileChooser::cb_newButton_i(Fl_Button*, void*) {
+ newdir();
+}
+void Fl_FileChooser::cb_newButton(Fl_Button* o, void* v) {
+ ((Fl_FileChooser*)(o->parent()->user_data()))->cb_newButton_i(o,v);
+}
+
+static unsigned char bits_new[] =
+"\0\0x\0\204\0\2\1\1\376\1\200""1\200""1\200\375\200\375\200""1\200""1\200\1\
+\200\1\200\377\377\0\0";
+static Fl_Bitmap bitmap_new(bits_new, 16, 16);
+
+inline void Fl_FileChooser::cb__i(Fl_Button*, void*) {
+ fileList->filter("*");;
+rescan();
+}
+void Fl_FileChooser::cb_(Fl_Button* o, void* v) {
+ ((Fl_FileChooser*)(o->parent()->user_data()))->cb__i(o,v);
+}
+
+static unsigned char bits_allfiles[] =
+"\374?\4 \4 \4 \204!\244%\304#\364/\364/\304#\244%\204!\4 \4 \4 \374?";
+static Fl_Bitmap bitmap_allfiles(bits_allfiles, 16, 16);
+
+inline void Fl_FileChooser::cb_fileList_i(Fl_FileBrowser*, void*) {
+ fileListCB();
+}
+void Fl_FileChooser::cb_fileList(Fl_FileBrowser* o, void* v) {
+ ((Fl_FileChooser*)(o->parent()->user_data()))->cb_fileList_i(o,v);
+}
+
+inline void Fl_FileChooser::cb_fileName_i(Fl_FileInput*, void*) {
+ fileNameCB();
+}
+void Fl_FileChooser::cb_fileName(Fl_FileInput* o, void* v) {
+ ((Fl_FileChooser*)(o->parent()->user_data()))->cb_fileName_i(o,v);
+}
+
+inline void Fl_FileChooser::cb_okButton_i(Fl_Return_Button*, void*) {
+ char pathname[1024];
+
+snprintf(pathname, sizeof(pathname), "%s/%s",
+ fileList->directory(), fileName->value());
+if (filename_isdir(pathname))
+ directory(pathname);
+else
+ window->hide();
+}
+void Fl_FileChooser::cb_okButton(Fl_Return_Button* o, void* v) {
+ ((Fl_FileChooser*)(o->parent()->user_data()))->cb_okButton_i(o,v);
+}
+
+inline void Fl_FileChooser::cb_Cancel_i(Fl_Button*, void*) {
+ fileList->deselect();
+fileName->value("");
+window->hide();
+}
+void Fl_FileChooser::cb_Cancel(Fl_Button* o, void* v) {
+ ((Fl_FileChooser*)(o->parent()->user_data()))->cb_Cancel_i(o,v);
+}
+
+Fl_FileChooser::Fl_FileChooser(const char *d, const char *p, int t, const char *title) {
+ { Fl_Window* o = window = new Fl_Window(375, 315, "Pick a File");
+ o->callback((Fl_Callback*)cb_window, (void*)(this));
+ ((Fl_Window*)(o))->hotspot(o);
+ { Fl_Choice* o = dirMenu = new Fl_Choice(65, 10, 210, 25, "Directory:"); o->begin();
+ o->callback((Fl_Callback*)cb_dirMenu);
+ o->tooltip("Click to access directory tree.");
+ o->set_flag(FL_ALIGN_LEFT | FL_ALIGN_RIGHT);
+ o->align(FL_ALIGN_LEFT | FL_ALIGN_RIGHT);
+ o->end();
+ }
+ { Fl_Button* o = upButton = new Fl_Button(280, 10, 25, 25);
+ o->image(bitmap_up);
+ o->label_size(8);
+ o->callback((Fl_Callback*)cb_upButton);
+ o->tooltip("Click to display parent directory.");
+ }
+ { Fl_Button* o = newButton = new Fl_Button(310, 10, 25, 25);
+ o->image(bitmap_new);
+ o->label_size(8);
+ o->callback((Fl_Callback*)cb_newButton);
+ o->tooltip("Click to create a new directory.");
+ }
+ { Fl_Button* o = new Fl_Button(340, 10, 25, 25);
+ o->image(bitmap_allfiles);
+ o->label_color((Fl_Color)4);
+ o->label_size(28);
+ o->callback((Fl_Callback*)cb_);
+ o->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);
+ o->tooltip("Click to show all files.");
+ }
+ { Fl_FileBrowser* o = fileList = new Fl_FileBrowser(10, 45, 355, 180);
+ o->callback((Fl_Callback*)cb_fileList);
+ Fl_Group::current()->resizable(o);
+ o->tooltip("Double-click to change directories.");
+ }
+ { Fl_FileInput* o = fileName = new Fl_FileInput(10, 245, 355, 25, "Filename:");
+ o->callback((Fl_Callback*)cb_fileName);
+ o->align(FL_ALIGN_TOP | FL_ALIGN_LEFT);
+ o->when(FL_WHEN_ENTER_KEY);
+ o->tooltip("Type a filename or directory name here.");
+ fileName->when(FL_WHEN_CHANGED | FL_WHEN_ENTER_KEY_ALWAYS);
+ }
+ { Fl_Return_Button* o = okButton = new Fl_Return_Button(240, 280, 55, 25, "OK");
+ o->shortcut(0xff0d);
+ o->callback((Fl_Callback*)cb_okButton);
+ }
+ { Fl_Button* o = new Fl_Button(300, 280, 65, 25, "Cancel");
+ o->callback((Fl_Callback*)cb_Cancel);
+ }
+ if (title) window->label(title);
+ o->set_modal();
+ o->end();
+ }
+ window->size_range(345, 270, 345);
+fileList->filter(p);
+type(t);
+value(d);
+}
+
+void Fl_FileChooser::color(Fl_Color c) {
+ fileList->color(c);
+}
+
+Fl_Color Fl_FileChooser::color() {
+ return (fileList->color());
+}
+
+char * Fl_FileChooser::directory() {
+ return directory_;
+}
+
+void Fl_FileChooser::filter(const char *p) {
+ fileList->filter(p);
+rescan();
+}
+
+const char * Fl_FileChooser::filter() {
+ return (fileList->filter());
+}
+
+void Fl_FileChooser::hide() {
+ window->hide();
+}
+
+void Fl_FileChooser::iconsize(uchar s) {
+ fileList->iconsize(s);
+}
+
+uchar Fl_FileChooser::iconsize() {
+ return (fileList->iconsize());
+}
+
+void Fl_FileChooser::label(const char *l) {
+ window->label(l);
+}
+
+const char * Fl_FileChooser::label() {
+ return (window->label());
+}
+
+void Fl_FileChooser::exec() {
+ window->exec();
+fileList->deselect();
+}
+
+void Fl_FileChooser::textcolor(Fl_Color c) {
+ fileList->textcolor(c);
+ fileList->text_color(c);
+}
+
+Fl_Color Fl_FileChooser::textcolor() {
+ return (fileList->textcolor());
+}
+
+void Fl_FileChooser::textfont(Fl_Font f) {
+ fileList->text_font(f);
+}
+
+Fl_Font Fl_FileChooser::textfont() {
+ return (fileList->text_font());
+}
+
+void Fl_FileChooser::textsize(uchar s) {
+ fileList->text_size(s);
+}
+
+uchar Fl_FileChooser::textsize() {
+ return (fileList->textsize());
+}
+
+void Fl_FileChooser::type(int t) {
+ type_ = t;
+if (t == MULTI)
+ fileList->type(FL_MULTI_BROWSER);
+else
+ fileList->type(FL_HOLD_BROWSER);
+if (t != CREATE)
+ newButton->deactivate();
+else
+ newButton->activate();
+}
+
+int Fl_FileChooser::type() {
+ return (type_);
+}
+
+int Fl_FileChooser::visible() {
+ return window->visible();
+}
diff --git a/src/Fl_FileChooser.fl b/src/Fl_FileChooser.fl
new file mode 100644
index 000000000..5fa2d22bd
--- /dev/null
+++ b/src/Fl_FileChooser.fl
@@ -0,0 +1,208 @@
+# data file for the FLTK User Interface Designer (FLUID)
+version 2.0000
+images_dir ./
+do_not_include_H_from_C
+header_name {.H}
+code_name {.cxx}
+gridx 5
+gridy 5
+snap 3
+decl {\#include <fltk/Fl_FileChooser.h>} {}
+
+decl {\#include <config.h>} {}
+
+class Fl_FileChooser {open
+} {
+ decl {enum { SINGLE, MULTI, CREATE };} {public
+ }
+ Function {Fl_FileChooser(const char *d, const char *p, int t, const char *title)} {open
+ } {
+ Fl_Window window {
+ label {Pick a File}
+ callback {fileList->deselect();
+fileName->value("");
+window->hide();} open
+ xywh {269 372 375 315} resizable hotspot
+ code0 {if (title) window->label(title);}
+ code1 {\#include <stdio.h>}
+ code2 {\#include <stdlib.h>}
+ code3 {\#include <string.h>} modal visible
+ } {
+ Fl_Choice dirMenu {
+ label {Directory:}
+ callback {if (dirMenu->value())
+ directory(dirMenu->text(dirMenu->value()));
+else
+ directory("");} open
+ tooltip {Click to access directory tree.}
+ private xywh {65 10 210 25}
+ code0 {o->set_flag(FL_ALIGN_LEFT | FL_ALIGN_RIGHT);
+ o->align(FL_ALIGN_LEFT | FL_ALIGN_RIGHT);}
+ } {}
+ Fl_Button upButton {
+ callback {up();}
+ tooltip {Click to display parent directory.}
+ private xywh {280 10 25 25} image not_inlined {up.xbm} labelsize 8
+ }
+ Fl_Button newButton {
+ callback {newdir();}
+ tooltip {Click to create a new directory.}
+ private xywh {310 10 25 25} image not_inlined {new.xbm} labelsize 8
+ }
+ Fl_Button {} {
+ callback {fileList->filter("*");;
+rescan();}
+ tooltip {Click to show all files.}
+ private xywh {340 10 25 25} align 524304 image not_inlined {allfiles.xbm} labelcolor 4 labelsize 28
+ }
+ Fl_Browser fileList {
+ callback {fileListCB();}
+ tooltip {Double-click to change directories.}
+ private xywh {10 45 355 180} resizable
+ code0 {\#include "filename.H"}
+ code1 {\#include <fltk/Fl_FileBrowser.h>}
+ class Fl_FileBrowser
+ }
+ Fl_Input fileName {
+ label {Filename:}
+ callback {fileNameCB();} selected
+ tooltip {Type a filename or directory name here.}
+ private xywh {10 245 355 25} align 5 when 8
+ code0 {fileName->when(FL_WHEN_CHANGED | FL_WHEN_ENTER_KEY_ALWAYS);}
+ code1 {\#include <fltk/Fl_FileInput.h>}
+ class Fl_FileInput
+ }
+ Fl_Return_Button okButton {
+ label OK
+ callback {char pathname[1024];
+
+snprintf(pathname, sizeof(pathname), "%s/%s",
+ fileList->directory(), fileName->value());
+if (filename_isdir(pathname))
+ directory(pathname);
+else
+ window->hide();}
+ private xywh {240 280 55 25} shortcut 0xff0d
+ }
+ Fl_Button {} {
+ label Cancel
+ callback {fileList->deselect();
+fileName->value("");
+window->hide();}
+ private xywh {300 280 65 25}
+ }
+ }
+ code {window->size_range(345, 270, 345);
+fileList->filter(p);
+type(t);
+value(d);} {}
+ }
+ decl {char directory_[1024];} {}
+ decl {int type_;} {}
+ decl {void fileListCB();} {}
+ decl {void fileNameCB();} {}
+ decl {void newdir();} {}
+ decl {void up();} {}
+ Function {color(Fl_Color c)} {} {
+ code {fileList->color(c);} {}
+ }
+ Function {color()} {return_type Fl_Color
+ } {
+ code {return (fileList->color());} {}
+ }
+ decl {int count();} {public
+ }
+ decl {void directory(const char *d);} {public
+ }
+ Function {directory()} {return_type {char *}
+ } {
+ code {return directory_;} {}
+ }
+ Function {filter(const char *p)} {return_type void
+ } {
+ code {fileList->filter(p);
+rescan();} {}
+ }
+ Function {filter()} {return_type {const char *}
+ } {
+ code {return (fileList->filter());} {}
+ }
+ Function {hide()} {return_type void
+ } {
+ code {window->hide();} {}
+ }
+ Function {iconsize(uchar s)} {return_type void
+ } {
+ code {fileList->iconsize(s);} {}
+ }
+ Function {iconsize()} {return_type uchar
+ } {
+ code {return (fileList->iconsize());} {}
+ }
+ Function {label(const char *l)} {return_type void
+ } {
+ code {window->label(l);} {}
+ }
+ Function {label()} {return_type {const char *}
+ } {
+ code {return (window->label());} {}
+ }
+ decl {void rescan();} {public
+ }
+ Function {exec()} {return_type void
+ } {
+ code {window->exec();
+fileList->deselect();} {}
+ }
+ Function {textcolor(Fl_Color c)} {return_type void
+ } {
+ code {fileList->textcolor(c);
+ fileList->text_color(c);} {}
+ }
+ Function {textcolor()} {return_type Fl_Color
+ } {
+ code {return (fileList->textcolor());
+ return (fileList->textcolor());} {}
+ }
+ Function {textfont(Fl_Font f)} {return_type void
+ } {
+ code {fileList->text_font(f);} {}
+ }
+ Function {textfont()} {return_type Fl_Font
+ } {
+ code {return (fileList->text_font());
+ return (fileList->textfont());} {}
+ }
+ Function {textsize(uchar s)} {return_type void
+ } {
+ code {fileList->text_size(s);} {}
+ }
+ Function {textsize()} {return_type uchar
+ } {
+ code {return (fileList->textsize());} {}
+ }
+ Function {type(int t)} {return_type void
+ } {
+ code {type_ = t;
+if (t == MULTI)
+ fileList->type(FL_MULTI_BROWSER);
+else
+ fileList->type(FL_HOLD_BROWSER);
+if (t != CREATE)
+ newButton->deactivate();
+else
+ newButton->activate();} {}
+ }
+ Function {type()} {return_type int
+ } {
+ code {return (type_);} {}
+ }
+ decl {const char *value(int f = 1);} {public
+ }
+ decl {void value(const char *filename);} {public
+ }
+ Function {visible()} {return_type int
+ } {
+ code {return window->visible();} {}
+ }
+}
diff --git a/src/Fl_FileChooser2.cxx b/src/Fl_FileChooser2.cxx
new file mode 100644
index 000000000..40837e910
--- /dev/null
+++ b/src/Fl_FileChooser2.cxx
@@ -0,0 +1,669 @@
+//
+// "$Id: Fl_FileChooser2.cxx,v 1.15 2001/07/29 22:04:43 spitzak Exp $"
+//
+// More Fl_FileChooser routines for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1997-2000 by Easy Software Products.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@easysw.com".
+//
+// Contents:
+//
+// Fl_FileChooser::directory() - Set the directory in the file chooser.
+// Fl_FileChooser::count() - Return the number of selected files.
+// Fl_FileChooser::value() - Return a selected filename.
+// Fl_FileChooser::up() - Go up one directory.
+// Fl_FileChooser::newdir() - Make a new directory.
+// Fl_FileChooser::rescan() - Rescan the current directory.
+// Fl_FileChooser::fileListCB() - Handle clicks (and double-clicks) in the
+// FileBrowser.
+// Fl_FileChooser::fileNameCB() - Handle text entry in the FileBrowser.
+//
+
+//
+// Include necessary headers.
+//
+
+#include <fltk/Fl_FileChooser.h>
+#include <fltk/filename.h>
+#include <fltk/fl_ask.h>
+#include <fltk/vsnprintf.h>
+#include <fltk/x.h>
+#include <config.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(_WIN32)
+# include <direct.h>
+# include <io.h>
+#else
+# include <unistd.h>
+# include <pwd.h>
+#endif /* _WIN32 */
+
+//
+// 'Fl_FileChooser::directory()' - Set the directory in the file chooser.
+//
+
+void
+Fl_FileChooser::directory(const char *d) // I - Directory to change to
+{
+ char pathname[1024], // Full path of directory
+ *pathptr, // Pointer into full path
+ *dirptr; // Pointer into directory
+ int levels; // Number of levels in directory
+
+
+ // NULL == current directory
+ if (d == NULL)
+ d = ".";
+
+ if (d[0] != '\0')
+ {
+ // Make the directory absolute...
+#if defined(_WIN32) || defined(__EMX__)
+ if (d[0] != '/' && d[0] != '\\' && d[1] != ':')
+#else
+ if (d[0] != '/' && d[0] != '\\')
+#endif /* _WIN32 || __EMX__ */
+ filename_absolute(directory_, d);
+ else
+ {
+ strncpy(directory_, d, sizeof(directory_) - 1);
+ directory_[sizeof(directory_) - 1] = '\0';
+ }
+
+ // Strip any trailing slash and/or period...
+ dirptr = directory_ + strlen(directory_) - 1;
+ if (*dirptr == '.')
+ *dirptr-- = '\0';
+ if ((*dirptr == '/' || *dirptr == '\\') && dirptr > directory_)
+ *dirptr = '\0';
+ }
+ else
+ directory_[0] = '\0';
+
+ // Clear the directory menu and fill it as needed...
+ dirMenu->clear();
+#if defined(_WIN32) || defined(__EMX__)
+ dirMenu->add("My Computer");
+#else
+ dirMenu->add("File Systems");
+#endif /* _WIN32 || __EMX__ */
+
+ levels = 0;
+ for (dirptr = directory_, pathptr = pathname; *dirptr != '\0';)
+ {
+ if (*dirptr == '/' || *dirptr == '\\')
+ {
+ // Need to quote the slash first, and then add it to the menu...
+ *pathptr++ = '\\';
+ *pathptr++ = '/';
+ *pathptr = '\0';
+ dirptr ++;
+
+ dirMenu->add(pathname);
+ levels ++;
+ }
+ else
+ *pathptr++ = *dirptr++;
+ }
+
+ if (pathptr > pathname)
+ {
+ *pathptr = '\0';
+ dirMenu->add(pathname);
+ levels ++;
+ }
+
+ dirMenu->value(levels);
+ dirMenu->redraw();
+
+ // Rescan the directory...
+ rescan();
+}
+
+
+//
+// 'Fl_FileChooser::count()' - Return the number of selected files.
+//
+
+int // O - Number of selected files
+Fl_FileChooser::count()
+{
+ int i; // Looping var
+ int count; // Number of selected files
+ const char *filename; // Filename in input field or list
+ char pathname[1024]; // Full path to file
+
+
+ if (type_ != MULTI)
+ {
+ // Check to see if the file name input field is blank...
+ filename = fileName->value();
+ if (filename == NULL || filename[0] == '\0')
+ return (0);
+
+ // Is the file name a directory?
+ if (directory_[0] != '\0')
+ snprintf(pathname, sizeof(pathname), "%s/%s", directory_, filename);
+ else
+ {
+ strncpy(pathname, filename, sizeof(pathname) - 1);
+ pathname[sizeof(pathname) - 1] = '\0';
+ }
+
+ if (filename_isdir(pathname))
+ return (0);
+ else
+ return (1);
+ }
+
+ for (i = 0, count = 0; i < fileList->size(); i ++)
+ if (fileList->selected(i))
+ {
+ // See if this file is a directory...
+ filename = (char *)fileList->text(i);
+ if (directory_[0] != '\0')
+ snprintf(pathname, sizeof(pathname), "%s/%s", directory_, filename);
+ else
+ {
+ strncpy(pathname, filename, sizeof(pathname) - 1);
+ pathname[sizeof(pathname) - 1] = '\0';
+ }
+
+ if (!filename_isdir(pathname))
+ count ++;
+ }
+
+ return (count);
+}
+
+
+//
+// 'Fl_FileChooser::value()' - Return a selected filename.
+//
+
+const char * // O - Filename or NULL
+Fl_FileChooser::value(int f) // I - File number
+{
+ int i; // Looping var
+ int count; // Number of selected files
+ const char *name; // Current filename
+ static char pathname[1024]; // Filename + directory
+
+
+ if (type_ != MULTI)
+ {
+ name = fileName->value();
+ if (name[0] == '\0')
+ return (NULL);
+
+ snprintf(pathname, sizeof(pathname), "%s/%s", directory_, name);
+ return ((const char *)pathname);
+ }
+
+ for (i = 0, count = 0; i < fileList->size(); i ++)
+ if (fileList->selected(i))
+ {
+ // See if this file is a directory...
+ name = fileList->text(i);
+ snprintf(pathname, sizeof(pathname), "%s/%s", directory_, name);
+
+ if (!filename_isdir(pathname))
+ {
+ // Nope, see if this this is "the one"...
+ count ++;
+ if (count == f)
+ return ((const char *)pathname);
+ }
+ }
+
+ return (NULL);
+}
+
+
+//
+// 'Fl_FileChooser::value()' - Set the current filename.
+//
+
+void
+Fl_FileChooser::value(const char *filename) // I - Filename + directory
+{
+ int i, // Looping var
+ count; // Number of items in list
+ char *slash; // Directory separator
+ char pathname[1024]; // Local copy of filename
+
+
+ // See if the filename is actually a directory...
+ if (filename == NULL || filename_isdir(filename))
+ {
+ // Yes, just change the current directory...
+ directory(filename);
+ return;
+ }
+
+ if (!filename[0])
+ {
+ // Just show the current directory...
+ directory(NULL);
+ return;
+ }
+
+ // Switch to single-selection mode as needed
+ if (type_ == MULTI)
+ type(SINGLE);
+
+ // See if there is a directory in there...
+ strncpy(pathname, filename, sizeof(pathname) - 1);
+ pathname[sizeof(pathname) - 1] = '\0';
+
+ if ((slash = strrchr(pathname, '/')) == NULL)
+ slash = strrchr(pathname, '\\');
+
+ if (slash != NULL)
+ {
+ // Yes, change the display to the directory...
+ *slash++ = '\0';
+ directory(pathname);
+ }
+ else
+ {
+ directory(NULL);
+ slash = pathname;
+ }
+
+ // Set the input field to the remaining portion
+ fileName->value(slash);
+ fileName->position(0, strlen(slash));
+ okButton->activate();
+
+ // Then find the file in the file list and select it...
+ count = fileList->size();
+
+ for (i = 0; i < count; i ++)
+ if (strcmp(fileList->text(i), slash) == 0)
+ {
+ fileList->select(i);
+ break;
+ }
+}
+
+
+//
+// 'Fl_FileChooser::up()' - Go up one directory.
+//
+
+void
+Fl_FileChooser::up()
+{
+ char *slash; // Trailing slash
+
+
+ if ((slash = strrchr(directory_, '/')) == NULL)
+ slash = strrchr(directory_, '\\');
+
+ if (directory_[0] != '\0')
+ dirMenu->value(dirMenu->value() - 1);
+
+ if (slash != NULL)
+ *slash = '\0';
+ else
+ {
+ upButton->deactivate();
+ directory_[0] = '\0';
+ }
+
+ rescan();
+}
+
+
+//
+// 'Fl_FileChooser::newdir()' - Make a new directory.
+//
+
+void
+Fl_FileChooser::newdir()
+{
+ const char *dir; // New directory name
+ char pathname[1024]; // Full path of directory
+
+
+ // Get a directory name from the user
+ if ((dir = fl_input("New Directory?")) == NULL)
+ return;
+
+ // Make it relative to the current directory as needed...
+#if defined(_WIN32) || defined(__EMX__)
+ if (dir[0] != '/' && dir[0] != '\\' && dir[1] != ':')
+#else
+ if (dir[0] != '/' && dir[0] != '\\')
+#endif /* _WIN32 || __EMX__ */
+ snprintf(pathname, sizeof(pathname), "%s/%s", directory_, dir);
+ else
+ {
+ strncpy(pathname, dir, sizeof(pathname) - 1);
+ pathname[sizeof(pathname) - 1] = '\0';
+ }
+
+ // Create the directory; ignore EEXIST errors...
+#if defined(_WIN32)
+ if (mkdir(pathname))
+#else
+ if (mkdir(pathname, 0777))
+#endif /* _WIN32 || __EMX__ */
+ if (errno != EEXIST)
+ {
+ fl_alert("Unable to create directory!");
+ return;
+ }
+
+ // Show the new directory...
+ directory(pathname);
+}
+
+
+//
+// 'Fl_FileChooser::rescan()' - Rescan the current directory.
+//
+
+void
+Fl_FileChooser::rescan()
+{
+ // Clear the current filename
+ fileName->value("");
+ okButton->deactivate();
+
+ // Build the file list...
+ fileList->load(directory_);
+ fileList->redraw();
+}
+
+
+//
+// 'Fl_FileChooser::fileListCB()' - Handle clicks (and double-clicks) in the
+// FileBrowser.
+//
+
+void
+Fl_FileChooser::fileListCB()
+{
+ char filename[1024], // New filename
+ pathname[1024]; // Full pathname to file
+
+
+ strncpy(filename, fileList->text(fileList->value()), sizeof(filename) - 1);
+ filename[sizeof(filename) - 1] = '\0';
+
+#if defined(_WIN32) || defined(__EMX__)
+ if (directory_[0] != '\0' && filename[0] != '/' && filename[0] != '\\' &&
+ !(isalpha(filename[0]) && filename[1] == ':'))
+ snprintf(pathname, sizeof(pathname), "%s/%s", directory_, filename);
+ else
+ {
+ strncpy(pathname, filename, sizeof(pathname) - 1);
+ pathname[sizeof(pathname) - 1] = '\0';
+ }
+#else
+ if (directory_[0] != '\0' && filename[0] != '/')
+ snprintf(pathname, sizeof(pathname), "%s/%s", directory_, filename);
+ else
+ {
+ strncpy(pathname, filename, sizeof(pathname) - 1);
+ pathname[sizeof(pathname) - 1] = '\0';
+ }
+#endif /* _WIN32 || __EMX__ */
+
+ if (Fl::event_clicks() || Fl::event_key() == FL_Enter)
+ {
+ puts("double-click");
+ if (filename_isdir(pathname))
+ {
+ puts("directory");
+ directory(pathname);
+ upButton->activate();
+ }
+ else
+ window->hide();
+ }
+ else
+ {
+ fileName->value(filename);
+ okButton->activate();
+ }
+}
+
+
+//
+// 'Fl_FileChooser::fileNameCB()' - Handle text entry in the FileBrowser.
+//
+
+void
+Fl_FileChooser::fileNameCB()
+{
+ char *filename, // New filename
+ *slash, // Pointer to trailing slash
+ pathname[1024]; // Full pathname to file
+ int i, // Looping var
+ min_match, // Minimum number of matching chars
+ max_match, // Maximum number of matching chars
+ num_files, // Number of files in directory
+ first_line; // First matching line
+ const char *file; // File from directory
+
+
+ // Get the filename from the text field...
+ filename = (char *)fileName->value();
+
+ if (filename == NULL || filename[0] == '\0')
+ {
+ okButton->deactivate();
+ return;
+ }
+
+#if defined(_WIN32) || defined(__EMX__)
+ if (directory_[0] != '\0' && filename[0] != '/' && filename[0] != '\\' &&
+ !(isalpha(filename[0]) && filename[1] == ':'))
+ snprintf(pathname, sizeof(pathname), "%s/%s", directory_, filename);
+ else
+ {
+ strncpy(pathname, filename, sizeof(pathname) - 1);
+ pathname[sizeof(pathname) - 1] = '\0';
+ }
+#else
+ if (filename[0] == '~')
+ {
+ // Lookup user...
+ struct passwd *pwd;
+
+ if (!filename[1] || filename[1] == '/')
+ pwd = getpwuid(getuid());
+ else
+ {
+ strncpy(pathname, filename + 1, sizeof(pathname) - 1);
+ pathname[sizeof(pathname) - 1] = '\0';
+
+ i = strlen(pathname) - 1;
+ if (pathname[i] == '/')
+ pathname[i] = '\0';
+
+ pwd = getpwnam(pathname);
+ }
+
+ if (pwd)
+ {
+ strncpy(pathname, pwd->pw_dir, sizeof(pathname) - 1);
+ pathname[sizeof(pathname) - 1] = '\0';
+
+ if (filename[strlen(filename) - 1] == '/')
+ strncat(pathname, "/", sizeof(pathname) - strlen(pathname) - 1);
+ }
+ else
+ snprintf(pathname, sizeof(pathname), "%s/%s", directory_, filename);
+
+ endpwent();
+ }
+ else if (directory_[0] != '\0' && filename[0] != '/')
+ snprintf(pathname, sizeof(pathname), "%s/%s", directory_, filename);
+ else
+ {
+ strncpy(pathname, filename, sizeof(pathname) - 1);
+ pathname[sizeof(pathname) - 1] = '\0';
+ }
+#endif /* _WIN32 || __EMX__ */
+
+ if (Fl::event_key() == FL_Enter)
+ {
+ // Enter pressed - select or change directory...
+ if (filename_isdir(pathname))
+ directory(pathname);
+ else if (type_ == CREATE || access(pathname, 0) == 0)
+ {
+ // New file or file exists... If we are in multiple selection mode,
+ // switch to single selection mode...
+ if (type_ == MULTI)
+ type(SINGLE);
+
+ // Hide the window to signal things are done...
+ window->hide();
+ }
+ else
+ {
+ // File doesn't exist, so alert the user...
+ fl_alert("Please choose an existing file!");
+ }
+ }
+ else if (Fl::event_key() != FL_Delete)
+ {
+ // Check to see if the user has entered a directory...
+ if ((slash = strrchr(filename, '/')) == NULL)
+ slash = strrchr(filename, '\\');
+
+ if (slash != NULL)
+ {
+ // Yes, change directories and update the file name field...
+ if ((slash = strrchr(pathname, '/')) == NULL)
+ slash = strrchr(pathname, '\\');
+
+ if (slash > pathname) // Special case for "/"
+ *slash++ = '\0';
+ else
+ slash++;
+
+ if (strcmp(filename, "../") == 0) // Special case for "../"
+ up();
+ else
+ directory(pathname);
+
+ // If the string ended after the slash, we're done for now...
+ if (*slash == '\0')
+ return;
+
+ // Otherwise copy the remainder and proceed...
+ fileName->value(slash);
+ fileName->position(strlen(slash));
+ filename = slash;
+ }
+
+ // Other key pressed - do filename completion as possible...
+ num_files = fileList->size();
+ min_match = strlen(filename);
+ max_match = 100000;
+ first_line = 0;
+
+ for (i = 0; i < num_files && max_match > min_match; i ++)
+ {
+ file = fileList->text(i);
+
+#if defined(_WIN32) || defined(__EMX__)
+ if (strnicmp(filename, file, min_match) == 0)
+#else
+ if (strncmp(filename, file, min_match) == 0)
+#endif // _WIN32 || __EMX__
+ {
+ // OK, this one matches; check against the previous match
+ if (max_match == 100000)
+ {
+ // First match; copy stuff over...
+ strncpy(pathname, file, sizeof(pathname) - 1);
+ pathname[sizeof(pathname) - 1] = '\0';
+ max_match = strlen(pathname);
+
+ // And then make sure that the item is visible
+ fileList->topline(i);
+ first_line = i;
+ }
+ else
+ {
+ // Succeeding match; compare to find maximum string match...
+ while (max_match > min_match)
+#if defined(_WIN32) || defined(__EMX__)
+ if (strnicmp(file, pathname, max_match) == 0)
+#else
+ if (strncmp(file, pathname, max_match) == 0)
+#endif // _WIN32 || __EMX__
+ break;
+ else
+ max_match --;
+
+ // Truncate the string as needed...
+ pathname[max_match] = '\0';
+ }
+ }
+ }
+
+ fileList->deselect(0);
+ fileList->redraw();
+
+ // If we have any matches, add them to the input field...
+ if (first_line > 0 && min_match == max_match &&
+ max_match == (int)strlen(fileList->text(first_line)))
+ fileList->select(first_line);
+ else if (max_match > min_match && max_match != 100000)
+ {
+ // Add the matching portion...
+ fileName->replace(0, min_match, pathname, strlen(pathname));
+
+ // Highlight it; if the user just pressed the backspace
+ // key, position the cursor at the start of the selection.
+ // Otherwise, put the cursor at the end of the selection so
+ // s/he can press the right arrow to accept the selection
+ // (Tab and End also do this for both cases.)
+ if (Fl::event_key() == FL_BackSpace)
+ fileName->position(min_match - 1, max_match);
+ else
+ fileName->position(max_match, min_match);
+ }
+
+ // See if we need to enable the OK button...
+ snprintf(pathname, sizeof(pathname), "%s/%s", directory_, fileName->value());
+
+ if (type_ == CREATE || access(pathname, 0) == 0)
+ okButton->activate();
+ else
+ okButton->deactivate();
+ }
+}
+
+
+//
+// End of "$Id: Fl_FileChooser2.cxx,v 1.15 2001/07/29 22:04:43 spitzak Exp $".
+//
diff --git a/src/Fl_FileIcon.cxx b/src/Fl_FileIcon.cxx
new file mode 100644
index 000000000..20a947223
--- /dev/null
+++ b/src/Fl_FileIcon.cxx
@@ -0,0 +1,329 @@
+//
+// "$Id: Fl_FileIcon.cxx,v 1.10 2001/07/29 22:04:43 spitzak Exp $"
+//
+// Fl_FileIcon routines for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1997-1999 by Easy Software Products.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@easysw.com".
+//
+// Contents:
+//
+// Fl_FileIcon::Fl_FileIcon() - Create a new file icon.
+// Fl_FileIcon::~Fl_FileIcon() - Remove a file icon.
+// Fl_FileIcon::add() - Add data to an icon.
+// Fl_FileIcon::find() - Find an icon based upon a given file.
+// Fl_FileIcon::draw() - Draw an icon.
+//
+
+//
+// Include necessary header files...
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef _WIN32
+# include <io.h>
+# define F_OK 0
+#else
+# include <unistd.h>
+#endif
+
+#include <fltk/Fl_FileIcon.h>
+#include <fltk/Fl_Widget.h>
+#include <fltk/fl_draw.h>
+#include <fltk/filename.h>
+
+
+//
+// Define missing POSIX/XPG4 macros as needed...
+//
+
+#ifndef S_ISDIR
+# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
+# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
+# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
+# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
+#endif /* !S_ISDIR */
+
+
+//
+// Icon cache...
+//
+
+Fl_FileIcon *Fl_FileIcon::first_ = (Fl_FileIcon *)0;
+
+
+//
+// 'Fl_FileIcon::Fl_FileIcon()' - Create a new file icon.
+//
+
+Fl_FileIcon::Fl_FileIcon(const char *p, /* I - Filename pattern */
+ int t, /* I - File type */
+ int nd, /* I - Number of data values */
+ short *d) /* I - Data values */
+{
+ // Initialize the pattern and type...
+ pattern_ = p;
+ type_ = t;
+
+ // Copy icon data as needed...
+ if (nd)
+ {
+ num_data_ = nd;
+ alloc_data_ = nd + 1;
+ data_ = (short *)calloc(sizeof(short), nd + 1);
+ memcpy(data_, d, nd * sizeof(short));
+ }
+ else
+ {
+ num_data_ = 0;
+ alloc_data_ = 0;
+ }
+
+ // And add the icon to the list of icons...
+ next_ = first_;
+ first_ = this;
+}
+
+
+//
+// 'Fl_FileIcon::~Fl_FileIcon()' - Remove a file icon.
+//
+
+Fl_FileIcon::~Fl_FileIcon()
+{
+ Fl_FileIcon *current, // Current icon in list
+ *prev; // Previous icon in list
+
+
+ // Find the icon in the list...
+ for (current = first_, prev = (Fl_FileIcon *)0;
+ current != this && current != (Fl_FileIcon *)0;
+ prev = current, current = current->next_);
+
+ // Remove the icon from the list as needed...
+ if (current)
+ {
+ if (prev)
+ prev->next_ = current->next_;
+ else
+ first_ = current->next_;
+ }
+
+ // Free any memory used...
+ if (alloc_data_)
+ free(data_);
+}
+
+
+//
+// 'Fl_FileIcon::add()' - Add data to an icon.
+//
+
+short * // O - Pointer to new data value
+Fl_FileIcon::add(short d) // I - Data to add
+{
+ short *dptr; // Pointer to new data value
+
+
+ // Allocate/reallocate memory as needed
+ if ((num_data_ + 1) >= alloc_data_)
+ {
+ alloc_data_ += 128;
+
+ if (alloc_data_ == 128)
+ dptr = (short *)malloc(sizeof(short) * alloc_data_);
+ else
+ dptr = (short *)realloc(data_, sizeof(short) * alloc_data_);
+
+ if (dptr == NULL)
+ return (NULL);
+
+ data_ = dptr;
+ }
+
+ // Store the new data value and return
+ data_[num_data_++] = d;
+ data_[num_data_] = END;
+
+ return (data_ + num_data_ - 1);
+}
+
+
+//
+// 'Fl_FileIcon::find()' - Find an icon based upon a given file.
+//
+
+Fl_FileIcon * // O - Matching file icon or NULL
+Fl_FileIcon::find(const char *filename, // I - Name of file */
+ int filetype) // I - Enumerated file type
+{
+ Fl_FileIcon *current; // Current file in list
+ struct stat fileinfo; // Information on file
+
+
+ // Get file information if needed...
+ if (filetype == ANY)
+ if (!stat(filename, &fileinfo))
+ {
+ if (S_ISDIR(fileinfo.st_mode))
+ filetype = DIR;
+#ifdef S_IFIFO
+ else if (S_ISFIFO(fileinfo.st_mode))
+ filetype = FIFO;
+#endif // S_IFIFO
+#if defined(S_ICHR) && defined(S_IBLK)
+ else if (S_ISCHR(fileinfo.st_mode) || S_ISBLK(fileinfo.st_mode))
+ filetype = DEVICE;
+#endif // S_ICHR && S_IBLK
+#ifdef S_ILNK
+ else if (S_ISLNK(fileinfo.st_mode))
+ filetype = LINK;
+#endif // S_ILNK
+ else
+ filetype = PLAIN;
+ }
+
+ // Loop through the available file types and return any match that
+ // is found...
+ for (current = first_; current != (Fl_FileIcon *)0; current = current->next_)
+ if ((current->type_ == filetype || current->type_ == ANY) &&
+ filename_match(filename, current->pattern_))
+ break;
+
+ // Return the match (if any)...
+ return (current);
+}
+
+
+//
+// 'Fl_FileIcon::draw()' - Draw an icon.
+//
+
+void
+Fl_FileIcon::draw(int x, // I - Upper-lefthand X
+ int y, // I - Upper-lefthand Y
+ int w, // I - Width of bounding box
+ int h, // I - Height of bounding box
+ Fl_Color ic, // I - Icon color...
+ int active) // I - Active or inactive?
+{
+ Fl_Color c; // Current color
+ short *d; // Pointer to data
+ short *prim; // Pointer to start of primitive...
+ double scale; // Scale of icon
+
+
+ // Don't try to draw a NULL array!
+ if (num_data_ == 0)
+ return;
+
+ // Setup the transform matrix as needed...
+ scale = w < h ? w : h;
+
+ fl_push_matrix();
+ fl_translate((float)x + 0.5 * ((float)w - scale),
+ (float)y + 0.5 * ((float)h + scale));
+ fl_scale(scale, -scale);
+
+ // Loop through the array until we see an unmatched END...
+ d = data_;
+ prim = NULL;
+ c = ic;
+
+ if (active)
+ fl_color(c);
+ else
+ fl_color(fl_inactive(c));
+
+ while (*d != END || prim)
+ switch (*d)
+ {
+ case END :
+ switch (*prim)
+ {
+ case LINE :
+ fl_stroke();
+ break;
+
+ case CLOSEDLINE :
+ fl_closepath();
+ fl_stroke();
+ break;
+
+ case POLYGON :
+ fl_fill();
+ break;
+
+ case OUTLINEPOLYGON : {
+ Fl_Color color = prim[1]==256 ? ic : (Fl_Color)prim[1];
+ if (!active) color = fl_inactive(color);
+ fl_fill_stroke(color);
+ break;}
+ }
+
+ prim = NULL;
+ d ++;
+ break;
+
+ case COLOR :
+ if (d[1] == 256)
+ c = ic;
+ else
+ c = (Fl_Color)d[1];
+
+ if (!active)
+ c = fl_inactive(c);
+
+ fl_color(c);
+ d += 2;
+ break;
+
+ case LINE :
+ case CLOSEDLINE :
+ case POLYGON :
+ prim = d;
+ d ++;
+ break;
+
+ case OUTLINEPOLYGON :
+ prim = d;
+ d += 2;
+ break;
+
+ case VERTEX :
+ if (prim)
+ fl_vertex(d[1] * 0.0001, d[2] * 0.0001);
+ d += 3;
+ break;
+ }
+
+ // Restore the transform matrix
+ fl_pop_matrix();
+}
+
+
+//
+// End of "$Id: Fl_FileIcon.cxx,v 1.10 2001/07/29 22:04:43 spitzak Exp $".
+//
diff --git a/src/Fl_Text_Buffer.cxx b/src/Fl_Text_Buffer.cxx
new file mode 100644
index 000000000..70b2e7db0
--- /dev/null
+++ b/src/Fl_Text_Buffer.cxx
@@ -0,0 +1,2287 @@
+//
+// "$Id: Fl_Text_Buffer.cxx,v 1.9 2001/07/23 09:50:05 spitzak Exp $"
+//
+// Copyright Mark Edel. Permission to distribute under the LGPL for
+// the FLTK library granted by Mark Edel.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@fltk.org".
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <fltk/Fl_Text_Buffer.h>
+
+
+#define PREFERRED_GAP_SIZE 80
+/* Initial size for the buffer gap (empty space
+in the buffer where text might be inserted
+if the user is typing sequential chars ) */
+
+static void histogramCharacters( const char *string, int length, char hist[ 256 ],
+ int init );
+static void subsChars( char *string, int length, char fromChar, char toChar );
+static char chooseNullSubsChar( char hist[ 256 ] );
+static void insertColInLine( const char *line, char *insLine, int column, int insWidth,
+ int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
+ int *endOffset );
+static void deleteRectFromLine( const char *line, int rectStart, int rectEnd,
+ int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
+ int *endOffset );
+static void overlayRectInLine( const char *line, char *insLine, int rectStart,
+ int rectEnd, int tabDist, int useTabs, char nullSubsChar, char *outStr,
+ int *outLen, int *endOffset );
+
+static void addPadding( char *string, int startIndent, int toIndent,
+ int tabDist, int useTabs, char nullSubsChar, int *charsAdded );
+static char *copyLine( const char* text, int *lineLen );
+static int countLines( const char *string );
+static int textWidth( const char *text, int tabDist, char nullSubsChar );
+static char *realignTabs( const char *text, int origIndent, int newIndent,
+ int tabDist, int useTabs, char nullSubsChar, int *newLength );
+static char *expandTabs( const char *text, int startIndent, int tabDist,
+ char nullSubsChar, int *newLen );
+static char *unexpandTabs( char *text, int startIndent, int tabDist,
+ char nullSubsChar, int *newLen );
+static int max( int i1, int i2 );
+static int min( int i1, int i2 );
+
+static const char *ControlCodeTable[ 32 ] = {
+ "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel",
+ "bs", "ht", "nl", "vt", "np", "cr", "so", "si",
+ "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb",
+ "can", "em", "sub", "esc", "fs", "gs", "rs", "us"};
+
+/*
+** Create an empty text buffer of a pre-determined size (use this to
+** avoid unnecessary re-allocation if you know exactly how much the buffer
+** will need to hold
+*/
+Fl_Text_Buffer::Fl_Text_Buffer( int requestedSize ) {
+ mLength = 0;
+ mBuf = (char *)malloc( requestedSize + PREFERRED_GAP_SIZE );
+ mGapStart = 0;
+ mGapEnd = PREFERRED_GAP_SIZE;
+ mTabDist = 8;
+ mUseTabs = 1;
+ mPrimary.mSelected = 0;
+ mPrimary.mRectangular = 0;
+ mPrimary.mStart = mPrimary.mEnd = 0;
+ mSecondary.mSelected = 0;
+ mSecondary.mStart = mSecondary.mEnd = 0;
+ mSecondary.mRectangular = 0;
+ mHighlight.mSelected = 0;
+ mHighlight.mStart = mHighlight.mEnd = 0;
+ mHighlight.mRectangular = 0;
+ mNodifyProcs = NULL;
+ mCbArgs = NULL;
+ mNModifyProcs = 0;
+ mNullSubsChar = '\0';
+#ifdef PURIFY
+{ int i; for (i = mGapStart; i < mGapEnd; i++) mBuf[ i ] = '.'; }
+#endif
+}
+
+/*
+** Free a text buffer
+*/
+Fl_Text_Buffer::~Fl_Text_Buffer() {
+ free( mBuf );
+ if ( mNModifyProcs != 0 ) {
+ free( ( void * ) mNodifyProcs );
+ free( ( void * ) mCbArgs );
+ }
+}
+
+/*
+** Get the entire contents of a text buffer. Memory is allocated to contain
+** the returned string, which the caller must free.
+*/
+const char * Fl_Text_Buffer::text() {
+ char *t;
+
+ t = (char *)malloc( mLength + 1 );
+ memcpy( t, mBuf, mGapStart );
+ memcpy( &t[ mGapStart ], &mBuf[ mGapEnd ],
+ mLength - mGapStart );
+ t[ mLength ] = '\0';
+ return t;
+}
+
+/*
+** Replace the entire contents of the text buffer
+*/
+void Fl_Text_Buffer::text( const char *t ) {
+ int length, deletedLength;
+ const char *deletedText;
+
+ /* Save information for redisplay, and get rid of the old buffer */
+ deletedText = text();
+ deletedLength = mLength;
+ free( (void *)mBuf );
+
+ /* Start a new buffer with a gap of PREFERRED_GAP_SIZE in the center */
+ length = strlen( t );
+ mBuf = (char *)malloc( length + PREFERRED_GAP_SIZE );
+ mLength = length;
+ mGapStart = length / 2;
+ mGapEnd = mGapStart + PREFERRED_GAP_SIZE;
+ memcpy( mBuf, t, mGapStart );
+ memcpy( &mBuf[ mGapEnd ], &t[ mGapStart ], length - mGapStart );
+#ifdef PURIFY
+{ int i; for ( i = mGapStart; i < mGapEnd; i++ ) mBuf[ i ] = '.'; }
+#endif
+
+ /* Zero all of the existing selections */
+ update_selections( 0, deletedLength, 0 );
+
+ /* Call the saved display routine(s) to update the screen */
+ call_modify_callbacks( 0, deletedLength, length, 0, deletedText );
+ free( (void *)deletedText );
+}
+
+/*
+** Return a copy of the text between "start" and "end" character positions
+** from text buffer "buf". Positions start at 0, and the range does not
+** include the character pointed to by "end"
+*/
+const char * Fl_Text_Buffer::text_range( int start, int end ) {
+ char * text;
+ int length, part1Length;
+
+ /* Make sure start and end are ok, and allocate memory for returned string.
+ If start is bad, return "", if end is bad, adjust it. */
+ if ( start < 0 || start > mLength ) {
+ text = (char *)malloc( 1 );
+ text[ 0 ] = '\0';
+ return text;
+ }
+ if ( end < start ) {
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+ if ( end > mLength )
+ end = mLength;
+ length = end - start;
+ text = (char *)malloc( length + 1 );
+
+ /* Copy the text from the buffer to the returned string */
+ if ( end <= mGapStart ) {
+ memcpy( text, &mBuf[ start ], length );
+ } else if ( start >= mGapStart ) {
+ memcpy( text, &mBuf[ start + ( mGapEnd - mGapStart ) ], length );
+ } else {
+ part1Length = mGapStart - start;
+ memcpy( text, &mBuf[ start ], part1Length );
+ memcpy( &text[ part1Length ], &mBuf[ mGapEnd ], length - part1Length );
+ }
+ text[ length ] = '\0';
+ return text;
+}
+
+/*
+** Return the character at buffer position "pos". Positions start at 0.
+*/
+char Fl_Text_Buffer::character( int pos ) {
+ if ( pos < 0 || pos > mLength )
+ return '\0';
+ if ( pos < mGapStart )
+ return mBuf[ pos ];
+ else
+ return mBuf[ pos + mGapEnd - mGapStart ];
+}
+
+/*
+** Insert null-terminated string "text" at position "pos" in "buf"
+*/
+void Fl_Text_Buffer::insert( int pos, const char *text ) {
+ int nInserted;
+
+ /* if pos is not contiguous to existing text, make it */
+ if ( pos > mLength ) pos = mLength;
+ if ( pos < 0 ) pos = 0;
+
+ /* insert and redisplay */
+ nInserted = insert_( pos, text );
+ mCursorPosHint = pos + nInserted;
+ call_modify_callbacks( pos, 0, nInserted, 0, NULL );
+}
+
+/*
+** Delete the characters between "start" and "end", and insert the
+** null-terminated string "text" in their place in in "buf"
+*/
+void Fl_Text_Buffer::replace( int start, int end, const char *text ) {
+ const char * deletedText;
+ int nInserted;
+
+ deletedText = text_range( start, end );
+ remove_( start, end );
+ nInserted = insert_( start, text );
+ mCursorPosHint = start + nInserted;
+ call_modify_callbacks( start, end - start, nInserted, 0, deletedText );
+ free( (void *)deletedText );
+}
+
+void Fl_Text_Buffer::remove( int start, int end ) {
+ const char * deletedText;
+
+ /* Make sure the arguments make sense */
+ if ( start > end ) {
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+ if ( start > mLength ) start = mLength;
+ if ( start < 0 ) start = 0;
+ if ( end > mLength ) end = mLength;
+ if ( end < 0 ) end = 0;
+
+ /* Remove and redisplay */
+ deletedText = text_range( start, end );
+ remove_( start, end );
+ mCursorPosHint = start;
+ call_modify_callbacks( start, end - start, 0, 0, deletedText );
+ free( (void *)deletedText );
+}
+
+void Fl_Text_Buffer::copy( Fl_Text_Buffer *fromBuf, int fromStart,
+ int fromEnd, int toPos ) {
+ int length = fromEnd - fromStart;
+ int part1Length;
+
+ /* Prepare the buffer to receive the new text. If the new text fits in
+ the current buffer, just move the gap (if necessary) to where
+ the text should be inserted. If the new text is too large, reallocate
+ the buffer with a gap large enough to accomodate the new text and a
+ gap of PREFERRED_GAP_SIZE */
+ if ( length > mGapEnd - mGapStart )
+ reallocate_with_gap( toPos, length + PREFERRED_GAP_SIZE );
+ else if ( toPos != mGapStart )
+ move_gap( toPos );
+
+ /* Insert the new text (toPos now corresponds to the start of the gap) */
+ if ( fromEnd <= fromBuf->mGapStart ) {
+ memcpy( &mBuf[ toPos ], &fromBuf->mBuf[ fromStart ], length );
+ } else if ( fromStart >= fromBuf->mGapStart ) {
+ memcpy( &mBuf[ toPos ],
+ &fromBuf->mBuf[ fromStart + ( fromBuf->mGapEnd - fromBuf->mGapStart ) ],
+ length );
+ } else {
+ part1Length = fromBuf->mGapStart - fromStart;
+ memcpy( &mBuf[ toPos ], &fromBuf->mBuf[ fromStart ], part1Length );
+ memcpy( &mBuf[ toPos + part1Length ], &fromBuf->mBuf[ fromBuf->mGapEnd ],
+ length - part1Length );
+ }
+ mGapStart += length;
+ mLength += length;
+ update_selections( toPos, 0, length );
+}
+
+/*
+** Insert "text" columnwise into buffer starting at displayed character
+** position "column" on the line beginning at "startPos". Opens a rectangular
+** space the width and height of "text", by moving all text to the right of
+** "column" right. If charsInserted and charsDeleted are not NULL, the
+** number of characters inserted and deleted in the operation (beginning
+** at startPos) are returned in these arguments
+*/
+void Fl_Text_Buffer::insert_column( int column, int startPos, const char *text,
+ int *charsInserted, int *charsDeleted ) {
+ int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
+ const char *deletedText;
+
+ nLines = countLines( text );
+ lineStartPos = line_start( startPos );
+ nDeleted = line_end( skip_lines( startPos, nLines ) ) -
+ lineStartPos;
+ deletedText = text_range( lineStartPos, lineStartPos + nDeleted );
+ insert_column_( column, lineStartPos, text, &insertDeleted, &nInserted,
+ &mCursorPosHint );
+ if ( nDeleted != insertDeleted )
+ fprintf( stderr, "internal consistency check ins1 failed" );
+ call_modify_callbacks( lineStartPos, nDeleted, nInserted, 0, deletedText );
+ free( (void *) deletedText );
+ if ( charsInserted != NULL )
+ * charsInserted = nInserted;
+ if ( charsDeleted != NULL )
+ * charsDeleted = nDeleted;
+}
+
+/*
+** Overlay "text" between displayed character positions "rectStart" and
+** "rectEnd" on the line beginning at "startPos". If charsInserted and
+** charsDeleted are not NULL, the number of characters inserted and deleted
+** in the operation (beginning at startPos) are returned in these arguments.
+*/
+void Fl_Text_Buffer::overlay_rectangular( int startPos, int rectStart,
+ int rectEnd, const char *text, int *charsInserted, int *charsDeleted ) {
+ int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
+ const char *deletedText;
+
+ nLines = countLines( text );
+ lineStartPos = line_start( startPos );
+ nDeleted = line_end( skip_lines( startPos, nLines ) ) -
+ lineStartPos;
+ deletedText = text_range( lineStartPos, lineStartPos + nDeleted );
+ overlay_rectangular_( lineStartPos, rectStart, rectEnd, text, &insertDeleted,
+ &nInserted, &mCursorPosHint );
+ if ( nDeleted != insertDeleted )
+ fprintf( stderr, "internal consistency check ovly1 failed" );
+ call_modify_callbacks( lineStartPos, nDeleted, nInserted, 0, deletedText );
+ free( (void *) deletedText );
+ if ( charsInserted != NULL )
+ * charsInserted = nInserted;
+ if ( charsDeleted != NULL )
+ * charsDeleted = nDeleted;
+}
+
+/*
+** Replace a rectangular area in buf, given by "start", "end", "rectStart",
+** and "rectEnd", with "text". If "text" is vertically longer than the
+** rectangle, add extra lines to make room for it.
+*/
+void Fl_Text_Buffer::replace_rectangular( int start, int end, int rectStart,
+ int rectEnd, const char *text ) {
+ char *insPtr;
+ const char *deletedText;
+ char *insText = "";
+ int i, nInsertedLines, nDeletedLines, insLen, hint;
+ int insertDeleted, insertInserted, deleteInserted;
+ int linesPadded = 0;
+
+ /* Make sure start and end refer to complete lines, since the
+ columnar delete and insert operations will replace whole lines */
+ start = line_start( start );
+ end = line_end( end );
+
+ /* If more lines will be deleted than inserted, pad the inserted text
+ with newlines to make it as long as the number of deleted lines. This
+ will indent all of the text to the right of the rectangle to the same
+ column. If more lines will be inserted than deleted, insert extra
+ lines in the buffer at the end of the rectangle to make room for the
+ additional lines in "text" */
+ nInsertedLines = countLines( text );
+ nDeletedLines = count_lines( start, end );
+ if ( nInsertedLines < nDeletedLines ) {
+ insLen = strlen( text );
+ insText = (char *)malloc( insLen + nDeletedLines - nInsertedLines + 1 );
+ strcpy( insText, text );
+ insPtr = insText + insLen;
+ for ( i = 0; i < nDeletedLines - nInsertedLines; i++ )
+ *insPtr++ = '\n';
+ *insPtr = '\0';
+ } else if ( nDeletedLines < nInsertedLines ) {
+ linesPadded = nInsertedLines - nDeletedLines;
+ for ( i = 0; i < linesPadded; i++ )
+ insert_( end, "\n" );
+ } /* else nDeletedLines == nInsertedLines; */
+
+ /* Save a copy of the text which will be modified for the modify CBs */
+ deletedText = text_range( start, end );
+
+ /* Delete then insert */
+ remove_rectangular_( start, end, rectStart, rectEnd, &deleteInserted, &hint );
+ insert_column_( rectStart, start, insText, &insertDeleted, &insertInserted,
+ &mCursorPosHint );
+
+ /* Figure out how many chars were inserted and call modify callbacks */
+ if ( insertDeleted != deleteInserted + linesPadded )
+ fprintf( stderr, "NEdit: internal consistency check repl1 failed\n" );
+ call_modify_callbacks( start, end - start, insertInserted, 0, deletedText );
+ free( (void *) deletedText );
+ if ( nInsertedLines < nDeletedLines )
+ free( (void *) insText );
+}
+
+/*
+** Remove a rectangular swath of characters between character positions start
+** and end and horizontal displayed-character offsets rectStart and rectEnd.
+*/
+void Fl_Text_Buffer::remove_rectangular( int start, int end, int rectStart,
+ int rectEnd ) {
+ const char * deletedText;
+ int nInserted;
+
+ start = line_start( start );
+ end = line_end( end );
+ deletedText = text_range( start, end );
+ remove_rectangular_( start, end, rectStart, rectEnd, &nInserted,
+ &mCursorPosHint );
+ call_modify_callbacks( start, end - start, nInserted, 0, deletedText );
+ free( (void *) deletedText );
+}
+
+/*
+** Clear a rectangular "hole" out of the buffer between character positions
+** start and end and horizontal displayed-character offsets rectStart and
+** rectEnd.
+*/
+void Fl_Text_Buffer::clear_rectangular( int start, int end, int rectStart,
+ int rectEnd ) {
+ int i, nLines;
+ char *newlineString;
+
+ nLines = count_lines( start, end );
+ newlineString = (char *)malloc( nLines + 1 );
+ for ( i = 0; i < nLines; i++ )
+ newlineString[ i ] = '\n';
+ newlineString[ i ] = '\0';
+ overlay_rectangular( start, rectStart, rectEnd, newlineString,
+ NULL, NULL );
+ free( (void *) newlineString );
+}
+
+const char * Fl_Text_Buffer::text_in_rectangle( int start, int end,
+ int rectStart, int rectEnd ) {
+ int lineStart, selLeft, selRight, len;
+ char *textOut, *outPtr, *retabbedStr;
+ const char *textIn;
+
+ start = line_start( start );
+ end = line_end( end );
+ textOut = (char *)malloc( ( end - start ) + 1 );
+ lineStart = start;
+ outPtr = textOut;
+ while ( lineStart <= end ) {
+ rectangular_selection_boundaries( lineStart, rectStart, rectEnd,
+ &selLeft, &selRight );
+ textIn = text_range( selLeft, selRight );
+ len = selRight - selLeft;
+ memcpy( outPtr, textIn, len );
+ free( (void *) textIn );
+ outPtr += len;
+ lineStart = line_end( selRight ) + 1;
+ *outPtr++ = '\n';
+ }
+ if ( outPtr != textOut )
+ outPtr--; /* don't leave trailing newline */
+ *outPtr = '\0';
+
+ /* If necessary, realign the tabs in the selection as if the text were
+ positioned at the left margin */
+ retabbedStr = realignTabs( textOut, rectStart, 0, mTabDist,
+ mUseTabs, mNullSubsChar, &len );
+ free( (void *) textOut );
+ return retabbedStr;
+}
+
+/*
+** Set the hardware tab distance used by all displays for this buffer,
+** and used in computing offsets for rectangular selection operations.
+*/
+void Fl_Text_Buffer::tab_distance( int tabDist ) {
+ const char * deletedText;
+
+ /* Change the tab setting */
+ mTabDist = tabDist;
+
+ /* Force any display routines to redisplay everything (unfortunately,
+ this means copying the whole buffer contents to provide "deletedText" */
+ deletedText = text();
+ call_modify_callbacks( 0, mLength, mLength, 0, deletedText );
+ free( (void *) deletedText );
+}
+
+void Fl_Text_Buffer::select( int start, int end ) {
+ Fl_Text_Selection oldSelection = mPrimary;
+
+ mPrimary.set( start, end );
+ redisplay_selection( &oldSelection, &mPrimary );
+}
+
+void Fl_Text_Buffer::unselect() {
+ Fl_Text_Selection oldSelection = mPrimary;
+
+ mPrimary.mSelected = 0;
+ redisplay_selection( &oldSelection, &mPrimary );
+}
+
+void Fl_Text_Buffer::select_rectangular( int start, int end, int rectStart,
+ int rectEnd ) {
+ Fl_Text_Selection oldSelection = mPrimary;
+
+ mPrimary.set_rectangular( start, end, rectStart, rectEnd );
+ redisplay_selection( &oldSelection, &mPrimary );
+}
+
+int Fl_Text_Buffer::selection_position( int *start, int *end
+ ) {
+ return mPrimary.position( start, end );
+}
+
+int Fl_Text_Buffer::selection_position( int *start, int *end,
+ int *isRect, int *rectStart, int *rectEnd ) {
+ return mPrimary.position( start, end, isRect, rectStart,
+ rectEnd );
+}
+
+const char * Fl_Text_Buffer::selection_text() {
+ return selection_text_( &mPrimary );
+}
+
+void Fl_Text_Buffer::remove_selection() {
+ remove_selection_( &mPrimary );
+}
+
+void Fl_Text_Buffer::replace_selection( const char *text ) {
+ replace_selection_( &mPrimary, text );
+}
+
+void Fl_Text_Buffer::secondary_select( int start, int end ) {
+ Fl_Text_Selection oldSelection = mSecondary;
+
+ mSecondary.set( start, end );
+ redisplay_selection( &oldSelection, &mSecondary );
+}
+
+void Fl_Text_Buffer::secondary_unselect() {
+ Fl_Text_Selection oldSelection = mSecondary;
+
+ mSecondary.mSelected = 0;
+ redisplay_selection( &oldSelection, &mSecondary );
+}
+
+void Fl_Text_Buffer::secondary_select_rectangular( int start, int end,
+ int rectStart, int rectEnd ) {
+ Fl_Text_Selection oldSelection = mSecondary;
+
+ mSecondary.set_rectangular( start, end, rectStart, rectEnd );
+ redisplay_selection( &oldSelection, &mSecondary );
+}
+
+int Fl_Text_Buffer::secondary_selection_position( int *start, int *end,
+ int *isRect, int *rectStart, int *rectEnd ) {
+ return mSecondary.position( start, end, isRect, rectStart,
+ rectEnd );
+}
+
+const char * Fl_Text_Buffer::secondary_selection_text() {
+ return selection_text_( &mSecondary );
+}
+
+void Fl_Text_Buffer::remove_secondary_selection() {
+ remove_selection_( &mSecondary );
+}
+
+void Fl_Text_Buffer::replace_secondary_selection( const char *text ) {
+ replace_selection_( &mSecondary, text );
+}
+
+void Fl_Text_Buffer::highlight( int start, int end ) {
+ Fl_Text_Selection oldSelection = mHighlight;
+
+ mHighlight.set( start, end );
+ redisplay_selection( &oldSelection, &mHighlight );
+}
+
+void Fl_Text_Buffer::unhighlight() {
+ Fl_Text_Selection oldSelection = mHighlight;
+
+ mHighlight.mSelected = 0;
+ redisplay_selection( &oldSelection, &mHighlight );
+}
+
+void Fl_Text_Buffer::highlight_rectangular( int start, int end,
+ int rectStart, int rectEnd ) {
+ Fl_Text_Selection oldSelection = mHighlight;
+
+ mHighlight.set_rectangular( start, end, rectStart, rectEnd );
+ redisplay_selection( &oldSelection, &mHighlight );
+}
+
+int Fl_Text_Buffer::highlight_position( int *start, int *end,
+ int *isRect, int *rectStart, int *rectEnd ) {
+ return mHighlight.position( start, end, isRect, rectStart,
+ rectEnd );
+}
+
+const char * Fl_Text_Buffer::highlight_text() {
+ return selection_text_( &mHighlight );
+}
+
+/*
+** Add a callback routine to be called when the buffer is modified
+*/
+void Fl_Text_Buffer::add_modify_callback( Fl_Text_Modify_Cb bufModifiedCB,
+ void *cbArg ) {
+ Fl_Text_Modify_Cb * newModifyProcs;
+ void **newCBArgs;
+ int i;
+
+ newModifyProcs = new Fl_Text_Modify_Cb [ mNModifyProcs + 1 ];
+ newCBArgs = new void * [ mNModifyProcs + 1 ];
+ for ( i = 0; i < mNModifyProcs; i++ ) {
+ newModifyProcs[ i ] = mNodifyProcs[ i ];
+ newCBArgs[ i ] = mCbArgs[ i ];
+ }
+ if ( mNModifyProcs != 0 ) {
+ delete [] mNodifyProcs;
+ delete [] mCbArgs;
+ }
+ newModifyProcs[ mNModifyProcs ] = bufModifiedCB;
+ newCBArgs[ mNModifyProcs ] = cbArg;
+ mNModifyProcs++;
+ mNodifyProcs = newModifyProcs;
+ mCbArgs = newCBArgs;
+}
+
+void Fl_Text_Buffer::remove_modify_callback( Fl_Text_Modify_Cb bufModifiedCB,
+ void *cbArg ) {
+ int i, toRemove = -1;
+ Fl_Text_Modify_Cb *newModifyProcs;
+ void **newCBArgs;
+
+ /* find the matching callback to remove */
+ for ( i = 0; i < mNModifyProcs; i++ ) {
+ if ( mNodifyProcs[ i ] == bufModifiedCB && mCbArgs[ i ] == cbArg ) {
+ toRemove = i;
+ break;
+ }
+ }
+ if ( toRemove == -1 ) {
+ fprintf( stderr, "Internal Error: Can't find modify CB to remove\n" );
+ return;
+ }
+
+ /* Allocate new lists for remaining callback procs and args (if
+ any are left) */
+ mNModifyProcs--;
+ if ( mNModifyProcs == 0 ) {
+ mNModifyProcs = 0;
+ delete[] mNodifyProcs;
+ mNodifyProcs = NULL;
+ delete[] mCbArgs;
+ mCbArgs = NULL;
+ return;
+ }
+ newModifyProcs = new Fl_Text_Modify_Cb [ mNModifyProcs ];
+ newCBArgs = new void * [ mNModifyProcs ];
+
+ /* copy out the remaining members and free the old lists */
+ for ( i = 0; i < toRemove; i++ ) {
+ newModifyProcs[ i ] = mNodifyProcs[ i ];
+ newCBArgs[ i ] = mCbArgs[ i ];
+ }
+ for ( ; i < mNModifyProcs; i++ ) {
+ newModifyProcs[ i ] = mNodifyProcs[ i + 1 ];
+ newCBArgs[ i ] = mCbArgs[ i + 1 ];
+ }
+ delete[] mNodifyProcs;
+ delete[] mCbArgs;
+ mNodifyProcs = newModifyProcs;
+ mCbArgs = newCBArgs;
+}
+
+/*
+** Return the text from the entire line containing position "pos"
+*/
+const char * Fl_Text_Buffer::line_text( int pos ) {
+ return text_range( line_start( pos ), line_end( pos ) );
+}
+
+/*
+** Find the position of the start of the line containing position "pos"
+*/
+int Fl_Text_Buffer::line_start( int pos ) {
+ if ( !findchar_backward( pos, '\n', &pos ) )
+ return 0;
+ return pos + 1;
+}
+
+/*
+** Find the position of the end of the line containing position "pos"
+** (which is either a pointer to the newline character ending the line,
+** or a pointer to one character beyond the end of the buffer)
+*/
+int Fl_Text_Buffer::line_end( int pos ) {
+ if ( !findchar_forward( pos, '\n', &pos ) )
+ pos = mLength;
+ return pos;
+}
+
+int Fl_Text_Buffer::word_start( int pos ) {
+ while ( pos && ( isalnum( character( pos ) ) || character( pos ) == '_' ) ) {
+ pos--;
+ }
+ if ( !( isalnum( character( pos ) ) || character( pos ) == '_' ) ) pos++;
+ return pos;
+}
+
+int Fl_Text_Buffer::word_end( int pos ) {
+ while (pos < length() && (isalnum(character(pos)) || character(pos) == '_' )) {
+ pos++;
+ }
+ return pos;
+}
+
+/*
+** Get a character from the text buffer expanded into it's screen
+** representation (which may be several characters for a tab or a
+** control code). Returns the number of characters written to "outStr".
+** "indent" is the number of characters from the start of the line
+** for figuring tabs. Output string is guranteed to be shorter or
+** equal in length to FL_TEXT_MAX_EXP_CHAR_LEN
+*/
+int Fl_Text_Buffer::expand_character( int pos, int indent, char *outStr ) {
+ return expand_character( character( pos ), indent, outStr,
+ mTabDist, mNullSubsChar );
+}
+
+/*
+** Expand a single character from the text buffer into it's screen
+** representation (which may be several characters for a tab or a
+** control code). Returns the number of characters added to "outStr".
+** "indent" is the number of characters from the start of the line
+** for figuring tabs. Output string is guranteed to be shorter or
+** equal in length to FL_TEXT_MAX_EXP_CHAR_LEN
+*/
+int Fl_Text_Buffer::expand_character( char c, int indent, char *outStr, int tabDist,
+ char nullSubsChar ) {
+ int i, nSpaces;
+
+ /* Convert tabs to spaces */
+ if ( c == '\t' ) {
+ nSpaces = tabDist - ( indent % tabDist );
+ for ( i = 0; i < nSpaces; i++ )
+ outStr[ i ] = ' ';
+ return nSpaces;
+ }
+
+ /* Convert control codes to readable character sequences */
+ /*... is this safe with international character sets? */
+ if ( ( ( unsigned char ) c ) <= 31 ) {
+ sprintf( outStr, "<%s>", ControlCodeTable[ c ] );
+ return strlen( outStr );
+ } else if ( c == 127 ) {
+ sprintf( outStr, "<del>" );
+ return 5;
+ } else if ( c == nullSubsChar ) {
+ sprintf( outStr, "<nul>" );
+ return 5;
+ }
+
+ /* Otherwise, just return the character */
+ *outStr = c;
+ return 1;
+}
+
+/*
+** Return the length in displayed characters of character "c" expanded
+** for display (as discussed above in BufGetExpandedChar). If the
+** buffer for which the character width is being measured is doing null
+** substitution, nullSubsChar should be passed as that character (or nul
+** to ignore).
+*/
+int Fl_Text_Buffer::character_width( char c, int indent, int tabDist, char nullSubsChar ) {
+ /* Note, this code must parallel that in Fl_Text_Buffer::ExpandCharacter */
+ if ( c == '\t' )
+ return tabDist - ( indent % tabDist );
+ else if ( ( ( unsigned char ) c ) <= 31 )
+ return strlen( ControlCodeTable[ c ] ) + 2;
+ else if ( c == 127 )
+ return 5;
+ else if ( c == nullSubsChar )
+ return 5;
+ return 1;
+}
+
+/*
+** Count the number of displayed characters between buffer position
+** "lineStartPos" and "targetPos". (displayed characters are the characters
+** shown on the screen to represent characters in the buffer, where tabs and
+** control characters are expanded)
+*/
+int Fl_Text_Buffer::count_displayed_characters( int lineStartPos, int targetPos ) {
+ int pos, charCount = 0;
+ char expandedChar[ FL_TEXT_MAX_EXP_CHAR_LEN ];
+
+ pos = lineStartPos;
+ while ( pos < targetPos )
+ charCount += expand_character( pos++, charCount, expandedChar );
+ return charCount;
+}
+
+/*
+** Count forward from buffer position "startPos" in displayed characters
+** (displayed characters are the characters shown on the screen to represent
+** characters in the buffer, where tabs and control characters are expanded)
+*/
+int Fl_Text_Buffer::skip_displayed_characters( int lineStartPos, int nChars ) {
+ int pos, charCount = 0;
+ char c;
+
+ pos = lineStartPos;
+ while ( charCount < nChars && pos < mLength ) {
+ c = character( pos );
+ if ( c == '\n' )
+ return pos;
+ charCount += character_width( c, charCount, mTabDist, mNullSubsChar );
+ pos++;
+ }
+ return pos;
+}
+
+/*
+** Count the number of newlines between startPos and endPos in buffer "buf".
+** The character at position "endPos" is not counted.
+*/
+int Fl_Text_Buffer::count_lines( int startPos, int endPos ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+ int lineCount = 0;
+
+ pos = startPos;
+ while ( pos < mGapStart ) {
+ if ( pos == endPos )
+ return lineCount;
+ if ( mBuf[ pos++ ] == '\n' )
+ lineCount++;
+ }
+ while ( pos < mLength ) {
+ if ( pos == endPos )
+ return lineCount;
+ if ( mBuf[ pos++ + gapLen ] == '\n' )
+ lineCount++;
+ }
+ return lineCount;
+}
+
+/*
+** Find the first character of the line "nLines" forward from "startPos"
+** in "buf" and return its position
+*/
+int Fl_Text_Buffer::skip_lines( int startPos, int nLines ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+ int lineCount = 0;
+
+ if ( nLines == 0 )
+ return startPos;
+
+ pos = startPos;
+ while ( pos < mGapStart ) {
+ if ( mBuf[ pos++ ] == '\n' ) {
+ lineCount++;
+ if ( lineCount == nLines )
+ return pos;
+ }
+ }
+ while ( pos < mLength ) {
+ if ( mBuf[ pos++ + gapLen ] == '\n' ) {
+ lineCount++;
+ if ( lineCount >= nLines )
+ return pos;
+ }
+ }
+ return pos;
+}
+
+/*
+** Find the position of the first character of the line "nLines" backwards
+** from "startPos" (not counting the character pointed to by "startpos" if
+** that is a newline) in "buf". nLines == 0 means find the beginning of
+** the line
+*/
+int Fl_Text_Buffer::rewind_lines( int startPos, int nLines ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+ int lineCount = -1;
+
+ pos = startPos - 1;
+ if ( pos <= 0 )
+ return 0;
+
+ while ( pos >= mGapStart ) {
+ if ( mBuf[ pos + gapLen ] == '\n' ) {
+ if ( ++lineCount >= nLines )
+ return pos + 1;
+ }
+ pos--;
+ }
+ while ( pos >= 0 ) {
+ if ( mBuf[ pos ] == '\n' ) {
+ if ( ++lineCount >= nLines )
+ return pos + 1;
+ }
+ pos--;
+ }
+ return 0;
+}
+
+/*
+** Search forwards in buffer for string "searchString", starting with the
+** character "startPos", and returning the result in "foundPos"
+** returns 1 if found, 0 if not.
+*/
+int Fl_Text_Buffer::search_forward( int startPos, const char *searchString,
+ int *foundPos, int matchCase )
+{
+ if (!searchString) return 0;
+ int bp;
+ const char* sp;
+ while (startPos < length()) {
+ bp = startPos;
+ sp = searchString;
+ do {
+ if (!*sp) { *foundPos = startPos; return 1; }
+ } while ((matchCase ? character(bp++) == *sp++ :
+ toupper(character(bp++)) == toupper(*sp++))
+ && bp < length());
+ startPos++;
+ }
+ return 0;
+}
+
+/*
+** Search backwards in buffer for string "searchString", starting with the
+** character BEFORE "startPos", returning the result in "foundPos"
+** returns 1 if found, 0 if not.
+*/
+int Fl_Text_Buffer::search_backward( int startPos, const char *searchString,
+ int *foundPos, int matchCase )
+{
+ if (!searchString) return 0;
+ int bp;
+ const char* sp;
+ while (startPos > 0) {
+ bp = startPos-1;
+ sp = searchString+strlen(searchString)-1;
+ do {
+ if (sp < searchString) { *foundPos = bp+1; return 1; }
+ } while ((matchCase ? character(bp--) == *sp-- :
+ toupper(character(bp--)) == toupper(*sp--))
+ && bp >= 0);
+ startPos--;
+ }
+ return 0;
+}
+
+/*
+** Search forwards in buffer for characters in "searchChars", starting
+** with the character "startPos", and returning the result in "foundPos"
+** returns 1 if found, 0 if not.
+*/
+int Fl_Text_Buffer::findchars_forward( int startPos, const char *searchChars,
+ int *foundPos ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+ const char *c;
+
+ pos = startPos;
+ while ( pos < mGapStart ) {
+ for ( c = searchChars; *c != '\0'; c++ ) {
+ if ( mBuf[ pos ] == *c ) {
+ *foundPos = pos;
+ return 1;
+ }
+ }
+ pos++;
+ }
+ while ( pos < mLength ) {
+ for ( c = searchChars; *c != '\0'; c++ ) {
+ if ( mBuf[ pos + gapLen ] == *c ) {
+ *foundPos = pos;
+ return 1;
+ }
+ }
+ pos++;
+ }
+ *foundPos = mLength;
+ return 0;
+}
+
+/*
+** Search backwards in buffer for characters in "searchChars", starting
+** with the character BEFORE "startPos", returning the result in "foundPos"
+** returns 1 if found, 0 if not.
+*/
+int Fl_Text_Buffer::findchars_backward( int startPos, const char *searchChars,
+ int *foundPos ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+ const char *c;
+
+ if ( startPos == 0 ) {
+ *foundPos = 0;
+ return 0;
+ }
+ pos = startPos == 0 ? 0 : startPos - 1;
+ while ( pos >= mGapStart ) {
+ for ( c = searchChars; *c != '\0'; c++ ) {
+ if ( mBuf[ pos + gapLen ] == *c ) {
+ *foundPos = pos;
+ return 1;
+ }
+ }
+ pos--;
+ }
+ while ( pos >= 0 ) {
+ for ( c = searchChars; *c != '\0'; c++ ) {
+ if ( mBuf[ pos ] == *c ) {
+ *foundPos = pos;
+ return 1;
+ }
+ }
+ pos--;
+ }
+ *foundPos = 0;
+ return 0;
+}
+
+/*
+** A horrible design flaw in NEdit (from the very start, before we knew that
+** NEdit would become so popular), is that it uses C NULL terminated strings
+** to hold text. This means editing text containing NUL characters is not
+** possible without special consideration. Here is the special consideration.
+** The routines below maintain a special substitution-character which stands
+** in for a null, and translates strings an buffers back and forth from/to
+** the substituted form, figure out what to substitute, and figure out
+** when we're in over our heads and no translation is possible.
+*/
+
+/*
+** The primary routine for integrating new text into a text buffer with
+** substitution of another character for ascii nuls. This substitutes null
+** characters in the string in preparation for being copied or replaced
+** into the buffer, and if neccessary, adjusts the buffer as well, in the
+** event that the string contains the character it is currently using for
+** substitution. Returns 0, if substitution is no longer possible
+** because all non-printable characters are already in use.
+*/
+int Fl_Text_Buffer::substitute_null_characters( char *string, int length ) {
+ char histogram[ 256 ];
+
+ /* Find out what characters the string contains */
+ histogramCharacters( string, length, histogram, 1 );
+
+ /* Does the string contain the null-substitute character? If so, re-
+ histogram the buffer text to find a character which is ok in both the
+ string and the buffer, and change the buffer's null-substitution
+ character. If none can be found, give up and return 0 */
+ if ( histogram[ ( unsigned char ) mNullSubsChar ] != 0 ) {
+ char * bufString;
+ char newSubsChar;
+ bufString = (char*)text();
+ histogramCharacters( bufString, mLength, histogram, 0 );
+ newSubsChar = chooseNullSubsChar( histogram );
+ if ( newSubsChar == '\0' )
+ return 0;
+ subsChars( bufString, mLength, mNullSubsChar, newSubsChar );
+ remove_( 0, mLength );
+ insert_( 0, bufString );
+ free( (void *) bufString );
+ mNullSubsChar = newSubsChar;
+ }
+
+ /* If the string contains null characters, substitute them with the
+ buffer's null substitution character */
+ if ( histogram[ 0 ] != 0 )
+ subsChars( string, length, '\0', mNullSubsChar );
+ return 1;
+}
+
+/*
+** Convert strings obtained from buffers which contain null characters, which
+** have been substituted for by a special substitution character, back to
+** a null-containing string. There is no time penalty for calling this
+** routine if no substitution has been done.
+*/
+void Fl_Text_Buffer::unsubstitute_null_characters( char *string ) {
+ register char * c, subsChar = mNullSubsChar;
+
+ if ( subsChar == '\0' )
+ return;
+ for ( c = string; *c != '\0'; c++ )
+ if ( *c == subsChar )
+ * c = '\0';
+}
+
+/*
+** Create a pseudo-histogram of the characters in a string (don't actually
+** count, because we don't want overflow, just mark the character's presence
+** with a 1). If init is true, initialize the histogram before acumulating.
+** if not, add the new data to an existing histogram.
+*/
+static void histogramCharacters( const char *string, int length, char hist[ 256 ],
+ int init ) {
+ int i;
+ const char *c;
+
+ if ( init )
+ for ( i = 0; i < 256; i++ )
+ hist[ i ] = 0;
+ for ( c = string; c < &string[ length ]; c++ )
+ hist[ *( ( unsigned char * ) c ) ] |= 1;
+}
+
+/*
+** Substitute fromChar with toChar in string.
+*/
+static void subsChars( char *string, int length, char fromChar, char toChar ) {
+ char * c;
+
+ for ( c = string; c < &string[ length ]; c++ )
+ if ( *c == fromChar ) * c = toChar;
+}
+
+/*
+** Search through ascii control characters in histogram in order of least
+** likelihood of use, find an unused character to use as a stand-in for a
+** null. If the character set is full (no available characters outside of
+** the printable set, return the null character.
+*/
+static char chooseNullSubsChar( char hist[ 256 ] ) {
+#define N_REPLACEMENTS 25
+ static char replacements[ N_REPLACEMENTS ] = {1, 2, 3, 4, 5, 6, 14, 15, 16, 17, 18, 19,
+ 20, 21, 22, 23, 24, 25, 26, 28, 29, 30, 31, 11, 7};
+ int i;
+ for ( i = 0; i < N_REPLACEMENTS; i++ )
+ if ( hist[ replacements[ i ] ] == 0 )
+ return replacements[ i ];
+ return '\0';
+}
+
+/*
+** Internal (non-redisplaying) version of BufInsert. Returns the length of
+** text inserted (this is just strlen(text), however this calculation can be
+** expensive and the length will be required by any caller who will continue
+** on to call redisplay). pos must be contiguous with the existing text in
+** the buffer (i.e. not past the end).
+*/
+int Fl_Text_Buffer::insert_( int pos, const char *text ) {
+ int length = strlen( text );
+
+ /* Prepare the buffer to receive the new text. If the new text fits in
+ the current buffer, just move the gap (if necessary) to where
+ the text should be inserted. If the new text is too large, reallocate
+ the buffer with a gap large enough to accomodate the new text and a
+ gap of PREFERRED_GAP_SIZE */
+ if ( length > mGapEnd - mGapStart )
+ reallocate_with_gap( pos, length + PREFERRED_GAP_SIZE );
+ else if ( pos != mGapStart )
+ move_gap( pos );
+
+ /* Insert the new text (pos now corresponds to the start of the gap) */
+ memcpy( &mBuf[ pos ], text, length );
+ mGapStart += length;
+ mLength += length;
+ update_selections( pos, 0, length );
+
+ return length;
+}
+
+/*
+** Internal (non-redisplaying) version of BufRemove. Removes the contents
+** of the buffer between start and end (and moves the gap to the site of
+** the delete).
+*/
+void Fl_Text_Buffer::remove_( int start, int end ) {
+ /* if the gap is not contiguous to the area to remove, move it there */
+ if ( start > mGapStart )
+ move_gap( start );
+ else if ( end < mGapStart )
+ move_gap( end );
+
+ /* expand the gap to encompass the deleted characters */
+ mGapEnd += end - mGapStart;
+ mGapStart -= mGapStart - start;
+
+ /* update the length */
+ mLength -= end - start;
+
+ /* fix up any selections which might be affected by the change */
+ update_selections( start, end - start, 0 );
+}
+
+/*
+** Insert a column of text without calling the modify callbacks. Note that
+** in some pathological cases, inserting can actually decrease the size of
+** the buffer because of spaces being coalesced into tabs. "nDeleted" and
+** "nInserted" return the number of characters deleted and inserted beginning
+** at the start of the line containing "startPos". "endPos" returns buffer
+** position of the lower left edge of the inserted column (as a hint for
+** routines which need to set a cursor position).
+*/
+void Fl_Text_Buffer::insert_column_( int column, int startPos, const char *insText,
+ int *nDeleted, int *nInserted, int *endPos ) {
+ int nLines, start, end, insWidth, lineStart, lineEnd;
+ int expReplLen, expInsLen, len, endOffset;
+ char *c, *outStr, *outPtr, *expText, *insLine;
+ const char *line;
+ const char *replText;
+ const char *insPtr;
+
+ if ( column < 0 )
+ column = 0;
+
+ /* Allocate a buffer for the replacement string large enough to hold
+ possibly expanded tabs in both the inserted text and the replaced
+ area, as well as per line: 1) an additional 2*FL_TEXT_MAX_EXP_CHAR_LEN
+ characters for padding where tabs and control characters cross the
+ column of the selection, 2) up to "column" additional spaces per
+ line for padding out to the position of "column", 3) padding up
+ to the width of the inserted text if that must be padded to align
+ the text beyond the inserted column. (Space for additional
+ newlines if the inserted text extends beyond the end of the buffer
+ is counted with the length of insText) */
+ start = line_start( startPos );
+ nLines = countLines( insText ) + 1;
+ insWidth = textWidth( insText, mTabDist, mNullSubsChar );
+ end = line_end( skip_lines( start, nLines - 1 ) );
+ replText = text_range( start, end );
+ expText = expandTabs( replText, 0, mTabDist, mNullSubsChar,
+ &expReplLen );
+ free( (void *) replText );
+ free( (void *) expText );
+ expText = expandTabs( insText, 0, mTabDist, mNullSubsChar,
+ &expInsLen );
+ free( (void *) expText );
+ outStr = (char *)malloc( expReplLen + expInsLen +
+ nLines * ( column + insWidth + FL_TEXT_MAX_EXP_CHAR_LEN ) + 1 );
+
+ /* Loop over all lines in the buffer between start and end removing the
+ text between rectStart and rectEnd and padding appropriately. Trim
+ trailing space from line (whitespace at the ends of lines otherwise
+ tends to multiply, since additional padding is added to maintain it */
+ outPtr = outStr;
+ lineStart = start;
+ insPtr = insText;
+ for (;;) {
+ lineEnd = line_end( lineStart );
+ line = text_range( lineStart, lineEnd );
+ insLine = copyLine( insPtr, &len );
+ insPtr += len;
+ insertColInLine( line, insLine, column, insWidth, mTabDist,
+ mUseTabs, mNullSubsChar, outPtr, &len, &endOffset );
+ free( (void *) line );
+ free( (void *) insLine );
+ for ( c = outPtr + len - 1; c > outPtr && isspace( *c ); c-- )
+ len--;
+ outPtr += len;
+ *outPtr++ = '\n';
+ lineStart = lineEnd < mLength ? lineEnd + 1 : mLength;
+ if ( *insPtr == '\0' )
+ break;
+ insPtr++;
+ }
+ if ( outPtr != outStr )
+ outPtr--; /* trim back off extra newline */
+ *outPtr = '\0';
+
+ /* replace the text between start and end with the new stuff */
+ remove_( start, end );
+ insert_( start, outStr );
+ *nInserted = outPtr - outStr;
+ *nDeleted = end - start;
+ *endPos = start + ( outPtr - outStr ) - len + endOffset;
+ free( (void *) outStr );
+}
+
+/*
+** Delete a rectangle of text without calling the modify callbacks. Returns
+** the number of characters replacing those between start and end. Note that
+** in some pathological cases, deleting can actually increase the size of
+** the buffer because of tab expansions. "endPos" returns the buffer position
+** of the point in the last line where the text was removed (as a hint for
+** routines which need to position the cursor after a delete operation)
+*/
+void Fl_Text_Buffer::remove_rectangular_( int start, int end, int rectStart,
+ int rectEnd, int *replaceLen, int *endPos ) {
+ int nLines, lineStart, lineEnd, len, endOffset;
+ char *outStr, *outPtr, *expText;
+ const char *text, *line;
+
+ /* allocate a buffer for the replacement string large enough to hold
+ possibly expanded tabs as well as an additional FL_TEXT_MAX_EXP_CHAR_LEN * 2
+ characters per line for padding where tabs and control characters cross
+ the edges of the selection */
+ start = line_start( start );
+ end = line_end( end );
+ nLines = count_lines( start, end ) + 1;
+ text = text_range( start, end );
+ expText = expandTabs( text, 0, mTabDist, mNullSubsChar, &len );
+ free( (void *) text );
+ free( (void *) expText );
+ outStr = (char *)malloc( len + nLines * FL_TEXT_MAX_EXP_CHAR_LEN * 2 + 1 );
+
+ /* loop over all lines in the buffer between start and end removing
+ the text between rectStart and rectEnd and padding appropriately */
+ lineStart = start;
+ outPtr = outStr;
+ while ( lineStart <= mLength && lineStart <= end ) {
+ lineEnd = line_end( lineStart );
+ line = text_range( lineStart, lineEnd );
+ deleteRectFromLine( line, rectStart, rectEnd, mTabDist,
+ mUseTabs, mNullSubsChar, outPtr, &len, &endOffset );
+ free( (void *) line );
+ outPtr += len;
+ *outPtr++ = '\n';
+ lineStart = lineEnd + 1;
+ }
+ if ( outPtr != outStr )
+ outPtr--; /* trim back off extra newline */
+ *outPtr = '\0';
+
+ /* replace the text between start and end with the newly created string */
+ remove_( start, end );
+ insert_( start, outStr );
+ *replaceLen = outPtr - outStr;
+ *endPos = start + ( outPtr - outStr ) - len + endOffset;
+ free( (void *) outStr );
+}
+
+/*
+** Overlay a rectangular area of text without calling the modify callbacks.
+** "nDeleted" and "nInserted" return the number of characters deleted and
+** inserted beginning at the start of the line containing "startPos".
+** "endPos" returns buffer position of the lower left edge of the inserted
+** column (as a hint for routines which need to set a cursor position).
+*/
+void Fl_Text_Buffer::overlay_rectangular_(int startPos, int rectStart,
+ int rectEnd, const char *insText,
+ int *nDeleted, int *nInserted,
+ int *endPos ) {
+ int nLines, start, end, lineStart, lineEnd;
+ int expInsLen, len, endOffset;
+ char *c, *outStr, *outPtr, *expText, *insLine;
+ const char *line;
+ const char *insPtr;
+
+ /* Allocate a buffer for the replacement string large enough to hold
+ possibly expanded tabs in the inserted text, as well as per line: 1)
+ an additional 2*FL_TEXT_MAX_EXP_CHAR_LEN characters for padding where tabs
+ and control characters cross the column of the selection, 2) up to
+ "column" additional spaces per line for padding out to the position
+ of "column", 3) padding up to the width of the inserted text if that
+ must be padded to align the text beyond the inserted column. (Space
+ for additional newlines if the inserted text extends beyond the end
+ of the buffer is counted with the length of insText) */
+ start = line_start( startPos );
+ nLines = countLines( insText ) + 1;
+ end = line_end( skip_lines( start, nLines - 1 ) );
+ expText = expandTabs( insText, 0, mTabDist, mNullSubsChar,
+ &expInsLen );
+ free( (void *) expText );
+ outStr = (char *)malloc( end - start + expInsLen +
+ nLines * ( rectEnd + FL_TEXT_MAX_EXP_CHAR_LEN ) + 1 );
+
+ /* Loop over all lines in the buffer between start and end overlaying the
+ text between rectStart and rectEnd and padding appropriately. Trim
+ trailing space from line (whitespace at the ends of lines otherwise
+ tends to multiply, since additional padding is added to maintain it */
+ outPtr = outStr;
+ lineStart = start;
+ insPtr = insText;
+ for (;;) {
+ lineEnd = line_end( lineStart );
+ line = text_range( lineStart, lineEnd );
+ insLine = copyLine( insPtr, &len );
+ insPtr += len;
+ overlayRectInLine( line, insLine, rectStart, rectEnd, mTabDist,
+ mUseTabs, mNullSubsChar, outPtr, &len, &endOffset );
+ free( (void *) line );
+ free( (void *) insLine );
+ for ( c = outPtr + len - 1; c > outPtr && isspace( *c ); c-- )
+ len--;
+ outPtr += len;
+ *outPtr++ = '\n';
+ lineStart = lineEnd < mLength ? lineEnd + 1 : mLength;
+ if ( *insPtr == '\0' )
+ break;
+ insPtr++;
+ }
+ if ( outPtr != outStr )
+ outPtr--; /* trim back off extra newline */
+ *outPtr = '\0';
+
+ /* replace the text between start and end with the new stuff */
+ remove_( start, end );
+ insert_( start, outStr );
+ *nInserted = outPtr - outStr;
+ *nDeleted = end - start;
+ *endPos = start + ( outPtr - outStr ) - len + endOffset;
+ free( (void *) outStr );
+}
+
+/*
+** Insert characters from single-line string "insLine" in single-line string
+** "line" at "column", leaving "insWidth" space before continuing line.
+** "outLen" returns the number of characters written to "outStr", "endOffset"
+** returns the number of characters from the beginning of the string to
+** the right edge of the inserted text (as a hint for routines which need
+** to position the cursor).
+*/
+static void insertColInLine( const char *line, char *insLine, int column, int insWidth,
+ int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
+ int *endOffset ) {
+ char * c, *outPtr, *retabbedStr;
+ const char *linePtr;
+ int indent, toIndent, len, postColIndent;
+
+ /* copy the line up to "column" */
+ outPtr = outStr;
+ indent = 0;
+ for ( linePtr = line; *linePtr != '\0'; linePtr++ ) {
+ len = Fl_Text_Buffer::character_width( *linePtr, indent, tabDist, nullSubsChar );
+ if ( indent + len > column )
+ break;
+ indent += len;
+ *outPtr++ = *linePtr;
+ }
+
+ /* If "column" falls in the middle of a character, and the character is a
+ tab, leave it off and leave the indent short and it will get padded
+ later. If it's a control character, insert it and adjust indent
+ accordingly. */
+ if ( indent < column && *linePtr != '\0' ) {
+ postColIndent = indent + len;
+ if ( *linePtr == '\t' )
+ linePtr++;
+ else {
+ *outPtr++ = *linePtr++;
+ indent += len;
+ }
+ } else
+ postColIndent = indent;
+
+ /* If there's no text after the column and no text to insert, that's all */
+ if ( *insLine == '\0' && *linePtr == '\0' ) {
+ *outLen = *endOffset = outPtr - outStr;
+ return;
+ }
+
+ /* pad out to column if text is too short */
+ if ( indent < column ) {
+ addPadding( outPtr, indent, column, tabDist, useTabs, nullSubsChar, &len );
+ outPtr += len;
+ indent = column;
+ }
+
+ /* Copy the text from "insLine" (if any), recalculating the tabs as if
+ the inserted string began at column 0 to its new column destination */
+ if ( *insLine != '\0' ) {
+ retabbedStr = realignTabs( insLine, 0, indent, tabDist, useTabs,
+ nullSubsChar, &len );
+ for ( c = retabbedStr; *c != '\0'; c++ ) {
+ *outPtr++ = *c;
+ len = Fl_Text_Buffer::character_width( *c, indent, tabDist, nullSubsChar );
+ indent += len;
+ }
+ free( (void *) retabbedStr );
+ }
+
+ /* If the original line did not extend past "column", that's all */
+ if ( *linePtr == '\0' ) {
+ *outLen = *endOffset = outPtr - outStr;
+ return;
+ }
+
+ /* Pad out to column + width of inserted text + (additional original
+ offset due to non-breaking character at column) */
+ toIndent = column + insWidth + postColIndent - column;
+ addPadding( outPtr, indent, toIndent, tabDist, useTabs, nullSubsChar, &len );
+ outPtr += len;
+ indent = toIndent;
+
+ /* realign tabs for text beyond "column" and write it out */
+ retabbedStr = realignTabs( linePtr, postColIndent, indent, tabDist,
+ useTabs, nullSubsChar, &len );
+ strcpy( outPtr, retabbedStr );
+ free( (void *) retabbedStr );
+ *endOffset = outPtr - outStr;
+ *outLen = ( outPtr - outStr ) + len;
+}
+
+/*
+** Remove characters in single-line string "line" between displayed positions
+** "rectStart" and "rectEnd", and write the result to "outStr", which is
+** assumed to be large enough to hold the returned string. Note that in
+** certain cases, it is possible for the string to get longer due to
+** expansion of tabs. "endOffset" returns the number of characters from
+** the beginning of the string to the point where the characters were
+** deleted (as a hint for routines which need to position the cursor).
+*/
+static void deleteRectFromLine( const char *line, int rectStart, int rectEnd,
+ int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
+ int *endOffset ) {
+ int indent, preRectIndent, postRectIndent, len;
+ const char *c;
+ char *retabbedStr, *outPtr;
+
+ /* copy the line up to rectStart */
+ outPtr = outStr;
+ indent = 0;
+ for ( c = line; *c != '\0'; c++ ) {
+ if ( indent > rectStart )
+ break;
+ len = Fl_Text_Buffer::character_width( *c, indent, tabDist, nullSubsChar );
+ if ( indent + len > rectStart && ( indent == rectStart || *c == '\t' ) )
+ break;
+ indent += len;
+ *outPtr++ = *c;
+ }
+ preRectIndent = indent;
+
+ /* skip the characters between rectStart and rectEnd */
+ for ( ; *c != '\0' && indent < rectEnd; c++ )
+ indent += Fl_Text_Buffer::character_width( *c, indent, tabDist, nullSubsChar );
+ postRectIndent = indent;
+
+ /* If the line ended before rectEnd, there's nothing more to do */
+ if ( *c == '\0' ) {
+ *outPtr = '\0';
+ *outLen = *endOffset = outPtr - outStr;
+ return;
+ }
+
+ /* fill in any space left by removed tabs or control characters
+ which straddled the boundaries */
+ indent = max( rectStart + postRectIndent - rectEnd, preRectIndent );
+ addPadding( outPtr, preRectIndent, indent, tabDist, useTabs, nullSubsChar,
+ &len );
+ outPtr += len;
+
+ /* Copy the rest of the line. If the indentation has changed, preserve
+ the position of non-whitespace characters by converting tabs to
+ spaces, then back to tabs with the correct offset */
+ retabbedStr = realignTabs( c, postRectIndent, indent, tabDist, useTabs,
+ nullSubsChar, &len );
+ strcpy( outPtr, retabbedStr );
+ free( (void *) retabbedStr );
+ *endOffset = outPtr - outStr;
+ *outLen = ( outPtr - outStr ) + len;
+}
+
+/*
+** Overlay characters from single-line string "insLine" on single-line string
+** "line" between displayed character offsets "rectStart" and "rectEnd".
+** "outLen" returns the number of characters written to "outStr", "endOffset"
+** returns the number of characters from the beginning of the string to
+** the right edge of the inserted text (as a hint for routines which need
+** to position the cursor).
+*/
+static void overlayRectInLine( const char *line, char *insLine, int rectStart,
+ int rectEnd, int tabDist, int useTabs, char nullSubsChar, char *outStr,
+ int *outLen, int *endOffset ) {
+ char * c, *outPtr, *retabbedStr;
+ int inIndent, outIndent, len, postRectIndent;
+ const char *linePtr;
+
+ /* copy the line up to "rectStart" */
+ outPtr = outStr;
+ inIndent = outIndent = 0;
+ for ( linePtr = line; *linePtr != '\0'; linePtr++ ) {
+ len = Fl_Text_Buffer::character_width( *linePtr, inIndent, tabDist, nullSubsChar );
+ if ( inIndent + len > rectStart )
+ break;
+ inIndent += len;
+ outIndent += len;
+ *outPtr++ = *linePtr;
+ }
+
+ /* If "rectStart" falls in the middle of a character, and the character
+ is a tab, leave it off and leave the outIndent short and it will get
+ padded later. If it's a control character, insert it and adjust
+ outIndent accordingly. */
+ if ( inIndent < rectStart && *linePtr != '\0' ) {
+ if ( *linePtr == '\t' ) {
+ linePtr++;
+ inIndent += len;
+ } else {
+ *outPtr++ = *linePtr++;
+ outIndent += len;
+ inIndent += len;
+ }
+ }
+
+ /* skip the characters between rectStart and rectEnd */
+ postRectIndent = rectEnd;
+ for ( ; *linePtr != '\0'; linePtr++ ) {
+ inIndent += Fl_Text_Buffer::character_width( *linePtr, inIndent, tabDist, nullSubsChar );
+ if ( inIndent >= rectEnd ) {
+ linePtr++;
+ postRectIndent = inIndent;
+ break;
+ }
+ }
+
+ /* If there's no text after rectStart and no text to insert, that's all */
+ if ( *insLine == '\0' && *linePtr == '\0' ) {
+ *outLen = *endOffset = outPtr - outStr;
+ return;
+ }
+
+ /* pad out to rectStart if text is too short */
+ if ( outIndent < rectStart ) {
+ addPadding( outPtr, outIndent, rectStart, tabDist, useTabs, nullSubsChar,
+ &len );
+ outPtr += len;
+ }
+ outIndent = rectStart;
+
+ /* Copy the text from "insLine" (if any), recalculating the tabs as if
+ the inserted string began at column 0 to its new column destination */
+ if ( *insLine != '\0' ) {
+ retabbedStr = realignTabs( insLine, 0, rectStart, tabDist, useTabs,
+ nullSubsChar, &len );
+ for ( c = retabbedStr; *c != '\0'; c++ ) {
+ *outPtr++ = *c;
+ len = Fl_Text_Buffer::character_width( *c, outIndent, tabDist, nullSubsChar );
+ outIndent += len;
+ }
+ free( (void *) retabbedStr );
+ }
+
+ /* If the original line did not extend past "rectStart", that's all */
+ if ( *linePtr == '\0' ) {
+ *outLen = *endOffset = outPtr - outStr;
+ return;
+ }
+
+ /* Pad out to rectEnd + (additional original offset
+ due to non-breaking character at right boundary) */
+ addPadding( outPtr, outIndent, postRectIndent, tabDist, useTabs,
+ nullSubsChar, &len );
+ outPtr += len;
+ outIndent = postRectIndent;
+
+ /* copy the text beyond "rectEnd" */
+ strcpy( outPtr, linePtr );
+ *endOffset = outPtr - outStr;
+ *outLen = ( outPtr - outStr ) + strlen( linePtr );
+}
+
+void Fl_Text_Selection::set( int start, int end ) {
+ mSelected = start != end;
+ mRectangular = 0;
+ mStart = min( start, end );
+ mEnd = max( start, end );
+}
+
+void Fl_Text_Selection::set_rectangular( int start, int end,
+ int rectStart, int rectEnd ) {
+ mSelected = rectStart < rectEnd;
+ mRectangular = 1;
+ mStart = start;
+ mEnd = end;
+ mRectStart = rectStart;
+ mRectEnd = rectEnd;
+}
+
+int Fl_Text_Selection::position( int *start, int *end ) {
+ if ( !mSelected )
+ return 0;
+ *start = mStart;
+ *end = mEnd;
+
+ return 1;
+}
+
+int Fl_Text_Selection::position( int *start, int *end,
+ int *isRect, int *rectStart, int *rectEnd ) {
+ if ( !mSelected )
+ return 0;
+ *isRect = mRectangular;
+ *start = mStart;
+ *end = mEnd;
+ if ( mRectangular ) {
+ *rectStart = mRectStart;
+ *rectEnd = mRectEnd;
+ }
+ return 1;
+}
+
+/*
+** Return true if position "pos" with indentation "dispIndex" is in
+** the Fl_Text_Selection.
+*/
+int Fl_Text_Selection::includes(int pos, int lineStartPos, int dispIndex) {
+ return selected() &&
+ ( (!rectangular() && pos >= start() && pos < end()) ||
+ (rectangular() && pos >= start() && lineStartPos <= end() &&
+ dispIndex >= rect_start() && dispIndex < rect_end())
+ );
+}
+
+
+
+const char * Fl_Text_Buffer::selection_text_( Fl_Text_Selection *sel ) {
+ int start, end, isRect, rectStart, rectEnd;
+ char *text;
+
+ /* If there's no selection, return an allocated empty string */
+ if ( !sel->position( &start, &end, &isRect, &rectStart, &rectEnd ) ) {
+ text = (char *)malloc( 1 );
+ *text = '\0';
+ return text;
+ }
+
+ /* If the selection is not rectangular, return the selected range */
+ if ( isRect )
+ return text_in_rectangle( start, end, rectStart, rectEnd );
+ else
+ return text_range( start, end );
+}
+
+void Fl_Text_Buffer::remove_selection_( Fl_Text_Selection *sel ) {
+ int start, end;
+ int isRect, rectStart, rectEnd;
+
+ if ( !sel->position( &start, &end, &isRect, &rectStart, &rectEnd ) )
+ return;
+ if ( isRect )
+ remove_rectangular( start, end, rectStart, rectEnd );
+ else
+ remove( start, end );
+}
+
+void Fl_Text_Buffer::replace_selection_( Fl_Text_Selection *sel, const char *text ) {
+ int start, end, isRect, rectStart, rectEnd;
+ Fl_Text_Selection oldSelection = *sel;
+
+ /* If there's no selection, return */
+ if ( !sel->position( &start, &end, &isRect, &rectStart, &rectEnd ) )
+ return;
+
+ /* Do the appropriate type of replace */
+ if ( isRect )
+ replace_rectangular( start, end, rectStart, rectEnd, text );
+ else
+ replace( start, end, text );
+
+ /* Unselect (happens automatically in BufReplace, but BufReplaceRect
+ can't detect when the contents of a selection goes away) */
+ sel->mSelected = 0;
+ redisplay_selection( &oldSelection, sel );
+}
+
+static void addPadding( char *string, int startIndent, int toIndent,
+ int tabDist, int useTabs, char nullSubsChar, int *charsAdded ) {
+ char * outPtr;
+ int len, indent;
+
+ indent = startIndent;
+ outPtr = string;
+ if ( useTabs ) {
+ while ( indent < toIndent ) {
+ len = Fl_Text_Buffer::character_width( '\t', indent, tabDist, nullSubsChar );
+ if ( len > 1 && indent + len <= toIndent ) {
+ *outPtr++ = '\t';
+ indent += len;
+ } else {
+ *outPtr++ = ' ';
+ indent++;
+ }
+ }
+ } else {
+ while ( indent < toIndent ) {
+ *outPtr++ = ' ';
+ indent++;
+ }
+ }
+ *charsAdded = outPtr - string;
+}
+
+/*
+** Call the stored modify callback procedure(s) for this buffer to update the
+** changed area(s) on the screen and any other listeners.
+*/
+void Fl_Text_Buffer::call_modify_callbacks( int pos, int nDeleted,
+ int nInserted, int nRestyled, const char *deletedText ) {
+ int i;
+
+ for ( i = 0; i < mNModifyProcs; i++ )
+ ( *mNodifyProcs[ i ] ) ( pos, nInserted, nDeleted, nRestyled,
+ deletedText, mCbArgs[ i ] );
+}
+
+/*
+** Call the stored redisplay procedure(s) for this buffer to update the
+** screen for a change in a selection.
+*/
+void Fl_Text_Buffer::redisplay_selection( Fl_Text_Selection *oldSelection,
+ Fl_Text_Selection *newSelection ) {
+ int oldStart, oldEnd, newStart, newEnd, ch1Start, ch1End, ch2Start, ch2End;
+
+ /* If either selection is rectangular, add an additional character to
+ the end of the selection to request the redraw routines to wipe out
+ the parts of the selection beyond the end of the line */
+ oldStart = oldSelection->mStart;
+ newStart = newSelection->mStart;
+ oldEnd = oldSelection->mEnd;
+ newEnd = newSelection->mEnd;
+ if ( oldSelection->mRectangular )
+ oldEnd++;
+ if ( newSelection->mRectangular )
+ newEnd++;
+
+ /* If the old or new selection is unselected, just redisplay the
+ single area that is (was) selected and return */
+ if ( !oldSelection->mSelected && !newSelection->mSelected )
+ return;
+ if ( !oldSelection->mSelected ) {
+ call_modify_callbacks( newStart, 0, 0, newEnd - newStart, NULL );
+ return;
+ }
+ if ( !newSelection->mSelected ) {
+ call_modify_callbacks( oldStart, 0, 0, oldEnd - oldStart, NULL );
+ return;
+ }
+
+ /* If the selection changed from normal to rectangular or visa versa, or
+ if a rectangular selection changed boundaries, redisplay everything */
+ if ( ( oldSelection->mRectangular && !newSelection->mRectangular ) ||
+ ( !oldSelection->mRectangular && newSelection->mRectangular ) ||
+ ( oldSelection->mRectangular && (
+ ( oldSelection->mRectStart != newSelection->mRectStart ) ||
+ ( oldSelection->mRectEnd != newSelection->mRectEnd ) ) ) ) {
+ call_modify_callbacks( min( oldStart, newStart ), 0, 0,
+ max( oldEnd, newEnd ) - min( oldStart, newStart ), NULL );
+ return;
+ }
+
+ /* If the selections are non-contiguous, do two separate updates
+ and return */
+ if ( oldEnd < newStart || newEnd < oldStart ) {
+ call_modify_callbacks( oldStart, 0, 0, oldEnd - oldStart, NULL );
+ call_modify_callbacks( newStart, 0, 0, newEnd - newStart, NULL );
+ return;
+ }
+
+ /* Otherwise, separate into 3 separate regions: ch1, and ch2 (the two
+ changed areas), and the unchanged area of their intersection,
+ and update only the changed area(s) */
+ ch1Start = min( oldStart, newStart );
+ ch2End = max( oldEnd, newEnd );
+ ch1End = max( oldStart, newStart );
+ ch2Start = min( oldEnd, newEnd );
+ if ( ch1Start != ch1End )
+ call_modify_callbacks( ch1Start, 0, 0, ch1End - ch1Start, NULL );
+ if ( ch2Start != ch2End )
+ call_modify_callbacks( ch2Start, 0, 0, ch2End - ch2Start, NULL );
+}
+
+void Fl_Text_Buffer::move_gap( int pos ) {
+ int gapLen = mGapEnd - mGapStart;
+
+ if ( pos > mGapStart )
+ memmove( &mBuf[ mGapStart ], &mBuf[ mGapEnd ],
+ pos - mGapStart );
+ else
+ memmove( &mBuf[ pos + gapLen ], &mBuf[ pos ], mGapStart - pos );
+ mGapEnd += pos - mGapStart;
+ mGapStart += pos - mGapStart;
+}
+
+/*
+** reallocate the text storage in "buf" to have a gap starting at "newGapStart"
+** and a gap size of "newGapLen", preserving the buffer's current contents.
+*/
+void Fl_Text_Buffer::reallocate_with_gap( int newGapStart, int newGapLen ) {
+ char * newBuf;
+ int newGapEnd;
+
+ newBuf = (char *)malloc( mLength + newGapLen );
+ newGapEnd = newGapStart + newGapLen;
+ if ( newGapStart <= mGapStart ) {
+ memcpy( newBuf, mBuf, newGapStart );
+ memcpy( &newBuf[ newGapEnd ], &mBuf[ newGapStart ],
+ mGapStart - newGapStart );
+ memcpy( &newBuf[ newGapEnd + mGapStart - newGapStart ],
+ &mBuf[ mGapEnd ], mLength - mGapStart );
+ } else { /* newGapStart > mGapStart */
+ memcpy( newBuf, mBuf, mGapStart );
+ memcpy( &newBuf[ mGapStart ], &mBuf[ mGapEnd ],
+ newGapStart - mGapStart );
+ memcpy( &newBuf[ newGapEnd ],
+ &mBuf[ mGapEnd + newGapStart - mGapStart ],
+ mLength - newGapStart );
+ }
+ free( (void *) mBuf );
+ mBuf = newBuf;
+ mGapStart = newGapStart;
+ mGapEnd = newGapEnd;
+#ifdef PURIFY
+{int i; for ( i = mGapStart; i < mGapEnd; i++ ) mBuf[ i ] = '.'; }
+#endif
+}
+
+/*
+** Update all of the selections in "buf" for changes in the buffer's text
+*/
+void Fl_Text_Buffer::update_selections( int pos, int nDeleted,
+ int nInserted ) {
+ mPrimary.update( pos, nDeleted, nInserted );
+ mSecondary.update( pos, nDeleted, nInserted );
+ mHighlight.update( pos, nDeleted, nInserted );
+}
+
+/*
+** Update an individual selection for changes in the corresponding text
+*/
+void Fl_Text_Selection::update( int pos, int nDeleted,
+ int nInserted ) {
+ if ( !mSelected || pos > mEnd )
+ return;
+ if ( pos + nDeleted <= mStart ) {
+ mStart += nInserted - nDeleted;
+ mEnd += nInserted - nDeleted;
+ } else if ( pos <= mStart && pos + nDeleted >= mEnd ) {
+ mStart = pos;
+ mEnd = pos;
+ mSelected = 0;
+ } else if ( pos <= mStart && pos + nDeleted < mEnd ) {
+ mStart = pos;
+ mEnd = nInserted + mEnd - nDeleted;
+ } else if ( pos < mEnd ) {
+ mEnd += nInserted - nDeleted;
+ if ( mEnd <= mStart )
+ mSelected = 0;
+ }
+}
+
+/*
+** Search forwards in buffer "buf" for character "searchChar", starting
+** with the character "startPos", and returning the result in "foundPos"
+** returns 1 if found, 0 if not. (The difference between this and
+** BufSearchForward is that it's optimized for single characters. The
+** overall performance of the text widget is dependent on its ability to
+** count lines quickly, hence searching for a single character: newline)
+*/
+int Fl_Text_Buffer::findchar_forward( int startPos, char searchChar,
+ int *foundPos ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+
+ pos = startPos;
+ while ( pos < mGapStart ) {
+ if ( mBuf[ pos ] == searchChar ) {
+ *foundPos = pos;
+ return 1;
+ }
+ pos++;
+ }
+ while ( pos < mLength ) {
+ if ( mBuf[ pos + gapLen ] == searchChar ) {
+ *foundPos = pos;
+ return 1;
+ }
+ pos++;
+ }
+ *foundPos = mLength;
+ return 0;
+}
+
+/*
+** Search backwards in buffer "buf" for character "searchChar", starting
+** with the character BEFORE "startPos", returning the result in "foundPos"
+** returns 1 if found, 0 if not. (The difference between this and
+** BufSearchBackward is that it's optimized for single characters. The
+** overall performance of the text widget is dependent on its ability to
+** count lines quickly, hence searching for a single character: newline)
+*/
+int Fl_Text_Buffer::findchar_backward( int startPos, char searchChar,
+ int *foundPos ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+
+ if ( startPos == 0 ) {
+ *foundPos = 0;
+ return 0;
+ }
+ pos = startPos == 0 ? 0 : startPos - 1;
+ while ( pos >= mGapStart ) {
+ if ( mBuf[ pos + gapLen ] == searchChar ) {
+ *foundPos = pos;
+ return 1;
+ }
+ pos--;
+ }
+ while ( pos >= 0 ) {
+ if ( mBuf[ pos ] == searchChar ) {
+ *foundPos = pos;
+ return 1;
+ }
+ pos--;
+ }
+ *foundPos = 0;
+ return 0;
+}
+
+/*
+** Copy from "text" to end up to but not including newline (or end of "text")
+** and return the copy as the function value, and the length of the line in
+** "lineLen"
+*/
+static char *copyLine( const char *text, int *lineLen ) {
+ int len = 0;
+ const char *c;
+ char *outStr;
+
+ for ( c = text; *c != '\0' && *c != '\n'; c++ )
+ len++;
+ outStr = (char *)malloc( len + 1 );
+ strncpy( outStr, text, len );
+ outStr[ len ] = '\0';
+ *lineLen = len;
+ return outStr;
+}
+
+/*
+** Count the number of newlines in a null-terminated text string;
+*/
+static int countLines( const char *string ) {
+ const char * c;
+ int lineCount = 0;
+
+ for ( c = string; *c != '\0'; c++ )
+ if ( *c == '\n' ) lineCount++;
+ return lineCount;
+}
+
+/*
+** Measure the width in displayed characters of string "text"
+*/
+static int textWidth( const char *text, int tabDist, char nullSubsChar ) {
+ int width = 0, maxWidth = 0;
+ const char *c;
+
+ for ( c = text; *c != '\0'; c++ ) {
+ if ( *c == '\n' ) {
+ if ( width > maxWidth )
+ maxWidth = width;
+ width = 0;
+ } else
+ width += Fl_Text_Buffer::character_width( *c, width, tabDist, nullSubsChar );
+ }
+ if ( width > maxWidth )
+ return width;
+ return maxWidth;
+}
+
+/*
+** Find the first and last character position in a line within a rectangular
+** selection (for copying). Includes tabs which cross rectStart, but not
+** control characters which do so. Leaves off tabs which cross rectEnd.
+**
+** Technically, the calling routine should convert tab characters which
+** cross the right boundary of the selection to spaces which line up with
+** the edge of the selection. Unfortunately, the additional memory
+** management required in the parent routine to allow for the changes
+** in string size is not worth all the extra work just for a couple of
+** shifted characters, so if a tab protrudes, just lop it off and hope
+** that there are other characters in the selection to establish the right
+** margin for subsequent columnar pastes of this data.
+*/
+void Fl_Text_Buffer::rectangular_selection_boundaries( int lineStartPos,
+ int rectStart, int rectEnd, int *selStart, int *selEnd ) {
+ int pos, width, indent = 0;
+ char c;
+
+ /* find the start of the selection */
+ for ( pos = lineStartPos; pos < mLength; pos++ ) {
+ c = character( pos );
+ if ( c == '\n' )
+ break;
+ width = Fl_Text_Buffer::character_width( c, indent, mTabDist, mNullSubsChar );
+ if ( indent + width > rectStart ) {
+ if ( indent != rectStart && c != '\t' ) {
+ pos++;
+ indent += width;
+ }
+ break;
+ }
+ indent += width;
+ }
+ *selStart = pos;
+
+ /* find the end */
+ for ( ; pos < mLength; pos++ ) {
+ c = character( pos );
+ if ( c == '\n' )
+ break;
+ width = Fl_Text_Buffer::character_width( c, indent, mTabDist, mNullSubsChar );
+ indent += width;
+ if ( indent > rectEnd ) {
+ if ( indent - width != rectEnd && c != '\t' )
+ pos++;
+ break;
+ }
+ }
+ *selEnd = pos;
+}
+
+/*
+** Adjust the space and tab characters from string "text" so that non-white
+** characters remain stationary when the text is shifted from starting at
+** "origIndent" to starting at "newIndent". Returns an allocated string
+** which must be freed by the caller with XtFree.
+*/
+static char *realignTabs( const char *text, int origIndent, int newIndent,
+ int tabDist, int useTabs, char nullSubsChar, int *newLength ) {
+ char * expStr, *outStr;
+ int len;
+
+ /* If the tabs settings are the same, retain original tabs */
+ if ( origIndent % tabDist == newIndent % tabDist ) {
+ len = strlen( text );
+ outStr = (char *)malloc( len + 1 );
+ strcpy( outStr, text );
+ *newLength = len;
+ return outStr;
+ }
+
+ /* If the tab settings are not the same, brutally convert tabs to
+ spaces, then back to tabs in the new position */
+ expStr = expandTabs( text, origIndent, tabDist, nullSubsChar, &len );
+ if ( !useTabs ) {
+ *newLength = len;
+ return expStr;
+ }
+ outStr = unexpandTabs( expStr, newIndent, tabDist, nullSubsChar, newLength );
+ free( (void *) expStr );
+ return outStr;
+}
+
+/*
+** Expand tabs to spaces for a block of text. The additional parameter
+** "startIndent" if nonzero, indicates that the text is a rectangular selection
+** beginning at column "startIndent"
+*/
+static char *expandTabs( const char *text, int startIndent, int tabDist,
+ char nullSubsChar, int *newLen ) {
+ char * outStr, *outPtr;
+ const char *c;
+ int indent, len, outLen = 0;
+
+ /* rehearse the expansion to figure out length for output string */
+ indent = startIndent;
+ for ( c = text; *c != '\0'; c++ ) {
+ if ( *c == '\t' ) {
+ len = Fl_Text_Buffer::character_width( *c, indent, tabDist, nullSubsChar );
+ outLen += len;
+ indent += len;
+ } else if ( *c == '\n' ) {
+ indent = startIndent;
+ outLen++;
+ } else {
+ indent += Fl_Text_Buffer::character_width( *c, indent, tabDist, nullSubsChar );
+ outLen++;
+ }
+ }
+
+ /* do the expansion */
+ outStr = (char *)malloc( outLen + 1 );
+ outPtr = outStr;
+ indent = startIndent;
+ for ( c = text; *c != '\0'; c++ ) {
+ if ( *c == '\t' ) {
+ len = Fl_Text_Buffer::expand_character( *c, indent, outPtr, tabDist, nullSubsChar );
+ outPtr += len;
+ indent += len;
+ } else if ( *c == '\n' ) {
+ indent = startIndent;
+ *outPtr++ = *c;
+ } else {
+ indent += Fl_Text_Buffer::character_width( *c, indent, tabDist, nullSubsChar );
+ *outPtr++ = *c;
+ }
+ }
+ outStr[ outLen ] = '\0';
+ *newLen = outLen;
+ return outStr;
+}
+
+/*
+** Convert sequences of spaces into tabs. The threshold for conversion is
+** when 3 or more spaces can be converted into a single tab, this avoids
+** converting double spaces after a period withing a block of text.
+*/
+static char *unexpandTabs( char *text, int startIndent, int tabDist,
+ char nullSubsChar, int *newLen ) {
+ char * outStr, *outPtr, *c, expandedChar[ FL_TEXT_MAX_EXP_CHAR_LEN ];
+ int indent, len;
+
+ outStr = (char *)malloc( strlen( text ) + 1 );
+ outPtr = outStr;
+ indent = startIndent;
+ for ( c = text; *c != '\0'; ) {
+ if ( *c == ' ' ) {
+ len = Fl_Text_Buffer::expand_character( '\t', indent, expandedChar, tabDist,
+ nullSubsChar );
+ if ( len >= 3 && !strncmp( c, expandedChar, len ) ) {
+ c += len;
+ *outPtr++ = '\t';
+ indent += len;
+ } else {
+ *outPtr++ = *c++;
+ indent++;
+ }
+ } else if ( *c == '\n' ) {
+ indent = startIndent;
+ *outPtr++ = *c++;
+ } else {
+ *outPtr++ = *c++;
+ indent++;
+ }
+ }
+ *outPtr = '\0';
+ *newLen = outPtr - outStr;
+ return outStr;
+}
+
+static int max( int i1, int i2 ) {
+ return i1 >= i2 ? i1 : i2;
+}
+
+static int min( int i1, int i2 ) {
+ return i1 <= i2 ? i1 : i2;
+}
+
+int
+Fl_Text_Buffer::insertfile(const char *file, int pos, int buflen) {
+ FILE *fp; int r;
+ if (!(fp = fopen(file, "r"))) return 1;
+ char *buffer = new char[buflen];
+ for (; (r = fread(buffer, 1, buflen - 1, fp)) > 0; pos += r) {
+ buffer[r] = (char)0;
+ insert(pos, buffer);
+ }
+
+ int e = ferror(fp) ? 2 : 0;
+ fclose(fp);
+ delete[] buffer;
+ return e;
+}
+
+int
+Fl_Text_Buffer::outputfile(const char *file, int start, int end, int buflen) {
+ FILE *fp;
+ if (!(fp = fopen(file, "w"))) return 1;
+ for (int n; (n = min(end - start, buflen)); start += n) {
+ const char *p = text_range(start, start + n);
+ int r = fwrite(p, 1, n, fp);
+ free((void *)p);
+ if (r != n) break;
+ }
+
+ int e = ferror(fp) ? 2 : 0;
+ fclose(fp);
+ return e;
+}
+
+
+//
+// End of "$Id: Fl_Text_Buffer.cxx,v 1.9 2001/07/23 09:50:05 spitzak Exp $".
+//
diff --git a/src/Fl_Text_Display.cxx b/src/Fl_Text_Display.cxx
new file mode 100644
index 000000000..8fa56af5c
--- /dev/null
+++ b/src/Fl_Text_Display.cxx
@@ -0,0 +1,1947 @@
+//
+// "$Id: Fl_Text_Display.cxx,v 1.12 2001/07/23 09:50:05 spitzak Exp $"
+//
+// Copyright Mark Edel. Permission to distribute under the LGPL for
+// the FLTK library granted by Mark Edel.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@fltk.org".
+//
+
+#include <fltk/Fl.h>
+#include <fltk/Fl_Text_Buffer.h>
+#include <fltk/Fl_Text_Display.h>
+#include <fltk/Fl_Style.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+
+#undef min
+#undef max
+
+// Text area margins. Left & right margins should be at least 3 so that
+// there is some room for the overhanging parts of the cursor!
+#define TOP_MARGIN 1
+#define BOTTOM_MARGIN 1
+#define LEFT_MARGIN 3
+#define RIGHT_MARGIN 3
+
+#define NO_HINT -1
+
+/* Masks for text drawing methods. These are or'd together to form an
+ integer which describes what drawing calls to use to draw a string */
+#define FILL_MASK 0x100
+#define SECONDARY_MASK 0x200
+#define PRIMARY_MASK 0x400
+#define HIGHLIGHT_MASK 0x800
+#define STYLE_LOOKUP_MASK 0xff
+
+/* Maximum displayable line length (how many characters will fit across the
+ widest window). This amount of memory is temporarily allocated from the
+ stack in the draw_vline() method for drawing strings */
+#define MAX_DISP_LINE_LEN 1000
+
+static int max( int i1, int i2 );
+static int min( int i1, int i2 );
+static int countlines( const char *string );
+
+// CET - FIXME
+#define TMPFONTWIDTH 6
+
+Fl_Text_Display::Fl_Text_Display(int X, int Y, int W, int H, const char* l)
+ : Fl_Group(X, Y, W, H, l) {
+ mMaxsize = 0;
+ damage_range1_start = damage_range1_end = -1;
+ damage_range2_start = damage_range2_end = -1;
+ dragPos = dragType = dragging = 0;
+ display_insert_position_hint = 0;
+
+ Fl_Group* current = Fl_Group::current();
+ Fl_Group::current(this);
+
+ mVScrollBar = new Fl_Scrollbar(0,0,0,0);
+ mVScrollBar->callback((Fl_Callback*)v_scrollbar_cb, this);
+ mHScrollBar = new Fl_Scrollbar(0,0,0,0);
+ mHScrollBar->callback((Fl_Callback*)h_scrollbar_cb, this);
+ mHScrollBar->type(FL_HORIZONTAL);
+
+ Fl_Group::current(current);
+
+ scrollbar_width(Fl_Style::scrollbar_width);
+ scrollbar_align(Fl_Style::scrollbar_align);
+
+ mCursorOn = 0;
+ mCursorPos = 0;
+ mCursorOldY = -100;
+ mCursorToHint = NO_HINT;
+ mCursorStyle = NORMAL_CURSOR;
+ mCursorPreferredCol = -1;
+ mBuffer = 0;
+ mFirstChar = 0;
+ mLastChar = 0;
+ mNBufferLines = 0;
+ mTopLineNum = mTopLineNumHint = 1;
+ mHorizOffset = mHorizOffsetHint = 0;
+
+ mCursor_color = FL_BLACK;
+
+ mFixedFontWidth = TMPFONTWIDTH;// CET - FIXME
+ mStyleBuffer = 0;
+ mStyleTable = 0;
+ mNStyles = 0;
+ mNVisibleLines = 1;
+ mLineStarts = new int[mNVisibleLines];
+ mLineStarts[0] = 0;
+}
+
+/*
+** Free a text display and release its associated memory. Note, the text
+** BUFFER that the text display displays is a separate entity and is not
+** freed, nor are the style buffer or style table.
+*/
+Fl_Text_Display::~Fl_Text_Display() {
+ delete mVScrollBar;
+ delete mHScrollBar;
+
+ if (mBuffer) mBuffer->remove_modify_callback(buffer_modified_cb, this);
+ delete[] mLineStarts;
+}
+
+/*
+** Attach a text buffer to display, replacing the current buffer (if any)
+*/
+void Fl_Text_Display::buffer( Fl_Text_Buffer *buf ) {
+ /* If the text display is already displaying a buffer, clear it off
+ of the display and remove our callback from it */
+ if ( mBuffer != 0 ) {
+ buffer_modified_cb( 0, 0, mBuffer->length(), 0, 0, this );
+ mBuffer->remove_modify_callback( buffer_modified_cb, this );
+ }
+
+ /* Add the buffer to the display, and attach a callback to the buffer for
+ receiving modification information when the buffer contents change */
+ mBuffer = buf;
+ mBuffer->add_modify_callback( buffer_modified_cb, this );
+
+ /* Update the display */
+ buffer_modified_cb( 0, buf->length(), 0, 0, 0, this );
+}
+
+/*
+** Attach (or remove) highlight information in text display and redisplay.
+** Highlighting information consists of a style buffer which parallels the
+** normal text buffer, but codes font and color information for the display;
+** a style table which translates style buffer codes (indexed by buffer
+** character - 'A') into fonts and colors; and a callback mechanism for
+** as-needed highlighting, triggered by a style buffer entry of
+** "unfinishedStyle". Style buffer can trigger additional redisplay during
+** a normal buffer modification if the buffer contains a primary Fl_Text_Selection
+** (see extendRangeForStyleMods for more information on this protocol).
+**
+** Style buffers, tables and their associated memory are managed by the caller.
+*/
+void
+Fl_Text_Display::highlight_data(Fl_Text_Buffer *styleBuffer,
+ Style_Table_Entry *styleTable,
+ int nStyles, char unfinishedStyle,
+ Unfinished_Style_Cb unfinishedHighlightCB,
+ void *cbArg ) {
+ mStyleBuffer = styleBuffer;
+ mStyleTable = styleTable;
+ mNStyles = nStyles;
+ mUnfinishedStyle = unfinishedStyle;
+ mUnfinishedHighlightCB = unfinishedHighlightCB;
+ mHighlightCBArg = cbArg;
+
+ damage(FL_DAMAGE_EXPOSE);
+}
+
+int Fl_Text_Display::longest_vline() {
+ int longest = 0;
+ for (int i = 0; i < mNVisibleLines; i++)
+ longest = max(longest, measure_vline(i));
+ return longest;
+}
+
+/*
+** Change the size of the displayed text area
+*/
+void Fl_Text_Display::layout() {
+ if (!buffer() || !visible_r()) return;
+ int X = 0, Y = 0, W = w(), H = h();
+ text_box()->inset(X, Y, W, H);
+ text_area.x = X+LEFT_MARGIN;
+ text_area.y = Y+BOTTOM_MARGIN;
+ text_area.w = W-LEFT_MARGIN-RIGHT_MARGIN;
+ text_area.h = H-TOP_MARGIN-BOTTOM_MARGIN;
+ int i;
+
+ /* Find the new maximum font height for this text display */
+ for (i = 0, mMaxsize = fl_height(text_font(), text_size())+leading(); i < mNStyles; i++)
+ mMaxsize = max(mMaxsize, fl_height(mStyleTable[i].font, mStyleTable[i].size)+leading());
+
+ // did we have scrollbars initially?
+ bool hscrollbarvisible = mHScrollBar->visible();
+ bool vscrollbarvisible = mVScrollBar->visible();
+
+ // try without scrollbars first
+ mVScrollBar->clear_visible();
+ mHScrollBar->clear_visible();
+
+ for (int again = 1; again;) {
+ again = 0;
+ /* reallocate and update the line starts array, which may have changed
+ size and / or contents. */
+ int nvlines = (text_area.h + mMaxsize - 1) / mMaxsize;
+ if (mNVisibleLines != nvlines) {
+ mNVisibleLines = nvlines;
+ delete[] mLineStarts;
+ mLineStarts = new int [mNVisibleLines];
+ calc_line_starts(0, mNVisibleLines);
+ calc_last_char();
+ }
+
+ // figure the scrollbars
+ if (scrollbar_width()) {
+ /* Decide if the vertical scroll bar needs to be visible */
+ if (scrollbar_align() & (FL_ALIGN_LEFT|FL_ALIGN_RIGHT) &&
+ mNBufferLines >= mNVisibleLines - 1)
+ {
+ mVScrollBar->set_visible();
+ if (scrollbar_align() & FL_ALIGN_LEFT) {
+ text_area.x = X+scrollbar_width()+LEFT_MARGIN;
+ text_area.w = W-scrollbar_width()-LEFT_MARGIN-RIGHT_MARGIN;
+ mVScrollBar->resize(X, text_area.y-TOP_MARGIN, scrollbar_width(),
+ text_area.h+TOP_MARGIN+BOTTOM_MARGIN);
+ } else {
+ text_area.x = X+LEFT_MARGIN;
+ text_area.w = W-scrollbar_width()-LEFT_MARGIN-RIGHT_MARGIN;
+ mVScrollBar->resize(X+W-scrollbar_width(), text_area.y-TOP_MARGIN,
+ scrollbar_width(), text_area.h+TOP_MARGIN+BOTTOM_MARGIN);
+ }
+ }
+
+ /*
+ Decide if the horizontal scroll bar needs to be visible. If there
+ is a vertical scrollbar, a horizontal is always created too. This
+ is because the alternatives are unatractive:
+ * Dynamically creating a horizontal scrollbar based on the currently
+ visible lines is what the original nedit does, but it always wastes
+ space for the scrollbar even when it's not used. Since the FLTK
+ widget dynamically allocates the space for the scrollbar and
+ rearranges the widget to make room for it, this would create a very
+ visually displeasing "bounce" effect when the vertical scrollbar is
+ dragged. Trust me, I tried it and it looks really bad.
+ * The other alternative would be to keep track of what the longest
+ line in the entire buffer is and base the scrollbar on that. I
+ didn't do this because I didn't see any easy way to do that using
+ the nedit code and this could involve a lengthy calculation for
+ large buffers. If an efficient and non-costly way of doing this
+ can be found, this might be a way to go.
+ */
+ /* WAS: Suggestion: Try turning the horizontal scrollbar on when
+ you first see a line that is too wide in the window, but then
+ don't turn it off (ie mix both of your solutions). */
+ if (scrollbar_align() & (FL_ALIGN_TOP|FL_ALIGN_BOTTOM) &&
+ (mVScrollBar->visible() || longest_vline() > text_area.w))
+ {
+ if (!mHScrollBar->visible()) {
+ mHScrollBar->set_visible();
+ again = 1; // loop again to see if we now need vert. & recalc sizes
+ }
+ if (scrollbar_align() & FL_ALIGN_TOP) {
+ text_area.y = Y + scrollbar_width()+TOP_MARGIN;
+ text_area.h = H - scrollbar_width()-TOP_MARGIN-BOTTOM_MARGIN;
+ mHScrollBar->resize(text_area.x-LEFT_MARGIN, Y,
+ text_area.w+LEFT_MARGIN+RIGHT_MARGIN, scrollbar_width());
+ } else {
+ text_area.y = Y+TOP_MARGIN;
+ text_area.h = H - scrollbar_width()-TOP_MARGIN-BOTTOM_MARGIN;
+ mHScrollBar->resize(text_area.x-LEFT_MARGIN, Y+H-scrollbar_width(),
+ text_area.w+LEFT_MARGIN+RIGHT_MARGIN, scrollbar_width());
+ }
+ }
+ }
+ }
+
+ // user request to change viewport
+ if (mTopLineNumHint != mTopLineNum || mHorizOffsetHint != mHorizOffset)
+ scroll_(mTopLineNumHint, mHorizOffsetHint);
+
+ // everything will fit in the viewport
+ if (mNBufferLines < mNVisibleLines)
+ scroll_(1, mHorizOffset);
+ /* if empty lines become visible, there may be an opportunity to
+ display more text by scrolling down */
+ else while (mLineStarts[mNVisibleLines-2] == -1)
+ scroll_(mTopLineNum-1, mHorizOffset);
+
+ // user request to display insert position
+ if (display_insert_position_hint)
+ display_insert();
+
+ // in case horizontal offset is now greater than longest line
+ int maxhoffset = max(0, longest_vline()-text_area.w);
+ if (mHorizOffset > maxhoffset)
+ scroll_(mTopLineNumHint, maxhoffset);
+
+ mTopLineNumHint = mTopLineNum;
+ mHorizOffsetHint = mHorizOffset;
+ display_insert_position_hint = 0;
+
+ if (hscrollbarvisible != mHScrollBar->visible() ||
+ vscrollbarvisible != mVScrollBar->visible())
+ redraw();
+
+ update_v_scrollbar();
+ update_h_scrollbar();
+ damage(FL_DAMAGE_CHILD);
+
+ // clear the layout flag
+ damage(damage()&(~FL_DAMAGE_LAYOUT));
+}
+
+/*
+** Refresh a rectangle of the text display. left and top are in coordinates of
+** the text drawing window
+*/
+void Fl_Text_Display::draw_text( int left, int top, int width, int height ) {
+ int fontHeight, firstLine, lastLine, line;
+
+ /* find the line number range of the display */
+ fontHeight = mMaxsize;
+ firstLine = ( top - text_area.y - fontHeight + 1 ) / fontHeight;
+ lastLine = ( top + height - text_area.y ) / fontHeight + 1;
+
+ fl_push_clip( left, top, width, height );
+
+ /* draw the lines */
+ for ( line = firstLine; line <= lastLine; line++ )
+ draw_vline( line, left, left + width, 0, INT_MAX );
+
+ fl_pop_clip();
+}
+
+void Fl_Text_Display::redisplay_range(int start, int end) {
+ if (damage_range1_start == -1 && damage_range1_end == -1) {
+ damage_range1_start = start;
+ damage_range1_end = end;
+ } else if ((start >= damage_range1_start && start <= damage_range1_end) ||
+ (end >= damage_range1_start && end <= damage_range1_end)) {
+ damage_range1_start = min(damage_range1_start, start);
+ damage_range1_end = max(damage_range1_end, end);
+ } else if (damage_range2_start == -1 && damage_range2_end == -1) {
+ damage_range2_start = start;
+ damage_range2_end = end;
+ } else {
+ damage_range2_start = min(damage_range2_start, start);
+ damage_range2_end = max(damage_range2_end, end);
+ }
+ damage(FL_DAMAGE_SCROLL);
+}
+/*
+** Refresh all of the text between buffer positions "start" and "end"
+** not including the character at the position "end".
+** If end points beyond the end of the buffer, refresh the whole display
+** after pos, including blank lines which are not technically part of
+** any range of characters.
+*/
+void Fl_Text_Display::draw_range(int start, int end) {
+ int i, startLine, lastLine, startIndex, endIndex;
+
+ /* If the range is outside of the displayed text, just return */
+ if ( end < mFirstChar || ( start > mLastChar &&
+ !empty_vlines() ) )
+ return;
+
+ /* Clean up the starting and ending values */
+ if ( start < 0 ) start = 0;
+ if ( start > mBuffer->length() ) start = mBuffer->length();
+ if ( end < 0 ) end = 0;
+ if ( end > mBuffer->length() ) end = mBuffer->length();
+
+ /* Get the starting and ending lines */
+ if ( start < mFirstChar )
+ start = mFirstChar;
+ if ( !position_to_line( start, &startLine ) )
+ startLine = mNVisibleLines - 1;
+ if ( end >= mLastChar ) {
+ lastLine = mNVisibleLines - 1;
+ } else {
+ if ( !position_to_line( end, &lastLine ) ) {
+ /* shouldn't happen */
+ lastLine = mNVisibleLines - 1;
+ }
+ }
+
+ /* Get the starting and ending positions within the lines */
+ startIndex = mLineStarts[ startLine ] == -1 ? 0 :
+ start - mLineStarts[ startLine ];
+ if ( end >= mLastChar )
+ endIndex = INT_MAX;
+ else if ( mLineStarts[ lastLine ] == -1 )
+ endIndex = 0;
+ else
+ endIndex = end - mLineStarts[ lastLine ];
+
+ /* If the starting and ending lines are the same, redisplay the single
+ line between "start" and "end" */
+ if ( startLine == lastLine ) {
+ draw_vline( startLine, 0, INT_MAX, startIndex, endIndex );
+ return;
+ }
+
+ /* Redisplay the first line from "start" */
+ draw_vline( startLine, 0, INT_MAX, startIndex, INT_MAX );
+
+ /* Redisplay the lines in between at their full width */
+ for ( i = startLine + 1; i < lastLine; i++ )
+ draw_vline( i, 0, INT_MAX, 0, INT_MAX );
+
+ /* Redisplay the last line to "end" */
+ draw_vline( lastLine, 0, INT_MAX, 0, endIndex );
+}
+
+/*
+** Set the position of the text insertion cursor for text display
+*/
+void Fl_Text_Display::insert_position( int newPos ) {
+ /* make sure new position is ok, do nothing if it hasn't changed */
+ if ( newPos == mCursorPos )
+ return;
+ if ( newPos < 0 ) newPos = 0;
+ if ( newPos > mBuffer->length() ) newPos = mBuffer->length();
+
+ /* cursor movement cancels vertical cursor motion column */
+ mCursorPreferredCol = -1;
+
+ /* erase the cursor at it's previous position */
+ redisplay_range(mCursorPos - 1, mCursorPos + 1);
+
+ mCursorPos = newPos;
+
+ /* draw cursor at its new position */
+ redisplay_range(mCursorPos - 1, mCursorPos + 1);
+}
+
+void Fl_Text_Display::show_cursor(int b) {
+ mCursorOn = b;
+ redisplay_range(mCursorPos - 1, mCursorPos + 1);
+}
+
+void Fl_Text_Display::cursor_style(int style) {
+ mCursorStyle = style;
+ if (mCursorOn) show_cursor();
+}
+
+/*
+** Insert "text" at the current cursor location. This has the same
+** effect as inserting the text into the buffer using BufInsert and
+** then moving the insert position after the newly inserted text, except
+** that it's optimized to do less redrawing.
+*/
+void Fl_Text_Display::insert(const char* text) {
+ int pos = mCursorPos;
+
+ mCursorToHint = pos + strlen( text );
+ mBuffer->insert( pos, text );
+ mCursorToHint = NO_HINT;
+}
+
+/*
+** Insert "text" (which must not contain newlines), overstriking the current
+** cursor location.
+*/
+void Fl_Text_Display::overstrike(const char* text) {
+ int startPos = mCursorPos;
+ Fl_Text_Buffer *buf = mBuffer;
+ int lineStart = buf->line_start( startPos );
+ int textLen = strlen( text );
+ int i, p, endPos, indent, startIndent, endIndent;
+ const char *c;
+ char ch, *paddedText = NULL;
+
+ /* determine how many displayed character positions are covered */
+ startIndent = mBuffer->count_displayed_characters( lineStart, startPos );
+ indent = startIndent;
+ for ( c = text; *c != '\0'; c++ )
+ indent += Fl_Text_Buffer::character_width( *c, indent, buf->tab_distance(), buf->null_substitution_character() );
+ endIndent = indent;
+
+ /* find which characters to remove, and if necessary generate additional
+ padding to make up for removed control characters at the end */
+ indent = startIndent;
+ for ( p = startPos; ; p++ ) {
+ if ( p == buf->length() )
+ break;
+ ch = buf->character( p );
+ if ( ch == '\n' )
+ break;
+ indent += Fl_Text_Buffer::character_width( ch, indent, buf->tab_distance(), buf->null_substitution_character() );
+ if ( indent == endIndent ) {
+ p++;
+ break;
+ } else if ( indent > endIndent ) {
+ if ( ch != '\t' ) {
+ p++;
+ paddedText = new char [ textLen + FL_TEXT_MAX_EXP_CHAR_LEN + 1 ];
+ strcpy( paddedText, text );
+ for ( i = 0; i < indent - endIndent; i++ )
+ paddedText[ textLen + i ] = ' ';
+ paddedText[ textLen + i ] = '\0';
+ }
+ break;
+ }
+ }
+ endPos = p;
+
+ mCursorToHint = startPos + textLen;
+ buf->replace( startPos, endPos, paddedText == NULL ? text : paddedText );
+ mCursorToHint = NO_HINT;
+ if ( paddedText != NULL )
+ delete [] paddedText;
+}
+
+/*
+** Translate a buffer text position to the XY location where the top left
+** of the cursor would be positioned to point to that character. Returns
+** 0 if the position is not displayed because it is VERTICALLY out
+** of view. If the position is horizontally out of view, returns the
+** X coordinate where the position would be if it were visible.
+*/
+
+int Fl_Text_Display::position_to_xy( int pos, int* X, int* Y ) {
+ int charIndex, lineStartPos, fontHeight, lineLen;
+ int visLineNum, charLen, outIndex, xStep, charStyle;
+ char expandedChar[ FL_TEXT_MAX_EXP_CHAR_LEN ];
+ const char *lineStr;
+
+ /* If position is not displayed, return false */
+ if (pos < mFirstChar || (pos > mLastChar && !empty_vlines()))
+ return 0;
+
+ /* Calculate Y coordinate */
+ if (!position_to_line(pos, &visLineNum)) return 0;
+ fontHeight = mMaxsize;
+ *Y = text_area.y + visLineNum * fontHeight;
+
+ /* Get the text, length, and buffer position of the line. If the position
+ is beyond the end of the buffer and should be at the first position on
+ the first empty line, don't try to get or scan the text */
+ lineStartPos = mLineStarts[visLineNum];
+ if ( lineStartPos == -1 ) {
+ *X = text_area.x - mHorizOffset;
+ return 1;
+ }
+ lineLen = vline_length( visLineNum );
+ lineStr = mBuffer->text_range( lineStartPos, lineStartPos + lineLen );
+
+ /* Step through character positions from the beginning of the line
+ to "pos" to calculate the X coordinate */
+ xStep = text_area.x - mHorizOffset;
+ outIndex = 0;
+ for ( charIndex = 0; charIndex < pos - lineStartPos; charIndex++ ) {
+ charLen = Fl_Text_Buffer::expand_character( lineStr[ charIndex ], outIndex, expandedChar,
+ mBuffer->tab_distance(), mBuffer->null_substitution_character() );
+ charStyle = position_style( lineStartPos, lineLen, charIndex,
+ outIndex );
+ xStep += string_width( expandedChar, charLen, charStyle );
+ outIndex += charLen;
+ }
+ *X = xStep;
+ delete [] (char *)lineStr;
+ return 1;
+}
+
+/*
+** Find the line number of position "pos". Note: this only works for
+** displayed lines. If the line is not displayed, the function returns
+** 0 (without the lineStarts array it could turn in to very long
+** calculation involving scanning large amounts of text in the buffer).
+*/
+int Fl_Text_Display::position_to_linecol( int pos, int* lineNum, int* column ) {
+ int retVal;
+
+ retVal = position_to_line( pos, lineNum );
+ if ( retVal ) {
+ *column = mBuffer->count_displayed_characters(
+ mLineStarts[ *lineNum ], pos );
+ *lineNum += mTopLineNum;
+ }
+ return retVal;
+}
+
+/*
+** Return 1 if position (X, Y) is inside of the primary Fl_Text_Selection
+*/
+int Fl_Text_Display::in_selection( int X, int Y ) {
+ int row, column, pos = xy_to_position( X, Y, CHARACTER_POS );
+ Fl_Text_Buffer *buf = mBuffer;
+
+ xy_to_rowcol( X, Y, &row, &column, CHARACTER_POS );
+ return buf->primary_selection()->includes(pos, buf->line_start( pos ), column);
+}
+
+/*
+** Scroll the display to bring insertion cursor into view.
+**
+** Note: it would be nice to be able to do this without counting lines twice
+** (scroll_() counts them too) and/or to count from the most efficient
+** starting point, but the efficiency of this routine is not as important to
+** the overall performance of the text display.
+*/
+void Fl_Text_Display::display_insert() {
+ int hOffset, topLine, X, Y;
+ hOffset = mHorizOffset;
+ topLine = mTopLineNum;
+
+ if (insert_position() < mFirstChar) {
+ topLine -= buffer()->count_lines(insert_position(), mFirstChar);
+ } else if (mLineStarts[mNVisibleLines-2] != -1) {
+ int lastChar = buffer()->line_end(mLineStarts[mNVisibleLines-2]);
+ if (insert_position() > lastChar)
+ topLine += buffer()->count_lines(lastChar, insert_position());
+ }
+ /* Find the new setting for horizontal offset (this is a bit ungraceful).
+ If the line is visible, just use PositionToXY to get the position
+ to scroll to, otherwise, do the vertical scrolling first, then the
+ horizontal */
+ if (!position_to_xy( mCursorPos, &X, &Y )) {
+ scroll_(topLine, hOffset);
+ if (!position_to_xy( mCursorPos, &X, &Y ))
+ return; /* Give up, it's not worth it (but why does it fail?) */
+ }
+ if (X > text_area.x + text_area.w)
+ hOffset += X-(text_area.x + text_area.w);
+ else if (X < text_area.x)
+ hOffset += X-text_area.x;
+
+ /* Do the scroll */
+ if (topLine != mTopLineNum || hOffset != mHorizOffset)
+ scroll_(topLine, hOffset);
+}
+
+void Fl_Text_Display::show_insert_position() {
+ display_insert_position_hint = 1;
+ relayout();
+}
+
+/*
+** Cursor movement functions
+*/
+int Fl_Text_Display::move_right() {
+ if ( mCursorPos >= mBuffer->length() )
+ return 0;
+ insert_position( mCursorPos + 1 );
+ return 1;
+}
+
+int Fl_Text_Display::move_left() {
+ if ( mCursorPos <= 0 )
+ return 0;
+ insert_position( mCursorPos - 1 );
+ return 1;
+}
+
+int Fl_Text_Display::move_up() {
+ int lineStartPos, column, prevLineStartPos, newPos, visLineNum;
+
+ /* Find the position of the start of the line. Use the line starts array
+ if possible */
+ if ( position_to_line( mCursorPos, &visLineNum ) )
+ lineStartPos = mLineStarts[ visLineNum ];
+ else {
+ lineStartPos = buffer()->line_start( mCursorPos );
+ visLineNum = -1;
+ }
+ if ( lineStartPos == 0 )
+ return 0;
+
+ /* Decide what column to move to, if there's a preferred column use that */
+ column = mCursorPreferredCol >= 0 ? mCursorPreferredCol :
+ mBuffer->count_displayed_characters( lineStartPos, mCursorPos );
+
+ /* count forward from the start of the previous line to reach the column */
+ if ( visLineNum != -1 && visLineNum != 0 )
+ prevLineStartPos = mLineStarts[ visLineNum - 1 ];
+ else
+ prevLineStartPos = buffer()->rewind_lines( lineStartPos, 1 );
+ newPos = mBuffer->skip_displayed_characters( prevLineStartPos, column );
+
+ /* move the cursor */
+ insert_position( newPos );
+
+ /* if a preferred column wasn't aleady established, establish it */
+ mCursorPreferredCol = column;
+ return 1;
+}
+int Fl_Text_Display::move_down() {
+ int lineStartPos, column, nextLineStartPos, newPos, visLineNum;
+
+ if ( mCursorPos == mBuffer->length() )
+ return 0;
+ if ( position_to_line( mCursorPos, &visLineNum ) )
+ lineStartPos = mLineStarts[ visLineNum ];
+ else {
+ lineStartPos = buffer()->line_start( mCursorPos );
+ visLineNum = -1;
+ }
+ column = mCursorPreferredCol >= 0 ? mCursorPreferredCol :
+ mBuffer->count_displayed_characters( lineStartPos, mCursorPos );
+ nextLineStartPos = buffer()->skip_lines( lineStartPos, 1 );
+ newPos = mBuffer->skip_displayed_characters( nextLineStartPos, column );
+
+ insert_position( newPos );
+ mCursorPreferredCol = column;
+ return 1;
+}
+
+void Fl_Text_Display::next_word() {
+ int pos = insert_position();
+ while ( pos < buffer()->length() && (
+ isalnum( buffer()->character( pos ) ) || buffer()->character( pos ) == '_' ) ) {
+ pos++;
+ }
+ while ( pos < buffer()->length() && !( isalnum( buffer()->character( pos ) ) || buffer()->character( pos ) == '_' ) ) {
+ pos++;
+ }
+
+ insert_position( pos );
+}
+
+void Fl_Text_Display::previous_word() {
+ int pos = insert_position();
+ pos--;
+ while ( pos && !( isalnum( buffer()->character( pos ) ) || buffer()->character( pos ) == '_' ) ) {
+ pos--;
+ }
+ while ( pos && ( isalnum( buffer()->character( pos ) ) || buffer()->character( pos ) == '_' ) ) {
+ pos--;
+ }
+ if ( !( isalnum( buffer()->character( pos ) ) || buffer()->character( pos ) == '_' ) ) pos++;
+
+ insert_position( pos );
+}
+
+/*
+** Callback attached to the text buffer to receive modification information
+*/
+void Fl_Text_Display::buffer_modified_cb( int pos, int nInserted, int nDeleted,
+ int nRestyled, const char *deletedText, void *cbArg ) {
+ int linesInserted, linesDeleted, startDispPos, endDispPos;
+ Fl_Text_Display *textD = ( Fl_Text_Display * ) cbArg;
+ Fl_Text_Buffer *buf = textD->mBuffer;
+ int scrolled, origCursorPos = textD->mCursorPos;
+
+ // refigure scrollbars & stuff
+ textD->relayout();
+
+ /* buffer modification cancels vertical cursor motion column */
+ if ( nInserted != 0 || nDeleted != 0 )
+ textD->mCursorPreferredCol = -1;
+
+ /* Count the number of lines inserted and deleted */
+ linesInserted = nInserted == 0 ? 0 :
+ textD->buffer()->count_lines( pos, pos + nInserted );
+ linesDeleted = nDeleted == 0 ? 0 : countlines( deletedText );
+
+ /* Update the line starts and topLineNum */
+ if ( nInserted != 0 || nDeleted != 0 ) {
+ textD->update_line_starts( pos, nInserted, nDeleted, linesInserted,
+ linesDeleted, &scrolled );
+ } else
+ scrolled = 0;
+
+ /* Update the line count for the whole buffer */
+ textD->mNBufferLines += linesInserted - linesDeleted;
+
+ /* Update the cursor position */
+ if ( textD->mCursorToHint != NO_HINT ) {
+ textD->mCursorPos = textD->mCursorToHint;
+ textD->mCursorToHint = NO_HINT;
+ } else if ( textD->mCursorPos > pos ) {
+ if ( textD->mCursorPos < pos + nDeleted )
+ textD->mCursorPos = pos;
+ else
+ textD->mCursorPos += nInserted - nDeleted;
+ }
+
+ // don't need to do anything else if not visible?
+ if (!textD->visible_r()) return;
+
+ /* If the changes caused scrolling, re-paint everything and we're done. */
+ if ( scrolled ) {
+ textD->damage(FL_DAMAGE_EXPOSE);
+ if ( textD->mStyleBuffer ) /* See comments in extendRangeForStyleMods */
+ textD->mStyleBuffer->primary_selection()->selected(0);
+ return;
+ }
+
+ /* If the changes didn't cause scrolling, decide the range of characters
+ that need to be re-painted. Also if the cursor position moved, be
+ sure that the redisplay range covers the old cursor position so the
+ old cursor gets erased, and erase the bits of the cursor which extend
+ beyond the left and right edges of the text. */
+ startDispPos = pos;
+ if ( origCursorPos == startDispPos && textD->mCursorPos != startDispPos )
+ startDispPos = min( startDispPos, origCursorPos - 1 );
+ if ( linesInserted == linesDeleted ) {
+ if ( nInserted == 0 && nDeleted == 0 )
+ endDispPos = pos + nRestyled;
+ else {
+ endDispPos = buf->line_end( pos + nInserted ) + 1;
+ // CET - FIXME if ( origCursorPos >= startDispPos &&
+ // ( origCursorPos <= endDispPos || endDispPos == buf->length() ) )
+ }
+
+ } else {
+ endDispPos = textD->mLastChar + 1;
+ // CET - FIXME if ( origCursorPos >= pos )
+ }
+
+ /* If there is a style buffer, check if the modification caused additional
+ changes that need to be redisplayed. (Redisplaying separately would
+ cause double-redraw on almost every modification involving styled
+ text). Extend the redraw range to incorporate style changes */
+ if ( textD->mStyleBuffer )
+ textD->extend_range_for_styles( &startDispPos, &endDispPos );
+
+ /* Redisplay computed range */
+ textD->redisplay_range( startDispPos, endDispPos );
+}
+
+/*
+** Find the line number of position "pos" relative to the first line of
+** displayed text. Returns 0 if the line is not displayed.
+*/
+int Fl_Text_Display::position_to_line( int pos, int *lineNum ) {
+ int i;
+
+ if ( pos < mFirstChar )
+ return 0;
+ if ( pos > mLastChar ) {
+ if ( empty_vlines() ) {
+ if ( mLastChar < mBuffer->length() ) {
+ if ( !position_to_line( mLastChar, lineNum ) ) {
+ fprintf( stderr, "Consistency check ptvl failed\n" );
+ return 0;
+ }
+ return ++( *lineNum ) <= mNVisibleLines - 1;
+ } else {
+ position_to_line( mLastChar - 1, lineNum );
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ for ( i = mNVisibleLines - 1; i >= 0; i-- ) {
+ if ( mLineStarts[ i ] != -1 && pos >= mLineStarts[ i ] ) {
+ *lineNum = i;
+ return 1;
+ }
+ }
+ return 0; /* probably never be reached */
+}
+
+/*
+** Draw the text on a single line represented by "visLineNum" (the
+** number of lines down from the top of the display), limited by
+** "leftClip" and "rightClip" window coordinates and "leftCharIndex" and
+** "rightCharIndex" character positions (not including the character at
+** position "rightCharIndex").
+*/
+void Fl_Text_Display::draw_vline(int visLineNum, int leftClip, int rightClip,
+ int leftCharIndex, int rightCharIndex) {
+ Fl_Text_Buffer * buf = mBuffer;
+ int i, X, Y, startX, charIndex, lineStartPos, lineLen, fontHeight;
+ int stdCharWidth, charWidth, startIndex, charStyle, style;
+ int charLen, outStartIndex, outIndex, cursorX, hasCursor = 0;
+ int dispIndexOffset, cursorPos = mCursorPos;
+ char expandedChar[ FL_TEXT_MAX_EXP_CHAR_LEN ], outStr[ MAX_DISP_LINE_LEN ];
+ char *outPtr;
+ const char *lineStr;
+
+ /* If line is not displayed, skip it */
+ if ( visLineNum < 0 || visLineNum > mNVisibleLines )
+ return;
+
+ /* Calculate Y coordinate of the string to draw */
+ fontHeight = mMaxsize;
+ Y = text_area.y + visLineNum * fontHeight;
+
+ /* Get the text, length, and buffer position of the line to display */
+ lineStartPos = mLineStarts[ visLineNum ];
+ if ( lineStartPos == -1 ) {
+ lineLen = 0;
+ lineStr = NULL;
+ } else {
+ lineLen = vline_length( visLineNum );
+ lineStr = buf->text_range( lineStartPos, lineStartPos + lineLen );
+ }
+
+ /* Space beyond the end of the line is still counted in units of characters
+ of a standardized character width (this is done mostly because style
+ changes based on character position can still occur in this region due
+ to rectangular Fl_Text_Selections). stdCharWidth must be non-zero to prevent a
+ potential infinite loop if X does not advance */
+ stdCharWidth = TMPFONTWIDTH; //mFontStruct->max_bounds.width;
+ if ( stdCharWidth <= 0 ) {
+ fprintf( stderr, "Internal Error, bad font measurement\n" );
+ delete [] (char *)lineStr;
+ return;
+ }
+
+ /* Shrink the clipping range to the active display area */
+ leftClip = max( text_area.x, leftClip );
+ rightClip = min( rightClip, text_area.x + text_area.w );
+
+ /* Rectangular Fl_Text_Selections are based on "real" line starts (after a newline
+ or start of buffer). Calculate the difference between the last newline
+ position and the line start we're using. Since scanning back to find a
+ newline is expensive, only do so if there's actually a rectangular
+ Fl_Text_Selection which needs it */
+ dispIndexOffset = 0;
+
+ /* Step through character positions from the beginning of the line (even if
+ that's off the left edge of the displayed area) to find the first
+ character position that's not clipped, and the X coordinate for drawing
+ that character */
+ X = text_area.x - mHorizOffset;
+ outIndex = 0;
+ for ( charIndex = 0; ; charIndex++ ) {
+ charLen = charIndex >= lineLen ? 1 :
+ Fl_Text_Buffer::expand_character( lineStr[ charIndex ], outIndex,
+ expandedChar, buf->tab_distance(), buf->null_substitution_character() );
+ style = position_style( lineStartPos, lineLen, charIndex,
+ outIndex + dispIndexOffset );
+ charWidth = charIndex >= lineLen ? stdCharWidth :
+ string_width( expandedChar, charLen, style );
+ if ( X + charWidth >= leftClip && charIndex >= leftCharIndex ) {
+ startIndex = charIndex;
+ outStartIndex = outIndex;
+ startX = X;
+ break;
+ }
+ X += charWidth;
+ outIndex += charLen;
+ }
+
+ /* Scan character positions from the beginning of the clipping range, and
+ draw parts whenever the style changes (also note if the cursor is on
+ this line, and where it should be drawn to take advantage of the x
+ position which we've gone to so much trouble to calculate) */
+ outPtr = outStr;
+ outIndex = outStartIndex;
+ X = startX;
+ for ( charIndex = startIndex; charIndex < rightCharIndex; charIndex++ ) {
+ if ( lineStartPos + charIndex == cursorPos ) {
+ if ( charIndex < lineLen || ( charIndex == lineLen &&
+ cursorPos >= buf->length() ) ) {
+ hasCursor = 1; // CET - FIXME
+ cursorX = X - 1;
+ } else if ( charIndex == lineLen ) {
+ hasCursor = 1;
+ cursorX = X - 1;
+ }
+ }
+ charLen = charIndex >= lineLen ? 1 :
+ Fl_Text_Buffer::expand_character( lineStr[ charIndex ], outIndex, expandedChar,
+ buf->tab_distance(), buf->null_substitution_character() );
+ charStyle = position_style( lineStartPos, lineLen, charIndex,
+ outIndex + dispIndexOffset );
+ for ( i = 0; i < charLen; i++ ) {
+ if ( i != 0 && charIndex < lineLen && lineStr[ charIndex ] == '\t' )
+ charStyle = position_style( lineStartPos, lineLen,
+ charIndex, outIndex + dispIndexOffset );
+ if ( charStyle != style ) {
+ draw_string( style, startX, Y, X, outStr, outPtr - outStr );
+ outPtr = outStr;
+ startX = X;
+ style = charStyle;
+ }
+ if ( charIndex < lineLen ) {
+ *outPtr = expandedChar[ i ];
+ charWidth = string_width( &expandedChar[ i ], 1, charStyle );
+ } else
+ charWidth = stdCharWidth;
+ outPtr++;
+ X += charWidth;
+ outIndex++;
+ }
+ if ( outPtr - outStr + FL_TEXT_MAX_EXP_CHAR_LEN >= MAX_DISP_LINE_LEN || X >= rightClip )
+ break;
+ }
+
+ /* Draw the remaining style segment */
+ draw_string( style, startX, Y, X, outStr, outPtr - outStr );
+
+ /* Draw the cursor if part of it appeared on the redisplayed part of
+ this line. Also check for the cases which are not caught as the
+ line is scanned above: when the cursor appears at the very end
+ of the redisplayed section. */
+ /* CET - FIXME
+ if ( mCursorOn )
+ {
+ if ( hasCursor )
+ draw_cursor( cursorX, Y );
+ else if ( charIndex < lineLen && ( lineStartPos + charIndex + 1 == cursorPos )
+ && X == rightClip )
+ {
+ if ( cursorPos >= buf->length() )
+ draw_cursor( X - 1, Y );
+ else
+ {
+ draw_cursor( X - 1, Y );
+ }
+ }
+ }
+ */
+ if ( lineStr != NULL )
+ delete [] (char *)lineStr;
+}
+
+/*
+** Draw a string or blank area according to parameter "style", using the
+** appropriate colors and drawing method for that style, with top left
+** corner at X, y. If style says to draw text, use "string" as source of
+** characters, and draw "nChars", if style is FILL, erase
+** rectangle where text would have drawn from X to toX and from Y to
+** the maximum Y extent of the current font(s).
+*/
+void Fl_Text_Display::draw_string( int style, int X, int Y, int toX,
+ const char *string, int nChars ) {
+ Style_Table_Entry * styleRec;
+
+ /* Draw blank area rather than text, if that was the request */
+ if ( style & FILL_MASK ) {
+ clear_rect( style, X, Y, toX - X, mMaxsize );
+ return;
+ }
+
+ /* Set font, color, and gc depending on style. For normal text, GCs
+ for normal drawing, or drawing within a Fl_Text_Selection or highlight are
+ pre-allocated and pre-configured. For syntax highlighting, GCs are
+ configured here, on the fly. */
+
+ Fl_Font font = text_font();
+ int size = text_size();
+ Fl_Color foreground;
+ Fl_Color background;
+
+ if ( style & STYLE_LOOKUP_MASK ) {
+ styleRec = &mStyleTable[ ( style & STYLE_LOOKUP_MASK ) - 'A' ];
+ font = styleRec->font;
+ size = styleRec->size;
+ foreground = styleRec->color;
+ background = style & PRIMARY_MASK ? selection_color() :
+ style & HIGHLIGHT_MASK ? highlight_color() : text_background();
+ if ( foreground == background ) /* B&W kludge */
+ foreground = text_background();
+ } else if ( style & HIGHLIGHT_MASK ) {
+ foreground = highlight_label_color();
+ background = highlight_color();
+ } else if ( style & PRIMARY_MASK ) {
+ foreground = selection_text_color();
+ background = selection_color();
+ } else {
+ foreground = text_color();
+ background = text_background();
+ }
+
+ fl_color( background );
+ fl_rectf( X, Y, toX - X, mMaxsize );
+ fl_color( foreground );
+ fl_font( font, size );
+ fl_draw( string, nChars, X, Y + mMaxsize - fl_descent());
+
+ // CET - FIXME
+ /* If any space around the character remains unfilled (due to use of
+ different sized fonts for highlighting), fill in above or below
+ to erase previously drawn characters */
+ /*
+ if (fs->ascent < mAscent)
+ clear_rect( style, X, Y, toX - X, mAscent - fs->ascent);
+ if (fs->descent < mDescent)
+ clear_rect( style, X, Y + mAscent + fs->descent, toX - x,
+ mDescent - fs->descent);
+ */
+ /* Underline if style is secondary Fl_Text_Selection */
+
+ /*
+ if (style & SECONDARY_MASK)
+ XDrawLine(XtDisplay(mW), XtWindow(mW), gc, x,
+ y + mAscent, toX - 1, Y + fs->ascent);
+ */
+}
+
+/*
+** Clear a rectangle with the appropriate background color for "style"
+*/
+void Fl_Text_Display::clear_rect( int style, int X, int Y,
+ int width, int height ) {
+ /* A width of zero means "clear to end of window" to XClearArea */
+ if ( width == 0 )
+ return;
+
+ if ( style & HIGHLIGHT_MASK ) {
+ fl_color( highlight_color() );
+ fl_rectf( X, Y, width, height );
+ } else if ( style & PRIMARY_MASK ) {
+ fl_color( selection_color() );
+ fl_rectf( X, Y, width, height );
+ } else {
+ fl_color( text_background() );
+ fl_rectf( X, Y, width, height );
+ }
+}
+
+
+
+/*
+** Draw a cursor with top center at X, y.
+*/
+void Fl_Text_Display::draw_cursor( int X, int Y ) {
+ typedef struct {
+ int x1, y1, x2, y2;
+ }
+ Segment;
+
+ Segment segs[ 5 ];
+ int left, right, cursorWidth, midY;
+ // int fontWidth = mFontStruct->min_bounds.width, nSegs = 0;
+ int fontWidth = TMPFONTWIDTH; // CET - FIXME
+ int nSegs = 0;
+ int fontHeight = mMaxsize;
+ int bot = Y + fontHeight - 1;
+
+ if ( X < text_area.x - 1 || X > text_area.x + text_area.w )
+ return;
+
+ /* For cursors other than the block, make them around 2/3 of a character
+ width, rounded to an even number of pixels so that X will draw an
+ odd number centered on the stem at x. */
+ cursorWidth = 4; //(fontWidth/3) * 2;
+ left = X - cursorWidth / 2;
+ right = left + cursorWidth;
+
+ /* Create segments and draw cursor */
+ if ( mCursorStyle == CARET_CURSOR ) {
+ midY = bot - fontHeight / 5;
+ segs[ 0 ].x1 = left; segs[ 0 ].y1 = bot; segs[ 0 ].x2 = X; segs[ 0 ].y2 = midY;
+ segs[ 1 ].x1 = X; segs[ 1 ].y1 = midY; segs[ 1 ].x2 = right; segs[ 1 ].y2 = bot;
+ segs[ 2 ].x1 = left; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = X; segs[ 2 ].y2 = midY - 1;
+ segs[ 3 ].x1 = X; segs[ 3 ].y1 = midY - 1; segs[ 3 ].x2 = right; segs[ 3 ].y2 = bot;
+ nSegs = 4;
+ } else if ( mCursorStyle == NORMAL_CURSOR ) {
+ segs[ 0 ].x1 = left; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = right; segs[ 0 ].y2 = Y;
+ segs[ 1 ].x1 = X; segs[ 1 ].y1 = Y; segs[ 1 ].x2 = X; segs[ 1 ].y2 = bot;
+ segs[ 2 ].x1 = left; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = right; segs[ 2 ].y2 = bot;
+ nSegs = 3;
+ } else if ( mCursorStyle == HEAVY_CURSOR ) {
+ segs[ 0 ].x1 = X - 1; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = X - 1; segs[ 0 ].y2 = bot;
+ segs[ 1 ].x1 = X; segs[ 1 ].y1 = Y; segs[ 1 ].x2 = X; segs[ 1 ].y2 = bot;
+ segs[ 2 ].x1 = X + 1; segs[ 2 ].y1 = Y; segs[ 2 ].x2 = X + 1; segs[ 2 ].y2 = bot;
+ segs[ 3 ].x1 = left; segs[ 3 ].y1 = Y; segs[ 3 ].x2 = right; segs[ 3 ].y2 = Y;
+ segs[ 4 ].x1 = left; segs[ 4 ].y1 = bot; segs[ 4 ].x2 = right; segs[ 4 ].y2 = bot;
+ nSegs = 5;
+ } else if ( mCursorStyle == DIM_CURSOR ) {
+ midY = Y + fontHeight / 2;
+ segs[ 0 ].x1 = X; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = X; segs[ 0 ].y2 = Y;
+ segs[ 1 ].x1 = X; segs[ 1 ].y1 = midY; segs[ 1 ].x2 = X; segs[ 1 ].y2 = midY;
+ segs[ 2 ].x1 = X; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = X; segs[ 2 ].y2 = bot;
+ nSegs = 3;
+ } else if ( mCursorStyle == BLOCK_CURSOR ) {
+ right = X + fontWidth;
+ segs[ 0 ].x1 = X; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = right; segs[ 0 ].y2 = Y;
+ segs[ 1 ].x1 = right; segs[ 1 ].y1 = Y; segs[ 1 ].x2 = right; segs[ 1 ].y2 = bot;
+ segs[ 2 ].x1 = right; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = X; segs[ 2 ].y2 = bot;
+ segs[ 3 ].x1 = X; segs[ 3 ].y1 = bot; segs[ 3 ].x2 = X; segs[ 3 ].y2 = Y;
+ nSegs = 4;
+ }
+ fl_color( mCursor_color );
+
+ for ( int k = 0; k < nSegs; k++ ) {
+ fl_line( segs[ k ].x1, segs[ k ].y1, segs[ k ].x2, segs[ k ].y2 );
+ }
+}
+
+/*
+** Determine the drawing method to use to draw a specific character from "buf".
+** "lineStartPos" gives the character index where the line begins, "lineIndex",
+** the number of characters past the beginning of the line, and "dispIndex",
+** the number of displayed characters past the beginning of the line. Passing
+** lineStartPos of -1 returns the drawing style for "no text".
+**
+** Why not just: position_style(pos)? Because style applies to blank areas
+** of the window beyond the text boundaries, and because this routine must also
+** decide whether a position is inside of a rectangular Fl_Text_Selection, and do
+** so efficiently, without re-counting character positions from the start of the
+** line.
+**
+** Note that style is a somewhat incorrect name, drawing method would
+** be more appropriate.
+*/
+int Fl_Text_Display::position_style( int lineStartPos,
+ int lineLen, int lineIndex, int dispIndex ) {
+ Fl_Text_Buffer * buf = mBuffer;
+ Fl_Text_Buffer *styleBuf = mStyleBuffer;
+ int pos, style = 0;
+
+ if ( lineStartPos == -1 || buf == NULL )
+ return FILL_MASK;
+
+ pos = lineStartPos + min( lineIndex, lineLen );
+
+ if ( lineIndex >= lineLen )
+ style = FILL_MASK;
+ else if ( styleBuf != NULL ) {
+ style = ( unsigned char ) styleBuf->character( pos );
+ /*!!! if (style == mUnfinishedStyle) {
+ // encountered "unfinished" style, trigger parsing
+ (mUnfinishedHighlightCB)( pos, mHighlightCBArg);
+ style = (unsigned char) styleBuf->character( pos);
+ }
+ */
+ }
+ if (buf->primary_selection()->includes(pos, lineStartPos, dispIndex))
+ style |= PRIMARY_MASK;
+ if (buf->highlight_selection()->includes(pos, lineStartPos, dispIndex))
+ style |= HIGHLIGHT_MASK;
+ if (buf->secondary_selection()->includes(pos, lineStartPos, dispIndex))
+ style |= SECONDARY_MASK;
+ return style;
+}
+
+/*
+** Find the width of a string in the font of a particular style
+*/
+int Fl_Text_Display::string_width( const char *string, int length, int style ) {
+ Fl_Font font;
+ int size;
+
+ if ( style & STYLE_LOOKUP_MASK ) {
+ font = mStyleTable[ ( style & STYLE_LOOKUP_MASK ) - 'A' ].font;
+ size = mStyleTable[ ( style & STYLE_LOOKUP_MASK ) - 'A' ].size;
+ } else {
+ font = text_font();
+ size = text_size();
+ }
+ fl_font( font, size );
+
+ return ( int ) ( fl_width( string, length ) );
+}
+
+/*
+** Translate window coordinates to the nearest (insert cursor or character
+** cell) text position. The parameter posType specifies how to interpret the
+** position: CURSOR_POS means translate the coordinates to the nearest cursor
+** position, and CHARACTER_POS means return the position of the character
+** closest to (X, Y).
+*/
+int Fl_Text_Display::xy_to_position( int X, int Y, int posType ) {
+ int charIndex, lineStart, lineLen, fontHeight;
+ int charWidth, charLen, charStyle, visLineNum, xStep, outIndex;
+ char expandedChar[ FL_TEXT_MAX_EXP_CHAR_LEN ];
+ const char *lineStr;
+
+ /* Find the visible line number corresponding to the Y coordinate */
+ fontHeight = mMaxsize;
+ visLineNum = ( Y - text_area.y ) / fontHeight;
+ if ( visLineNum < 0 )
+ return mFirstChar;
+ if ( visLineNum >= mNVisibleLines )
+ visLineNum = mNVisibleLines - 1;
+
+ /* Find the position at the start of the line */
+ lineStart = mLineStarts[ visLineNum ];
+
+ /* If the line start was empty, return the last position in the buffer */
+ if ( lineStart == -1 )
+ return mBuffer->length();
+
+ /* Get the line text and its length */
+ lineLen = vline_length( visLineNum );
+ lineStr = mBuffer->text_range( lineStart, lineStart + lineLen );
+
+ /* Step through character positions from the beginning of the line
+ to find the character position corresponding to the X coordinate */
+ xStep = text_area.x - mHorizOffset;
+ outIndex = 0;
+ for ( charIndex = 0; charIndex < lineLen; charIndex++ ) {
+ charLen = Fl_Text_Buffer::expand_character( lineStr[ charIndex ], outIndex, expandedChar,
+ mBuffer->tab_distance(), mBuffer->null_substitution_character() );
+ charStyle = position_style( lineStart, lineLen, charIndex, outIndex );
+ charWidth = string_width( expandedChar, charLen, charStyle );
+ if ( X < xStep + ( posType == CURSOR_POS ? charWidth / 2 : charWidth ) ) {
+ delete [] (char *)lineStr;
+ return lineStart + charIndex;
+ }
+ xStep += charWidth;
+ outIndex += charLen;
+ }
+
+ /* If the X position was beyond the end of the line, return the position
+ of the newline at the end of the line */
+ delete [] (char *)lineStr;
+ return lineStart + lineLen;
+}
+
+/*
+** Translate window coordinates to the nearest row and column number for
+** positioning the cursor. This, of course, makes no sense when the font is
+** proportional, since there are no absolute columns. The parameter posType
+** specifies how to interpret the position: CURSOR_POS means translate the
+** coordinates to the nearest position between characters, and CHARACTER_POS
+** means translate the position to the nearest character cell.
+*/
+void Fl_Text_Display::xy_to_rowcol( int X, int Y, int *row,
+ int *column, int posType ) {
+ int fontHeight = mMaxsize;
+ int fontWidth = TMPFONTWIDTH; //mFontStruct->max_bounds.width;
+
+ /* Find the visible line number corresponding to the Y coordinate */
+ *row = ( Y - text_area.y ) / fontHeight;
+ if ( *row < 0 ) * row = 0;
+ if ( *row >= mNVisibleLines ) * row = mNVisibleLines - 1;
+ *column = ( ( X - text_area.x ) + mHorizOffset +
+ ( posType == CURSOR_POS ? fontWidth / 2 : 0 ) ) / fontWidth;
+ if ( *column < 0 ) * column = 0;
+}
+
+/*
+** Offset the line starts array, topLineNum, firstChar and lastChar, for a new
+** vertical scroll position given by newTopLineNum. If any currently displayed
+** lines will still be visible, salvage the line starts values, otherwise,
+** count lines from the nearest known line start (start or end of buffer, or
+** the closest value in the lineStarts array)
+*/
+void Fl_Text_Display::offset_line_starts( int newTopLineNum ) {
+ int oldTopLineNum = mTopLineNum;
+ int lineDelta = newTopLineNum - oldTopLineNum;
+ int nVisLines = mNVisibleLines;
+ int *lineStarts = mLineStarts;
+ int i, lastLineNum;
+ Fl_Text_Buffer *buf = mBuffer;
+
+ /* If there was no offset, nothing needs to be changed */
+ if ( lineDelta == 0 )
+ return;
+
+ /* Find the new value for firstChar by counting lines from the nearest
+ known line start (start or end of buffer, or the closest value in the
+ lineStarts array) */
+ lastLineNum = oldTopLineNum + nVisLines - 1;
+ if ( newTopLineNum < oldTopLineNum && newTopLineNum < -lineDelta ) {
+ mFirstChar = buffer()->skip_lines( 0, newTopLineNum - 1 );
+ } else if ( newTopLineNum < oldTopLineNum ) {
+ mFirstChar = buffer()->rewind_lines( mFirstChar, -lineDelta );
+ } else if ( newTopLineNum < lastLineNum ) {
+ mFirstChar = lineStarts[ newTopLineNum - oldTopLineNum ];
+ } else if ( newTopLineNum - lastLineNum < mNBufferLines - newTopLineNum ) {
+ mFirstChar = buffer()->skip_lines( lineStarts[ nVisLines - 1 ],
+ newTopLineNum - lastLineNum );
+ } else {
+ mFirstChar = buffer()->rewind_lines( buf->length(), mNBufferLines - newTopLineNum + 1 );
+ }
+
+ /* Fill in the line starts array */
+ if ( lineDelta < 0 && -lineDelta < nVisLines ) {
+ for ( i = nVisLines - 1; i >= -lineDelta; i-- )
+ lineStarts[ i ] = lineStarts[ i + lineDelta ];
+ calc_line_starts( 0, -lineDelta );
+ } else if ( lineDelta > 0 && lineDelta < nVisLines ) {
+ for ( i = 0; i < nVisLines - lineDelta; i++ )
+ lineStarts[ i ] = lineStarts[ i + lineDelta ];
+ calc_line_starts( nVisLines - lineDelta, nVisLines - 1 );
+ } else
+ calc_line_starts( 0, nVisLines );
+
+ /* Set lastChar and topLineNum */
+ calc_last_char();
+ mTopLineNum = newTopLineNum;
+}
+
+/*
+** Update the line starts array, topLineNum, firstChar and lastChar for text
+** display "textD" after a modification to the text buffer, given by the
+** position where the change began "pos", and the nmubers of characters
+** and lines inserted and deleted.
+*/
+void Fl_Text_Display::update_line_starts( int pos, int charsInserted,
+ int charsDeleted, int linesInserted, int linesDeleted, int *scrolled ) {
+ int * lineStarts = mLineStarts;
+ int i, lineOfPos, lineOfEnd, nVisLines = mNVisibleLines;
+ int charDelta = charsInserted - charsDeleted;
+ int lineDelta = linesInserted - linesDeleted;
+
+ /* If all of the changes were before the displayed text, the display
+ doesn't change, just update the top line num and offset the line
+ start entries and first and last characters */
+ if ( pos + charsDeleted < mFirstChar ) {
+ mTopLineNum += lineDelta;
+ for ( i = 0; i < nVisLines; i++ )
+ lineStarts[ i ] += charDelta;
+ mFirstChar += charDelta;
+ mLastChar += charDelta;
+ *scrolled = 0;
+ return;
+ }
+
+ /* The change began before the beginning of the displayed text, but
+ part or all of the displayed text was deleted */
+ if ( pos < mFirstChar ) {
+ /* If some text remains in the window, anchor on that */
+ if ( position_to_line( pos + charsDeleted, &lineOfEnd ) &&
+ ++lineOfEnd < nVisLines && lineStarts[ lineOfEnd ] != -1 ) {
+ mTopLineNum = max( 1, mTopLineNum + lineDelta );
+ mFirstChar = buffer()->rewind_lines( lineStarts[ lineOfEnd ] + charDelta, lineOfEnd );
+ /* Otherwise anchor on original line number and recount everything */
+ } else {
+ if ( mTopLineNum > mNBufferLines + lineDelta ) {
+ mTopLineNum = 1;
+ mFirstChar = 0;
+ } else
+ mFirstChar = buffer()->skip_lines( 0, mTopLineNum - 1 );
+ }
+ calc_line_starts( 0, nVisLines - 1 );
+ /* calculate lastChar by finding the end of the last displayed line */
+ calc_last_char();
+ *scrolled = 1;
+ return;
+ }
+
+ /* If the change was in the middle of the displayed text (it usually is),
+ salvage as much of the line starts array as possible by moving and
+ offsetting the entries after the changed area, and re-counting the
+ added lines or the lines beyond the salvaged part of the line starts
+ array */
+ if ( pos <= mLastChar ) {
+ /* find line on which the change began */
+ position_to_line( pos, &lineOfPos );
+ /* salvage line starts after the changed area */
+ if ( lineDelta == 0 ) {
+ for ( i = lineOfPos + 1; i < nVisLines && lineStarts[ i ] != -1; i++ )
+ lineStarts[ i ] += charDelta;
+ } else if ( lineDelta > 0 ) {
+ for ( i = nVisLines - 1; i >= lineOfPos + lineDelta + 1; i-- )
+ lineStarts[ i ] = lineStarts[ i - lineDelta ] +
+ ( lineStarts[ i - lineDelta ] == -1 ? 0 : charDelta );
+ } else /* (lineDelta < 0) */ {
+ for ( i = max( 0, lineOfPos + 1 ); i < nVisLines + lineDelta; i++ )
+ lineStarts[ i ] = lineStarts[ i - lineDelta ] +
+ ( lineStarts[ i - lineDelta ] == -1 ? 0 : charDelta );
+ }
+ /* fill in the missing line starts */
+ if ( linesInserted >= 0 )
+ calc_line_starts( lineOfPos + 1, lineOfPos + linesInserted );
+ if ( lineDelta < 0 )
+ calc_line_starts( nVisLines + lineDelta, nVisLines );
+ /* calculate lastChar by finding the end of the last displayed line */
+ calc_last_char();
+ *scrolled = 0;
+ return;
+ }
+
+ /* Change was past the end of the displayed text, but displayable by virtue
+ of being an insert at the end of the buffer into visible blank lines */
+ if ( empty_vlines() ) {
+ position_to_line( pos, &lineOfPos );
+ calc_line_starts( lineOfPos, lineOfPos + linesInserted );
+ calc_last_char();
+ *scrolled = 0;
+ return;
+ }
+
+ /* Change was beyond the end of the buffer and not visible, do nothing */
+ *scrolled = 0;
+}
+
+/*
+** Scan through the text in the "textD"'s buffer and recalculate the line
+** starts array values beginning at index "startLine" and continuing through
+** (including) "endLine". It assumes that the line starts entry preceding
+** "startLine" (or mFirstChar if startLine is 0) is good, and re-counts
+** newlines to fill in the requested entries. Out of range values for
+** "startLine" and "endLine" are acceptable.
+*/
+void Fl_Text_Display::calc_line_starts( int startLine, int endLine ) {
+ int startPos, bufLen = mBuffer->length();
+ int line, lineEnd, nextLineStart, nVis = mNVisibleLines;
+ int *lineStarts = mLineStarts;
+
+ /* Clean up (possibly) messy input parameters */
+ if ( endLine < 0 ) endLine = 0;
+ if ( endLine >= nVis ) endLine = nVis - 1;
+ if ( startLine < 0 ) startLine = 0;
+ if ( startLine >= nVis ) startLine = nVis - 1;
+ if ( startLine > endLine )
+ return;
+
+ /* Find the last known good line number -> position mapping */
+ if ( startLine == 0 ) {
+ lineStarts[ 0 ] = mFirstChar;
+ startLine = 1;
+ }
+ startPos = lineStarts[ startLine - 1 ];
+
+ /* If the starting position is already past the end of the text,
+ fill in -1's (means no text on line) and return */
+ if ( startPos == -1 ) {
+ for ( line = startLine; line <= endLine; line++ )
+ lineStarts[ line ] = -1;
+ return;
+ }
+
+ /* Loop searching for ends of lines and storing the positions of the
+ start of the next line in lineStarts */
+ for ( line = startLine; line <= endLine; line++ ) {
+ lineEnd = buffer()->line_end(startPos);
+ nextLineStart = min(buffer()->length(), lineEnd + 1);
+ startPos = nextLineStart;
+ if ( startPos >= bufLen ) {
+ /* If the buffer ends with a newline or line break, put
+ buf->length() in the next line start position (instead of
+ a -1 which is the normal marker for an empty line) to
+ indicate that the cursor may safely be displayed there */
+ if ( line == 0 || ( lineStarts[ line - 1 ] != bufLen &&
+ lineEnd != nextLineStart ) ) {
+ lineStarts[ line ] = bufLen;
+ line++;
+ }
+ break;
+ }
+ lineStarts[ line ] = startPos;
+ }
+
+ /* Set any entries beyond the end of the text to -1 */
+ for ( ; line <= endLine; line++ )
+ lineStarts[ line ] = -1;
+}
+
+/*
+** Given a Fl_Text_Display with a complete, up-to-date lineStarts array, update
+** the lastChar entry to point to the last buffer position displayed.
+*/
+void Fl_Text_Display::calc_last_char() {
+ int i;
+ for (i = mNVisibleLines - 1; i >= 0 && mLineStarts[i] == -1; i--) ;
+ mLastChar = i < 0 ? 0 : buffer()->line_end(mLineStarts[i]);
+}
+
+void Fl_Text_Display::scroll(int topLineNum, int horizOffset) {
+ mTopLineNumHint = topLineNum;
+ mHorizOffsetHint = horizOffset;
+ relayout();
+}
+
+void Fl_Text_Display::scroll_(int topLineNum, int horizOffset) {
+ /* Limit the requested scroll position to allowable values */
+ if (topLineNum > mNBufferLines + 3 - mNVisibleLines)
+ topLineNum = mNBufferLines + 3 - mNVisibleLines;
+ if (topLineNum < 1) topLineNum = 1;
+
+ if (horizOffset > longest_vline() - text_area.w)
+ horizOffset = longest_vline() - text_area.w;
+ if (horizOffset < 0) horizOffset = 0;
+
+ /* Do nothing if scroll position hasn't actually changed or there's no
+ window to draw in yet */
+ if (mHorizOffset == horizOffset && mTopLineNum == topLineNum)
+ return;
+
+ /* If the vertical scroll position has changed, update the line
+ starts array and related counters in the text display */
+ offset_line_starts(topLineNum);
+
+ /* Just setting mHorizOffset is enough information for redisplay */
+ mHorizOffset = horizOffset;
+
+ // redraw all text
+ damage(FL_DAMAGE_EXPOSE);
+}
+
+/*
+** Update the minimum, maximum, slider size, page increment, and value
+** for vertical scroll bar.
+*/
+void Fl_Text_Display::update_v_scrollbar() {
+ /* The Vert. scroll bar value and slider size directly represent the top
+ line number, and the number of visible lines respectively. The scroll
+ bar maximum value is chosen to generally represent the size of the whole
+ buffer, with minor adjustments to keep the scroll bar widget happy */
+ mVScrollBar->value(mTopLineNum, mNVisibleLines, 1, mNBufferLines+2);
+}
+
+/*
+** Update the minimum, maximum, slider size, page increment, and value
+** for the horizontal scroll bar.
+*/
+void Fl_Text_Display::update_h_scrollbar() {
+ int sliderMax = max(longest_vline(), text_area.w + mHorizOffset);
+ mHScrollBar->value( mHorizOffset, text_area.w, 0, sliderMax );
+}
+
+/*
+** Callbacks for drag or valueChanged on scroll bars
+*/
+void Fl_Text_Display::v_scrollbar_cb(Fl_Scrollbar* b, Fl_Text_Display* textD) {
+ if (b->value() == textD->mTopLineNum) return;
+ textD->scroll(b->value(), textD->mHorizOffset);
+}
+
+void Fl_Text_Display::h_scrollbar_cb(Fl_Scrollbar* b, Fl_Text_Display* textD) {
+ if (b->value() == textD->mHorizOffset) return;
+ textD->scroll(textD->mTopLineNum, b->value());
+}
+
+static int max( int i1, int i2 ) {
+ return i1 >= i2 ? i1 : i2;
+}
+
+static int min( int i1, int i2 ) {
+ return i1 <= i2 ? i1 : i2;
+}
+
+/*
+** Count the number of newlines in a null-terminated text string;
+*/
+static int countlines( const char *string ) {
+ const char * c;
+ int lineCount = 0;
+
+ for ( c = string; *c != '\0'; c++ )
+ if ( *c == '\n' ) lineCount++;
+ return lineCount;
+}
+
+/*
+** Return the width in pixels of the displayed line pointed to by "visLineNum"
+*/
+int Fl_Text_Display::measure_vline( int visLineNum ) {
+ int i, width = 0, len, style, lineLen = vline_length( visLineNum );
+ int charCount = 0, lineStartPos = mLineStarts[ visLineNum ];
+ char expandedChar[ FL_TEXT_MAX_EXP_CHAR_LEN ];
+
+ if ( mStyleBuffer == NULL ) {
+ for ( i = 0; i < lineLen; i++ ) {
+ len = mBuffer->expand_character( lineStartPos + i,
+ charCount, expandedChar );
+
+ fl_font( text_font(), text_size() );
+
+ width += ( int ) fl_width( expandedChar, len );
+
+ charCount += len;
+ }
+ } else {
+ for ( i = 0; i < lineLen; i++ ) {
+ len = mBuffer->expand_character( lineStartPos + i,
+ charCount, expandedChar );
+ style = ( unsigned char ) mStyleBuffer->character(
+ lineStartPos + i ) - 'A';
+
+ fl_font( mStyleTable[ style ].font, mStyleTable[ style ].size );
+
+ width += ( int ) fl_width( expandedChar, len );
+
+ charCount += len;
+ }
+ }
+ return width;
+}
+
+/*
+** Return true if there are lines visible with no corresponding buffer text
+*/
+int Fl_Text_Display::empty_vlines() {
+ return mNVisibleLines > 0 &&
+ mLineStarts[ mNVisibleLines - 1 ] == -1;
+}
+
+/*
+** Return the length of a line (number of displayable characters) by examining
+** entries in the line starts array rather than by scanning for newlines
+*/
+int Fl_Text_Display::vline_length( int visLineNum ) {
+ int nextLineStart, lineStartPos = mLineStarts[ visLineNum ];
+
+ if ( lineStartPos == -1 )
+ return 0;
+ if ( visLineNum + 1 >= mNVisibleLines )
+ return mLastChar - lineStartPos;
+ nextLineStart = mLineStarts[ visLineNum + 1 ];
+ if ( nextLineStart == -1 )
+ return mLastChar - lineStartPos;
+ return nextLineStart - 1 - lineStartPos;
+}
+
+/*
+** Extend the range of a redraw request (from *start to *end) with additional
+** redraw requests resulting from changes to the attached style buffer (which
+** contains auxiliary information for coloring or styling text).
+*/
+void Fl_Text_Display::extend_range_for_styles( int *start, int *end ) {
+ Fl_Text_Selection * sel = mStyleBuffer->primary_selection();
+ int extended = 0;
+
+ /* The peculiar protocol used here is that modifications to the style
+ buffer are marked by selecting them with the buffer's primary Fl_Text_Selection.
+ The style buffer is usually modified in response to a modify callback on
+ the text buffer BEFORE Fl_Text_Display.c's modify callback, so that it can keep
+ the style buffer in step with the text buffer. The style-update
+ callback can't just call for a redraw, because Fl_Text_Display hasn't processed
+ the original text changes yet. Anyhow, to minimize redrawing and to
+ avoid the complexity of scheduling redraws later, this simple protocol
+ tells the text display's buffer modify callback to extend it's redraw
+ range to show the text color/and font changes as well. */
+ if ( sel->selected() ) {
+ if ( sel->start() < *start ) {
+ *start = sel->start();
+ extended = 1;
+ }
+ if ( sel->end() > *end ) {
+ *end = sel->end();
+ extended = 1;
+ }
+ }
+
+ /* If the Fl_Text_Selection was extended due to a style change, and some of the
+ fonts don't match in spacing, extend redraw area to end of line to
+ redraw characters exposed by possible font size changes */
+ if ( mFixedFontWidth == -1 && extended )
+ * end = mBuffer->line_end( *end ) + 1;
+}
+
+// The draw() method. It tries to minimize what is draw as much as possible.
+void Fl_Text_Display::draw(void) {
+ // don't even try if there is no associated text buffer!
+ if (!buffer()) { draw_box(); return; }
+
+ // draw the non-text, non-scrollbar areas.
+ if (damage() & FL_DAMAGE_ALL) {
+ //printf("drawing all\n");
+ // draw the box()
+ draw_text_frame();
+
+ // left margin
+ fl_rectf(text_area.x-LEFT_MARGIN, text_area.y-TOP_MARGIN,
+ LEFT_MARGIN, text_area.h+TOP_MARGIN+BOTTOM_MARGIN,
+ text_background());
+
+ // right margin
+ fl_rectf(text_area.x+text_area.w, text_area.y-TOP_MARGIN,
+ RIGHT_MARGIN, text_area.h+TOP_MARGIN+BOTTOM_MARGIN,
+ text_background());
+
+ // top margin
+ fl_rectf(text_area.x, text_area.y-TOP_MARGIN,
+ text_area.w, TOP_MARGIN, text_background());
+
+ // bottom margin
+ fl_rectf(text_area.x, text_area.y+text_area.h,
+ text_area.w, BOTTOM_MARGIN, text_background());
+
+ // draw that little box in the corner of the scrollbars
+ if (mVScrollBar->visible() && mHScrollBar->visible())
+ fl_rectf(mVScrollBar->x(), mHScrollBar->y(),
+ mVScrollBar->w(), mHScrollBar->h(),
+ color());
+
+ // blank the previous cursor protrusions
+ }
+ else if (damage() & (FL_DAMAGE_SCROLL | FL_DAMAGE_EXPOSE)) {
+ //printf("blanking previous cursor extrusions at Y: %d\n", mCursorOldY);
+ // CET - FIXME - save old cursor position instead and just draw side needed?
+ fl_push_clip(text_area.x-LEFT_MARGIN,
+ text_area.y,
+ text_area.w+LEFT_MARGIN+RIGHT_MARGIN,
+ text_area.h);
+ fl_rectf(text_area.x-LEFT_MARGIN, mCursorOldY,
+ LEFT_MARGIN, mMaxsize, text_background());
+ fl_rectf(text_area.x+text_area.w, mCursorOldY,
+ RIGHT_MARGIN, mMaxsize, text_background());
+ fl_pop_clip();
+ }
+
+ // draw the scrollbars
+ if (damage() & (FL_DAMAGE_ALL | FL_DAMAGE_CHILD)) {
+ mVScrollBar->damage(FL_DAMAGE_ALL);
+ mHScrollBar->damage(FL_DAMAGE_ALL);
+ }
+ update_child(*mVScrollBar);
+ update_child(*mHScrollBar);
+
+ // draw all of the text
+ if (damage() & (FL_DAMAGE_ALL | FL_DAMAGE_EXPOSE)) {
+ //printf("drawing all text\n");
+ int X, Y, W, H;
+ fl_clip_box(text_area.x, text_area.y,
+ text_area.w, text_area.h,
+ X, Y, W, H);
+ draw_text(X, Y, W, H); // this sets the clipping internally
+
+ // draw some lines of text
+ }
+ else if (damage() & FL_DAMAGE_SCROLL) {
+ fl_push_clip(text_area.x, text_area.y,
+ text_area.w, text_area.h);
+ //printf("drawing text from %d to %d\n", damage_range1_start, damage_range1_end);
+ draw_range(damage_range1_start, damage_range1_end);
+ if (damage_range2_end != -1) {
+ //printf("drawing text from %d to %d\n", damage_range2_start, damage_range2_end);
+ draw_range(damage_range2_start, damage_range2_end);
+ }
+ damage_range1_start = damage_range1_end = -1;
+ damage_range2_start = damage_range2_end = -1;
+ fl_pop_clip();
+ }
+
+ // draw the text cursor
+ if (damage() & (FL_DAMAGE_ALL | FL_DAMAGE_SCROLL | FL_DAMAGE_EXPOSE)
+ && !buffer()->primary_selection()->selected() &&
+ mCursorOn && Fl::focus() == this ) {
+ fl_push_clip(text_area.x-LEFT_MARGIN,
+ text_area.y,
+ text_area.w+LEFT_MARGIN+RIGHT_MARGIN,
+ text_area.h);
+
+ int X, Y;
+ if (position_to_xy(mCursorPos, &X, &Y)) draw_cursor(X, Y);
+ //printf("drew cursor at pos: %d (%d,%d)\n", mCursorPos, X, Y);
+ mCursorOldY = Y;
+ fl_pop_clip();
+ }
+}
+
+// this processes drag events due to mouse for Fl_Text_Display and
+// also drags due to cursor movement with shift held down for
+// Fl_Text_Editor
+void fl_text_drag_me(int pos, Fl_Text_Display* d) {
+ if (d->dragType == Fl_Text_Display::DRAG_CHAR) {
+ if (pos >= d->dragPos) {
+ d->buffer()->select(d->dragPos, pos);
+ } else {
+ d->buffer()->select(pos, d->dragPos);
+ }
+ d->insert_position(pos);
+ } else if (d->dragType == Fl_Text_Display::DRAG_WORD) {
+ if (pos >= d->dragPos) {
+ d->insert_position(d->word_end(pos));
+ d->buffer()->select(d->word_start(d->dragPos), d->word_end(pos));
+ } else {
+ d->insert_position(d->word_start(pos));
+ d->buffer()->select(d->word_start(pos), d->word_end(d->dragPos));
+ }
+ } else if (d->dragType == Fl_Text_Display::DRAG_LINE) {
+ if (pos >= d->dragPos) {
+ d->insert_position(d->buffer()->line_end(pos)+1);
+ d->buffer()->select(d->buffer()->line_start(d->dragPos),
+ d->buffer()->line_end(pos)+1);
+ } else {
+ d->insert_position(d->buffer()->line_start(pos));
+ d->buffer()->select(d->buffer()->line_start(pos),
+ d->buffer()->line_end(d->dragPos)+1);
+ }
+ }
+}
+
+int Fl_Text_Display::handle(int event) {
+ if (!buffer()) return 0;
+ // This isn't very elegant!
+ if (!Fl::event_inside(text_area.x, text_area.y, text_area.w, text_area.h)
+ && !dragging) {
+ return Fl_Group::handle(event);
+ }
+
+ switch (event) {
+ case FL_PUSH: {
+ if (Fl::event_state()&FL_SHIFT) return handle(FL_DRAG);
+ dragging = 1;
+ int pos = xy_to_position(Fl::event_x(), Fl::event_y(), CURSOR_POS);
+ dragType = Fl::event_clicks();
+ dragPos = pos;
+ if (dragType == DRAG_CHAR)
+ buffer()->unselect();
+ else if (dragType == DRAG_WORD)
+ buffer()->select(word_start(pos), word_end(pos));
+ else if (dragType == DRAG_LINE)
+ buffer()->select(buffer()->line_start(pos), buffer()->line_end(pos)+1);
+
+ if (buffer()->primary_selection()->selected())
+ insert_position(buffer()->primary_selection()->end());
+ else
+ insert_position(pos);
+ show_insert_position();
+ return 1;
+ }
+
+ case FL_DRAG: {
+ if (dragType < 0) return 1;
+ int X = Fl::event_x(), Y = Fl::event_y(), pos;
+ if (Y < text_area.y) {
+ move_up();
+ pos = insert_position();
+ } else if (Y >= text_area.y+text_area.h) {
+ move_down();
+ pos = insert_position();
+ } else pos = xy_to_position(X, Y, CURSOR_POS);
+ fl_text_drag_me(pos, this);
+ return 1;
+ }
+
+ case FL_RELEASE: {
+ dragging = 0;
+
+ // convert from WORD or LINE selection to CHAR
+ if (insert_position() >= dragPos)
+ dragPos = buffer()->primary_selection()->start();
+ else
+ dragPos = buffer()->primary_selection()->end();
+ dragType = DRAG_CHAR;
+
+ const char* copy = buffer()->selection_text();
+ if (*copy) Fl::copy(copy, strlen(copy), false);
+ free((void*)copy);
+ return 1;
+ }
+
+ case FL_MOUSEWHEEL:
+ return send(event, *mVScrollBar);
+#if 0
+ // I shouldn't be using mNVisibleLines or mTopLineNum here in handle()
+ // because the values for these might change between now and layout(),
+ // but it's OK because I really want the result based on how things
+ // were last displayed rather than where they should be displayed next
+ // time layout()/draw() happens.
+ int lines, sign = (Fl::event_dy() < 0) ? -1 : 1;
+ if (abs(Fl::event_dy()) > mNVisibleLines-2) lines = mNVisibleLines-2;
+ else lines = abs(Fl::event_dy());
+ scroll(mTopLineNum - lines*sign, mHorizOffset);
+ return 1;
+#endif
+ }
+
+ return 0;
+}
+
+
+//
+// End of "$Id: Fl_Text_Display.cxx,v 1.12 2001/07/23 09:50:05 spitzak Exp $".
+//
diff --git a/src/Fl_Text_Editor.cxx b/src/Fl_Text_Editor.cxx
new file mode 100644
index 000000000..b1dc5e900
--- /dev/null
+++ b/src/Fl_Text_Editor.cxx
@@ -0,0 +1,446 @@
+//
+// "$Id: Fl_Text_Editor.cxx,v 1.9 2001/07/23 09:50:05 spitzak Exp $"
+//
+// Copyright Mark Edel. Permission to distribute under the LGPL for
+// the FLTK library granted by Mark Edel.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@fltk.org".
+//
+
+
+#include <fltk/Fl.h>
+#include <fltk/Fl_Text_Editor.h>
+#include <fltk/Fl_Style.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+static void revert(Fl_Style*) {}
+static Fl_Named_Style style("Text_Editor", revert, &Fl_Text_Editor::default_style);
+Fl_Named_Style* Fl_Text_Editor::default_style = &::style;
+
+Fl_Text_Editor::Fl_Text_Editor(int X, int Y, int W, int H, const char* l)
+ : Fl_Text_Display(X, Y, W, H, l) {
+ style(default_style);
+ mCursorOn = 1;
+ insert_mode_ = 1;
+ key_bindings = 0;
+
+ // handle the default key bindings
+ add_default_key_bindings(&key_bindings);
+
+ // handle everything else
+ default_key_function(kf_default);
+}
+
+Fl_Text_Editor::Key_Binding* Fl_Text_Editor::global_key_bindings = 0;
+
+static int ctrl_a(int, Fl_Text_Editor* e);
+
+// These are the default key bindings every widget should start with
+static struct {
+ int key;
+ int state;
+ Fl_Text_Editor::Key_Func func;
+} default_key_bindings[] = {
+ { FL_Escape, FL_TEXT_EDITOR_ANY_STATE, Fl_Text_Editor::kf_ignore },
+ { FL_Enter, FL_TEXT_EDITOR_ANY_STATE, Fl_Text_Editor::kf_enter },
+ { FL_KP_Enter, FL_TEXT_EDITOR_ANY_STATE, Fl_Text_Editor::kf_enter },
+ { FL_BackSpace, FL_TEXT_EDITOR_ANY_STATE, Fl_Text_Editor::kf_backspace },
+ { FL_Insert, FL_TEXT_EDITOR_ANY_STATE, Fl_Text_Editor::kf_insert },
+ { FL_Delete, FL_TEXT_EDITOR_ANY_STATE, Fl_Text_Editor::kf_delete },
+ { FL_Home, 0, Fl_Text_Editor::kf_move },
+ { FL_End, 0, Fl_Text_Editor::kf_move },
+ { FL_Left, 0, Fl_Text_Editor::kf_move },
+ { FL_Up, 0, Fl_Text_Editor::kf_move },
+ { FL_Right, 0, Fl_Text_Editor::kf_move },
+ { FL_Down, 0, Fl_Text_Editor::kf_move },
+ { FL_Page_Up, 0, Fl_Text_Editor::kf_move },
+ { FL_Page_Down, 0, Fl_Text_Editor::kf_move },
+ { FL_Home, FL_SHIFT, Fl_Text_Editor::kf_shift_move },
+ { FL_End, FL_SHIFT, Fl_Text_Editor::kf_shift_move },
+ { FL_Left, FL_SHIFT, Fl_Text_Editor::kf_shift_move },
+ { FL_Up, FL_SHIFT, Fl_Text_Editor::kf_shift_move },
+ { FL_Right, FL_SHIFT, Fl_Text_Editor::kf_shift_move },
+ { FL_Down, FL_SHIFT, Fl_Text_Editor::kf_shift_move },
+ { FL_Page_Up, FL_SHIFT, Fl_Text_Editor::kf_shift_move },
+ { FL_Page_Down, FL_SHIFT, Fl_Text_Editor::kf_shift_move },
+ { FL_Home, FL_CTRL, Fl_Text_Editor::kf_ctrl_move },
+ { FL_End, FL_CTRL, Fl_Text_Editor::kf_ctrl_move },
+ { FL_Left, FL_CTRL, Fl_Text_Editor::kf_ctrl_move },
+ { FL_Up, FL_CTRL, Fl_Text_Editor::kf_ctrl_move },
+ { FL_Right, FL_CTRL, Fl_Text_Editor::kf_ctrl_move },
+ { FL_Down, FL_CTRL, Fl_Text_Editor::kf_ctrl_move },
+ { FL_Page_Up, FL_CTRL, Fl_Text_Editor::kf_ctrl_move },
+ { FL_Page_Down, FL_CTRL, Fl_Text_Editor::kf_ctrl_move },
+ { FL_Home, FL_CTRL|FL_SHIFT, Fl_Text_Editor::kf_c_s_move },
+ { FL_End, FL_CTRL|FL_SHIFT, Fl_Text_Editor::kf_c_s_move },
+ { FL_Left, FL_CTRL|FL_SHIFT, Fl_Text_Editor::kf_c_s_move },
+ { FL_Up, FL_CTRL|FL_SHIFT, Fl_Text_Editor::kf_c_s_move },
+ { FL_Right, FL_CTRL|FL_SHIFT, Fl_Text_Editor::kf_c_s_move },
+ { FL_Down, FL_CTRL|FL_SHIFT, Fl_Text_Editor::kf_c_s_move },
+ { FL_Page_Up, FL_CTRL|FL_SHIFT, Fl_Text_Editor::kf_c_s_move },
+ { FL_Page_Down, FL_CTRL|FL_SHIFT, Fl_Text_Editor::kf_c_s_move },
+//{ FL_Clear, 0, Fl_Text_Editor::delete_to_eol },
+//{ 'z', FL_CTRL, Fl_Text_Editor::undo },
+//{ '/', FL_CTRL, Fl_Text_Editor::undo },
+ { 'x', FL_CTRL, Fl_Text_Editor::kf_cut },
+ { 'c', FL_CTRL, Fl_Text_Editor::kf_copy },
+ { 'v', FL_CTRL, Fl_Text_Editor::kf_paste },
+ { 'a', FL_CTRL, ctrl_a },
+ { 0, 0, 0 }
+};
+
+void Fl_Text_Editor::add_default_key_bindings(Key_Binding** list) {
+ for (int i = 0; default_key_bindings[i].key; i++) {
+ add_key_binding(default_key_bindings[i].key,
+ default_key_bindings[i].state,
+ default_key_bindings[i].func,
+ list);
+ }
+}
+
+Fl_Text_Editor::Key_Func
+Fl_Text_Editor::bound_key_function(int key, int state, Key_Binding* list) {
+ Key_Binding* current;
+ for (current = list; current; current = current->next)
+ if (current->key == key)
+ if (current->state == FL_TEXT_EDITOR_ANY_STATE || current->state == state)
+ break;
+ if (!current) return 0;
+ return current->function;
+}
+
+void
+Fl_Text_Editor::remove_all_key_bindings(Key_Binding** list) {
+ Key_Binding *current, *next;
+ for (current = *list; current; current = next) {
+ next = current->next;
+ delete current;
+ }
+ *list = 0;
+}
+
+void
+Fl_Text_Editor::remove_key_binding(int key, int state, Key_Binding** list) {
+ Key_Binding *current, *last = 0;
+ for (current = *list; current; last = current, current = current->next)
+ if (current->key == key && current->state == state) break;
+ if (!current) return;
+ if (last) last->next = current->next;
+ else *list = current->next;
+ delete current;
+}
+
+void
+Fl_Text_Editor::add_key_binding(int key, int state, Key_Func function,
+ Key_Binding** list) {
+ Key_Binding* kb = new Key_Binding;
+ kb->key = key;
+ kb->state = state;
+ kb->function = function;
+ kb->next = *list;
+ *list = kb;
+}
+
+////////////////////////////////////////////////////////////////
+
+#define NORMAL_INPUT_MOVE 0
+
+static void kill_selection(Fl_Text_Editor* e) {
+ if (e->buffer()->selected()) {
+ e->insert_position(e->buffer()->primary_selection()->start());
+ e->buffer()->remove_selection();
+ }
+}
+
+int Fl_Text_Editor::kf_default(int c, Fl_Text_Editor* e) {
+ if (!c || (!isprint(c) && c != '\t')) return 0;
+ char s[2] = "\0";
+ s[0] = (char)c;
+ kill_selection(e);
+ if (e->insert_mode()) e->insert(s);
+ else e->overstrike(s);
+ e->show_insert_position();
+ return 1;
+}
+
+int Fl_Text_Editor::kf_ignore(int, Fl_Text_Editor*) {
+ return 0; // don't handle
+}
+
+int Fl_Text_Editor::kf_backspace(int, Fl_Text_Editor* e) {
+ if (!e->buffer()->selected() && e->move_left())
+ e->buffer()->select(e->insert_position(), e->insert_position()+1);
+ kill_selection(e);
+ e->show_insert_position();
+ return 1;
+}
+
+int Fl_Text_Editor::kf_enter(int, Fl_Text_Editor* e) {
+ kill_selection(e);
+ e->insert("\n");
+ e->show_insert_position();
+ return 1;
+}
+
+extern void fl_text_drag_me(int pos, Fl_Text_Display* d);
+
+int Fl_Text_Editor::kf_move(int c, Fl_Text_Editor* e) {
+ int i;
+ int selected = e->buffer()->selected();
+ if (!selected)
+ e->dragPos = e->insert_position();
+ e->buffer()->unselect();
+ switch (c) {
+ case FL_Home:
+ e->insert_position(e->buffer()->line_start(e->insert_position()));
+ break;
+ case FL_End:
+ e->insert_position(e->buffer()->line_end(e->insert_position()));
+ break;
+ case FL_Left:
+ e->move_left();
+ break;
+ case FL_Right:
+ e->move_right();
+ break;
+ case FL_Up:
+ e->move_up();
+ break;
+ case FL_Down:
+ e->move_down();
+ break;
+ case FL_Page_Up:
+ for (i = 0; i < e->mNVisibleLines - 1; i++) e->move_up();
+ break;
+ case FL_Page_Down:
+ for (i = 0; i < e->mNVisibleLines - 1; i++) e->move_down();
+ break;
+ }
+ e->show_insert_position();
+ return 1;
+}
+
+int Fl_Text_Editor::kf_shift_move(int c, Fl_Text_Editor* e) {
+ kf_move(c, e);
+ fl_text_drag_me(e->insert_position(), e);
+ return 1;
+}
+
+int Fl_Text_Editor::kf_ctrl_move(int c, Fl_Text_Editor* e) {
+ if (!e->buffer()->selected())
+ e->dragPos = e->insert_position();
+ if (c != FL_Up && c != FL_Down) {
+ e->buffer()->unselect();
+ e->show_insert_position();
+ }
+ switch (c) {
+ case FL_Home:
+ e->insert_position(0);
+ break;
+ case FL_End:
+ e->insert_position(e->buffer()->length());
+ break;
+ case FL_Left:
+ e->previous_word();
+ break;
+ case FL_Right:
+ e->next_word();
+ break;
+ case FL_Up:
+ e->scroll(e->mTopLineNum-1, e->mHorizOffset);
+ break;
+ case FL_Down:
+ e->scroll(e->mTopLineNum+1, e->mHorizOffset);
+ break;
+ case FL_Page_Up:
+ e->insert_position(e->mLineStarts[0]);
+ break;
+ case FL_Page_Down:
+ e->insert_position(e->mLineStarts[e->mNVisibleLines-2]);
+ break;
+ }
+ return 1;
+}
+
+int Fl_Text_Editor::kf_c_s_move(int c, Fl_Text_Editor* e) {
+ kf_ctrl_move(c, e);
+ fl_text_drag_me(e->insert_position(), e);
+ return 1;
+}
+
+static int ctrl_a(int, Fl_Text_Editor* e) {
+ // make 2+ ^A's in a row toggle select-all:
+ int i = e->buffer()->line_start(e->insert_position());
+ if (i != e->insert_position())
+ return Fl_Text_Editor::kf_move(FL_Home, e);
+ else {
+ if (e->buffer()->selected())
+ e->buffer()->unselect();
+ else
+ Fl_Text_Editor::kf_select_all(0, e);
+ }
+ return 1;
+}
+
+int Fl_Text_Editor::kf_home(int, Fl_Text_Editor* e) {
+ return kf_move(FL_Home, e);
+}
+
+int Fl_Text_Editor::kf_end(int, Fl_Text_Editor* e) {
+ return kf_move(FL_End, e);
+}
+
+int Fl_Text_Editor::kf_left(int, Fl_Text_Editor* e) {
+ return kf_move(FL_Left, e);
+}
+
+int Fl_Text_Editor::kf_up(int, Fl_Text_Editor* e) {
+ return kf_move(FL_Up, e);
+}
+
+int Fl_Text_Editor::kf_right(int, Fl_Text_Editor* e) {
+ return kf_move(FL_Right, e);
+}
+
+int Fl_Text_Editor::kf_down(int, Fl_Text_Editor* e) {
+ return kf_move(FL_Down, e);
+}
+
+int Fl_Text_Editor::kf_page_up(int, Fl_Text_Editor* e) {
+ return kf_move(FL_Page_Up, e);
+}
+
+int Fl_Text_Editor::kf_page_down(int, Fl_Text_Editor* e) {
+ return kf_move(FL_Page_Down, e);
+}
+
+
+int Fl_Text_Editor::kf_insert(int, Fl_Text_Editor* e) {
+ e->insert_mode(e->insert_mode() ? 0 : 1);
+ return 1;
+}
+
+int Fl_Text_Editor::kf_delete(int, Fl_Text_Editor* e) {
+ if (!e->buffer()->selected())
+ e->buffer()->select(e->insert_position(), e->insert_position()+1);
+ kill_selection(e);
+ e->show_insert_position();
+ return 1;
+}
+
+int Fl_Text_Editor::kf_copy(int, Fl_Text_Editor* e) {
+ if (!e->buffer()->selected()) return 1;
+ const char *copy = e->buffer()->selection_text();
+ if (*copy) Fl::copy(copy, strlen(copy), true);
+ free((void*)copy);
+ e->show_insert_position();
+ return 1;
+}
+
+int Fl_Text_Editor::kf_cut(int c, Fl_Text_Editor* e) {
+ kf_copy(c, e);
+ kill_selection(e);
+ return 1;
+}
+
+int Fl_Text_Editor::kf_paste(int, Fl_Text_Editor* e) {
+ kill_selection(e);
+ Fl::paste(*e,true);
+ e->show_insert_position();
+ return 1;
+}
+
+int Fl_Text_Editor::kf_select_all(int, Fl_Text_Editor* e) {
+ e->buffer()->select(0, e->buffer()->length());
+ return 1;
+}
+
+int Fl_Text_Editor::handle_key() {
+
+ // Call fltk's rules to try to turn this into a printing character.
+ // This uses the right-hand ctrl key as a "compose prefix" and returns
+ // the changes that should be made to the text, as a number of
+ // bytes to delete and a string to insert:
+ int del;
+ if (Fl::compose(del)) {
+ if (del) buffer()->select(insert_position()-del, insert_position());
+ kill_selection(this);
+ if (Fl::event_length()) {
+ if (insert_mode()) insert(Fl::event_text());
+ else overstrike(Fl::event_text());
+ }
+ show_insert_position();
+ return 1;
+ }
+
+ int key = Fl::event_key(), state = Fl::event_state(), c = Fl::event_text()[0];
+ state &= FL_SHIFT|FL_CTRL|FL_ALT|FL_META; // only care about these states
+ Key_Func f;
+ f = bound_key_function(key, state, global_key_bindings);
+ if (!f) f = bound_key_function(key, state, key_bindings);
+
+ if (f) return f(key, this);
+ if (default_key_function_ && !state) return default_key_function_(c, this);
+ return 0;
+}
+
+int Fl_Text_Editor::handle(int event) {
+ if (!buffer()) return 0;
+
+ if (event == FL_PUSH && Fl::event_button() == 2) {
+ dragType = -1;
+ Fl::paste(*this,false);
+ Fl::focus(this);
+ return 1;
+ }
+
+ switch (event) {
+ case FL_FOCUS:
+ show_cursor(mCursorOn); // redraws the cursor
+ return 1;
+
+ case FL_UNFOCUS:
+ show_cursor(mCursorOn); // redraws the cursor
+ return 1;
+
+ case FL_KEYBOARD:
+ return handle_key();
+
+ case FL_PASTE:
+ buffer()->remove_selection();
+ if (insert_mode()) insert(Fl::event_text());
+ else overstrike(Fl::event_text());
+ show_insert_position();
+ return 1;
+
+// CET - FIXME - this will clobber the window's current cursor state!
+// case FL_ENTER:
+// case FL_MOVE:
+// case FL_LEAVE:
+// if (Fl::event_inside(text_area)) fl_cursor(FL_CURSOR_INSERT);
+// else fl_cursor(FL_CURSOR_DEFAULT);
+ }
+
+ return Fl_Text_Display::handle(event);
+}
+
+//
+// End of "$Id: Fl_Text_Editor.cxx,v 1.9 2001/07/23 09:50:05 spitzak Exp $".
+//
diff --git a/src/Fl_Tooltip.cxx b/src/Fl_Tooltip.cxx
new file mode 100644
index 000000000..e963424ad
--- /dev/null
+++ b/src/Fl_Tooltip.cxx
@@ -0,0 +1,157 @@
+//
+// "$Id: Fl_Tooltip.cxx,v 1.38 2001/07/23 09:50:05 spitzak Exp $"
+//
+// Tooltip code for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-1999 by Bill Spitzak and others.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@easysw.com".
+//
+
+#include <fltk/Fl_Tooltip.h>
+#include <fltk/fl_draw.h>
+#include <fltk/Fl_Menu_Window.h>
+
+float Fl_Tooltip::delay_ = 0.5f;
+int Fl_Tooltip::enabled_ = 1;
+
+#define MAX_WIDTH 400
+
+class Fl_TooltipBox : public Fl_Menu_Window {
+public:
+ Fl_TooltipBox() : Fl_Menu_Window(0, 0, 0, 0) {
+ style(Fl_Tooltip::default_style);}
+ void draw();
+ void layout();
+};
+
+static const char* tip;
+static Fl_Widget* widget;
+static Fl_TooltipBox *window = 0;
+static int X,Y,W,H;
+
+void Fl_TooltipBox::layout() {
+ fl_font(label_font(), label_size());
+ int ww, hh;
+ ww = MAX_WIDTH;
+ fl_measure(tip, ww, hh, FL_ALIGN_LEFT|FL_ALIGN_WRAP|FL_ALIGN_INSIDE);
+ ww += 6; hh += 6;
+
+ // find position on the screen of the widget:
+ int ox = Fl::event_x_root()+5;
+ //int ox = X+W/2;
+ int oy = Y + H+2;
+ for (Fl_Widget* p = widget; p; p = p->parent()) {
+ //ox += p->x();
+ oy += p->y();
+ }
+ if (ox+ww > Fl::w()) ox = Fl::w() - ww;
+ if (ox < 0) ox = 0;
+ if (H > 30) {
+ oy = Fl::event_y_root()+13;
+ if (oy+hh > Fl::h()) oy -= 23+hh;
+ } else {
+ if (oy+hh > Fl::h()) oy -= (4+hh+H);
+ }
+ if (oy < 0) oy = 0;
+
+ resize(ox, oy, ww, hh);
+ Fl_Menu_Window::layout();
+}
+
+void Fl_TooltipBox::draw() {
+ draw_box();
+ draw_label(3, 3, w()-6, h()-6, FL_ALIGN_LEFT|FL_ALIGN_WRAP|FL_ALIGN_INSIDE);
+}
+
+
+static void tooltip_timeout(void*) {
+ if (Fl::grab()) return;
+
+ if (window) delete window;
+ Fl_Group* saveCurrent = Fl_Group::current();
+ Fl_Group::current(0);
+ window = new Fl_TooltipBox;
+ window->set_override();
+ window->end();
+ Fl_Group::current(saveCurrent);
+
+ // this cast bypasses the normal Fl_Window label() code:
+ ((Fl_Widget*)window)->label(tip);
+ window->relayout();
+ window->show();
+}
+
+static int cheesy_flag = 0;
+
+static void
+tt_exit(Fl_Widget *w) {
+ if (!w || w != widget) return;
+ widget = 0;
+ Fl::remove_timeout((Fl_Timeout_Handler)tooltip_timeout);
+ if (window) {
+ // This flag makes sure that tootip_enter() isn't executed because of
+ // this destroy() which could cause unwanted recursion in tooltip_enter()
+ cheesy_flag = 1;
+ window->destroy();
+ cheesy_flag = 0;
+ }
+}
+
+static void
+tt_enter_area(Fl_Widget* w, int X, int Y, int W, int H, const char* t) {
+ if (cheesy_flag) return;
+ if (w == widget && X == ::X && Y == ::Y && W == ::W && H == ::H && t == tip)
+ return;
+ tt_exit(widget);
+ widget = w; ::X = X; ::Y = Y; ::W = W; ::H = H; tip = t;
+ if (!t || !Fl_Tooltip::enabled()) return;
+ float d = Fl_Tooltip::delay();
+ if (d < .01f) d = .01f;
+ Fl::add_timeout(d, (Fl_Timeout_Handler)tooltip_timeout);
+}
+
+static void
+tt_enter(Fl_Widget* w) {
+ if (cheesy_flag || w == widget) return;
+ if (!w || w == window) { tt_exit(widget); widget = 0; return; }
+ tt_enter_area(w, 0, 0, w->w(), w->h(), w->tooltip());
+}
+
+void Fl_Widget::tooltip(const char *tt) {
+ static int do_once = 0;
+ if (!do_once) {
+ do_once = 1;
+ Fl_Tooltip::enter = tt_enter;
+ Fl_Tooltip::enter_area = tt_enter_area;
+ Fl_Tooltip::exit = tt_exit;
+ }
+ tooltip_ = tt;
+}
+
+static void revert(Fl_Style* s) {
+ s->box = FL_BORDER_BOX;
+ s->color = (Fl_Color)215;
+ s->label_color = FL_BLACK;
+}
+static Fl_Named_Style style("Tooltip", revert, &Fl_Tooltip::default_style);
+Fl_Named_Style* Fl_Tooltip::default_style = &::style;
+
+//
+// End of "$Id: Fl_Tooltip.cxx,v 1.38 2001/07/23 09:50:05 spitzak Exp $".
+//
diff --git a/src/Fl_lock.cxx b/src/Fl_lock.cxx
new file mode 100644
index 000000000..ebd8b420f
--- /dev/null
+++ b/src/Fl_lock.cxx
@@ -0,0 +1,145 @@
+/* Fl_Lock.cxx
+
+ I would prefer that fltk contain the minimal amount of extra stuff
+ for doing threads. There are other portable thread wrapper libraries
+ out there and fltk should not be providing another. This file
+ is an attempt to make minimal additions and make them self-contained
+ in this source file.
+
+ Fl::lock() - recursive lock. Plus you must call this before the
+ first call to Fl::wait()/run() to initialize the thread system.
+ The lock is locked all the time except when Fl::wait() is waiting
+ for events.
+
+ Fl::unlock() - release the recursive lock.
+
+ Fl::awake(void*) - Causes Fl::wait() to return (with the lock locked)
+ even if there are no events ready.
+
+ Fl::thread_message() - returns an argument sent to an Fl::awake call,
+ or returns null if none. Warning: the current implementation only
+ has a one-entry queue and only returns the most recent value!
+
+ See also the Fl_Threads.h header file, which provides convienence
+ functions so you can create your own threads and mutexes.
+*/
+
+#include <fltk/Fl.h>
+#include <config.h>
+
+////////////////////////////////////////////////////////////////
+#if defined(_WIN32)
+
+#include <windows.h>
+#include <process.h>
+
+// these pointers are in Fl_win32.cxx:
+extern void (*fl_lock_function)();
+extern void (*fl_unlock_function)();
+
+static DWORD main_thread;
+
+CRITICAL_SECTION cs;
+
+static void unlock_function() {
+ LeaveCriticalSection(&cs);
+}
+
+static void lock_function() {
+ EnterCriticalSection(&cs);
+}
+
+void Fl::lock() {
+ if (!main_thread)
+ InitializeCriticalSection(&cs);
+ lock_function();
+ if (!main_thread) {
+ fl_lock_function = lock_function;
+ fl_unlock_function = unlock_function;
+ main_thread = GetCurrentThreadId();
+ }
+}
+
+void Fl::unlock() {
+ unlock_function();
+}
+
+// when called from a thread, it causes FLTK to awake from Fl::wait()
+void Fl::awake(void* msg) {
+ PostThreadMessage( main_thread, WM_USER, (WPARAM)msg, 0);
+}
+
+////////////////////////////////////////////////////////////////
+#elif HAVE_PTHREAD
+#include <unistd.h>
+#include <pthread.h>
+
+#ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+// Linux supports recursive locks, use them directly:
+
+static pthread_mutex_t fltk_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
+
+static void lock_function() {
+ pthread_mutex_lock(&fltk_mutex);
+}
+
+void Fl::unlock() {
+ pthread_mutex_unlock(&fltk_mutex);
+}
+
+// this is needed for the Fl_Mutex constructor:
+pthread_mutexattr_t Fl_Mutex_attrib = {PTHREAD_MUTEX_RECURSIVE_NP};
+
+#else
+// Make a recursive lock out of the pthread mutex:
+
+static pthread_mutex_t fltk_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_t owner;
+static int counter;
+
+static void lock_function() {
+ if (!counter || owner != pthread_self()) {
+ pthread_mutex_lock(&fltk_mutex); owner = pthread_self();
+ }
+ counter++;
+}
+
+void Fl::unlock() {
+ if (!--counter) pthread_mutex_unlock(&fltk_mutex);
+}
+
+#endif
+
+static int thread_filedes[2];
+
+// these pointers are in Fl_x.cxx:
+extern void (*fl_lock_function)();
+extern void (*fl_unlock_function)();
+
+static void* thread_message_;
+void* Fl::thread_message() {
+ void* r = thread_message_;
+ thread_message_ = 0;
+ return r;
+}
+
+static void thread_awake_cb(int fd, void*) {
+ read(fd, &thread_message_, sizeof(void*));
+}
+
+void Fl::lock() {
+ lock_function();
+ if (!thread_filedes[1]) { // initialize the mt support
+ // Init threads communication pipe to let threads awake FLTK from wait
+ pipe(thread_filedes);
+ Fl::add_fd(thread_filedes[0], FL_READ, thread_awake_cb);
+ fl_lock_function = lock_function;
+ fl_unlock_function = Fl::unlock;
+ }
+}
+
+void Fl::awake(void* msg) {
+ write(thread_filedes[1], &msg, sizeof(void*));
+}
+
+#endif
diff --git a/src/allfiles.xbm b/src/allfiles.xbm
new file mode 100644
index 000000000..26373b60b
--- /dev/null
+++ b/src/allfiles.xbm
@@ -0,0 +1,6 @@
+#define allfiles_width 16
+#define allfiles_height 16
+static unsigned char allfiles_bits[] = {
+ 0xfc, 0x3f, 0x04, 0x20, 0x04, 0x20, 0x04, 0x20, 0x84, 0x21, 0xa4, 0x25,
+ 0xc4, 0x23, 0xf4, 0x2f, 0xf4, 0x2f, 0xc4, 0x23, 0xa4, 0x25, 0x84, 0x21,
+ 0x04, 0x20, 0x04, 0x20, 0x04, 0x20, 0xfc, 0x3f};
diff --git a/src/fl_dnd.cxx b/src/fl_dnd.cxx
new file mode 100644
index 000000000..9390a272a
--- /dev/null
+++ b/src/fl_dnd.cxx
@@ -0,0 +1,34 @@
+//
+// "$Id: fl_dnd.cxx,v 1.3 2001/07/29 22:04:44 spitzak Exp $"
+//
+// Drag & Drop code for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-1999 by Bill Spitzak and others.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@easysw.com".
+//
+
+#ifdef _WIN32
+#include "fl_dnd_win32.cxx"
+#else
+#include "fl_dnd_x.cxx"
+#endif
+
+//
+// End of "$Id: fl_dnd.cxx,v 1.3 2001/07/29 22:04:44 spitzak Exp $".
+//
diff --git a/src/fl_dnd_x.cxx b/src/fl_dnd_x.cxx
new file mode 100644
index 000000000..5c1a3a832
--- /dev/null
+++ b/src/fl_dnd_x.cxx
@@ -0,0 +1,170 @@
+//
+// "$Id: fl_dnd_x.cxx,v 1.5 2001/07/23 09:50:05 spitzak Exp $"
+//
+// Drag & Drop code for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-1999 by Bill Spitzak and others.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@easysw.com".
+//
+
+#include <fltk/Fl.h>
+#include <fltk/Fl_Window.h>
+#include <fltk/x.h>
+
+extern Atom fl_XdndAware;
+extern Atom fl_XdndSelection;
+extern Atom fl_XdndEnter;
+extern Atom fl_XdndTypeList;
+extern Atom fl_XdndPosition;
+extern Atom fl_XdndLeave;
+extern Atom fl_XdndDrop;
+extern Atom fl_XdndStatus;
+extern Atom fl_XdndActionCopy;
+extern Atom fl_XdndFinished;
+//extern Atom fl_XdndProxy;
+
+extern char fl_i_own_selection;
+
+void fl_sendClientMessage(Window window, Atom message,
+ unsigned long d0,
+ unsigned long d1=0,
+ unsigned long d2=0,
+ unsigned long d3=0,
+ unsigned long d4=0);
+
+// return version # of Xdnd this window supports. Also change the
+// window the the proxy if it uses a proxy:
+static int dnd_aware(Window& window) {
+ Atom actual; int format; unsigned long count, remaining;
+ unsigned char *data = 0;
+ XGetWindowProperty(fl_display, window, fl_XdndAware,
+ 0, 4, False, XA_ATOM,
+ &actual, &format,
+ &count, &remaining, &data);
+ if (actual == XA_ATOM && format==32 && count && data)
+ return int(*(Atom*)data);
+ return 0;
+}
+
+static bool grabfunc(int event) {
+ if (event == FL_RELEASE) Fl::pushed(0);
+ return false;
+}
+
+extern bool (*fl_local_grab)(int); // in Fl.cxx
+
+// send an event to an fltk window belonging to this program:
+static bool local_handle(int event, Fl_Window* window) {
+ fl_local_grab = 0;
+ Fl::e_x = Fl::e_x_root-window->x();
+ Fl::e_y = Fl::e_y_root-window->y();
+ int ret = Fl::handle(event,window);
+ fl_local_grab = grabfunc;
+ return ret;
+}
+
+bool Fl::dnd() {
+ Fl::first_window()->cursor((Fl_Cursor)21);
+ Window source_window = fl_xid(Fl::first_window());
+ fl_local_grab = grabfunc;
+ Window target_window = 0;
+ Fl_Window* local_window = 0;
+ int version = 4; int dest_x, dest_y;
+ XSetSelectionOwner(fl_display, fl_XdndSelection, fl_message_window, fl_event_time);
+
+ while (Fl::pushed()) {
+
+ // figure out what window we are pointing at:
+ Window new_window = 0; int new_version = 0;
+ Fl_Window* new_local_window = 0;
+ for (Window child = RootWindow(fl_display, fl_screen);;) {
+ Window root; unsigned int junk3;
+ XQueryPointer(fl_display, child, &root, &child,
+ &e_x_root, &e_y_root, &dest_x, &dest_y, &junk3);
+ if (!child) {
+ if (!new_window && (new_version = dnd_aware(root))) new_window = root;
+ break;
+ }
+ new_window = child;
+ if ((new_local_window = fl_find(child))) break;
+ if ((new_version = dnd_aware(new_window))) break;
+ }
+
+ if (new_window != target_window) {
+ if (local_window) {
+ local_handle(FL_DND_LEAVE, local_window);
+ } else if (version) {
+ fl_sendClientMessage(target_window, fl_XdndLeave, source_window);
+ }
+ version = new_version;
+ target_window = new_window;
+ local_window = new_local_window;
+ if (local_window) {
+ local_handle(FL_DND_ENTER, local_window);
+ } else if (version) {
+ fl_sendClientMessage(target_window, fl_XdndEnter, source_window,
+ version<<24, XA_STRING, 0, 0);
+ }
+ }
+ if (local_window) {
+ local_handle(FL_DND_DRAG, local_window);
+ } else if (version) {
+ fl_sendClientMessage(target_window, fl_XdndPosition, source_window,
+ 0, (e_x_root<<16)|e_y_root, fl_event_time,
+ fl_XdndActionCopy);
+ }
+ Fl::wait();
+ }
+
+ if (local_window) {
+ fl_i_own_selection = 1;
+ if (local_handle(FL_DND_RELEASE, local_window)) paste(*belowmouse(),false);
+ } else if (version) {
+ fl_sendClientMessage(target_window, fl_XdndDrop, source_window,
+ 0, fl_event_time);
+ } else if (target_window) {
+ // fake a drop by clicking the middle mouse button:
+ XButtonEvent msg;
+ msg.type = ButtonPress;
+ msg.window = target_window;
+ msg.root = RootWindow(fl_display, fl_screen);
+ msg.subwindow = 0;
+ msg.time = fl_event_time+1;
+ msg.x = dest_x;
+ msg.y = dest_y;
+ msg.x_root = Fl::e_x_root;
+ msg.y_root = Fl::e_y_root;
+ msg.state = 0x0;
+ msg.button = Button2;
+ XSendEvent(fl_display, target_window, False, 0L, (XEvent*)&msg);
+ msg.time++;
+ msg.state = 0x200;
+ msg.type = ButtonRelease;
+ XSendEvent(fl_display, target_window, False, 0L, (XEvent*)&msg);
+ }
+
+ fl_local_grab = 0;
+ Fl::first_window()->cursor(FL_CURSOR_DEFAULT);
+ return true;
+}
+
+
+//
+// End of "$Id: fl_dnd_x.cxx,v 1.5 2001/07/23 09:50:05 spitzak Exp $".
+//
diff --git a/src/new.xbm b/src/new.xbm
new file mode 100644
index 000000000..25a56c593
--- /dev/null
+++ b/src/new.xbm
@@ -0,0 +1,6 @@
+#define new_width 16
+#define new_height 16
+static unsigned char new_bits[] = {
+ 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0x02, 0x01, 0x01, 0xfe, 0x01, 0x80,
+ 0x31, 0x80, 0x31, 0x80, 0xfd, 0x80, 0xfd, 0x80, 0x31, 0x80, 0x31, 0x80,
+ 0x01, 0x80, 0x01, 0x80, 0xff, 0xff, 0x00, 0x00};
diff --git a/src/up.xbm b/src/up.xbm
new file mode 100644
index 000000000..1a4f4b7e7
--- /dev/null
+++ b/src/up.xbm
@@ -0,0 +1,6 @@
+#define up_width 16
+#define up_height 16
+static unsigned char up_bits[] = {
+ 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0x02, 0x01, 0x31, 0xfe, 0x79, 0x80,
+ 0xfd, 0x80, 0x31, 0x80, 0x31, 0x80, 0x31, 0x80, 0x31, 0x80, 0x31, 0x80,
+ 0x01, 0x80, 0x01, 0x80, 0xff, 0xff, 0x00, 0x00};
diff --git a/test/tabs.cxx b/test/tabs.cxx
new file mode 100644
index 000000000..4e2c0223a
--- /dev/null
+++ b/test/tabs.cxx
@@ -0,0 +1,85 @@
+// generated by Fast Light User Interface Designer (fluid) version 2.0001
+
+#include "tabs.h"
+
+Fl_Window *foo_window=(Fl_Window *)0;
+
+static void cb_cancel(Fl_Button*, void*) {
+ exit(1);
+
+}
+
+static void cb_OK(Fl_Return_Button*, void*) {
+ exit(0);
+
+}
+
+int main (int argc, char **argv) {
+
+ Fl_Window* w;
+ { Fl_Window* o = foo_window = new Fl_Window(321, 324);
+ w = o;
+ { Fl_Tabs* o = new Fl_Tabs(10, 10, 300, 200);
+ o->color((Fl_Color)47);
+ o->selection_color((Fl_Color)15);
+ { Fl_Group* o = new Fl_Group(0, 20, 300, 180, "Label1");
+ o->hide();
+ new Fl_Input(50, 20, 240, 40, "input:");
+ new Fl_Input(50, 60, 240, 30, "input2:");
+ new Fl_Input(50, 90, 240, 80, "input3:");
+ o->end();
+ Fl_Group::current()->resizable(o);
+ }
+ { Fl_Group* o = new Fl_Group(0, 20, 300, 180, "tab2");
+ o->hide();
+ new Fl_Button(10, 30, 100, 30, "button1");
+ new Fl_Input(130, 70, 100, 30, "input in box2");
+ new Fl_Button(20, 110, 260, 30, "This is stuff inside the Fl_Group \"tab2\"");
+ o->end();
+ }
+ { Fl_Group* o = new Fl_Group(0, 20, 300, 180, "tab3");
+ o->hide();
+ new Fl_Button(10, 30, 60, 80, "button2");
+ new Fl_Button(70, 30, 60, 80, "button");
+ new Fl_Button(130, 30, 60, 80, "button");
+ o->end();
+ }
+ { Fl_Group* o = new Fl_Group(0, 20, 300, 180, "tab4");
+ o->label_font(fl_fonts+2);
+ o->hide();
+ new Fl_Button(10, 20, 60, 110, "button2");
+ new Fl_Button(70, 20, 60, 110, "button");
+ new Fl_Button(130, 20, 60, 110, "button");
+ o->end();
+ }
+ { Fl_Group* o = new Fl_Group(0, 20, 300, 180, " tab5 ");
+ o->label_type(FL_ENGRAVED_LABEL);
+ new Fl_Button(10, 50, 60, 80, "button2");
+ new Fl_Button(80, 60, 60, 80, "button");
+ { Fl_Clock* o = new Fl_Clock(150, 20, 100, 100, "Make sure this clock does not use processor time when this tab is hidden or w\
+indow is iconized");
+ o->box(FL_OSHADOW_BOX);
+ o->label_font(fl_fonts+8);
+ o->color((Fl_Color)238);
+ o->label_size(10);
+ o->align(130);
+ }
+ o->end();
+ }
+ o->end();
+ Fl_Group::current()->resizable(o);
+ }
+ new Fl_Input(60, 220, 130, 30, "inputA:");
+ new Fl_Input(60, 250, 250, 30, "inputB:");
+ { Fl_Button* o = new Fl_Button(180, 290, 60, 30, "cancel");
+ o->callback((Fl_Callback*)cb_cancel);
+ }
+ { Fl_Return_Button* o = new Fl_Return_Button(250, 290, 60, 30, "OK");
+ o->shortcut(0xff0d);
+ o->callback((Fl_Callback*)cb_OK);
+ }
+ o->end();
+ }
+ w->show(argc, argv);
+ return Fl::run();
+}
diff --git a/test/threads.cxx b/test/threads.cxx
new file mode 100644
index 000000000..6aabed8f3
--- /dev/null
+++ b/test/threads.cxx
@@ -0,0 +1,65 @@
+#include <fltk/Fl.h>
+#include <fltk/Fl_Window.h>
+#include <fltk/Fl_Browser.h>
+#include <fltk/Fl_Threads.h>
+#include <stdio.h>
+
+Fl_Thread prime_thread;
+
+Fl_Browser *browser1, *browser2;
+
+void* prime_func(void* p)
+{
+ Fl_Browser* browser = (Fl_Browser*) p;
+
+ // very loosy prime number calculator !
+ for (int n=1000000; ; n++) {
+ int p;
+ for (p=2; p<n; p++) if ( n%p == 0 ) break;
+ if (p == n) {
+ char s[128];
+ sprintf(s, "%d", n);
+ Fl::lock();
+ browser->add(s);
+ Fl::unlock();
+ Fl::awake((void*) (browser == browser1? p:0)); // Cause the browser to redraw ...
+ }
+ }
+ return 0;
+}
+
+int main()
+{
+ Fl_Window* w = new Fl_Window(200, 300, "Multithread test");
+ browser1 = new Fl_Browser(0, 0, 200, 300);
+ w->end();
+ w->show();
+ w = new Fl_Window(200, 300, "Multithread test");
+ browser2 = new Fl_Browser(0, 0, 200, 300);
+ w->end();
+ w->show();
+
+ browser1->add("Prime numbers :");
+ browser2->add("Prime numbers :");
+
+ Fl::lock(); // you must do this before creating any threads!
+
+ // One thread displaying in one browser
+ fl_create_thread(prime_thread, prime_func, browser1);
+ // Several threads displaying in another browser
+ fl_create_thread(prime_thread, prime_func, browser2);
+ fl_create_thread(prime_thread, prime_func, browser2);
+ fl_create_thread(prime_thread, prime_func, browser2);
+ fl_create_thread(prime_thread, prime_func, browser2);
+ fl_create_thread(prime_thread, prime_func, browser2);
+ fl_create_thread(prime_thread, prime_func, browser2);
+
+ // Fl::run();
+ while (w->visible()) {
+ Fl::wait();
+ void* m = Fl::thread_message();
+ if (m) printf("Recieved message: %d\n", int(m));
+ }
+
+ return 0;
+}