From 3cb5ebe0e811f3db008085d985b7761725589a74 Mon Sep 17 00:00:00 2001 From: No Author Date: Wed, 1 Aug 2001 21:24:49 +0000 Subject: This commit was manufactured by cvs2svn to create branch 'branch-1.1'. git-svn-id: file:///fltk/svn/fltk/branches/branch-1.1@1513 ea41ed52-d2ee-0310-a9c1-e6b18d33e121 --- FL/Fl_Export.H | 37 + FL/Fl_FileBrowser.H | 80 ++ FL/Fl_FileChooser.H | 80 ++ FL/Fl_FileIcon.H | 107 ++ FL/Fl_Shared_Image.H | 203 ++++ FL/Fl_Text_Buffer.H | 241 +++++ FL/Fl_Text_Display.H | 217 ++++ FL/Fl_Text_Editor.H | 105 ++ FL/Fl_Tooltip.H | 72 ++ documentation/Fl_Image.html | 142 +++ src/Fl_FileBrowser.cxx | 446 +++++++++ src/Fl_FileChooser.cxx | 248 +++++ src/Fl_FileChooser.fl | 208 ++++ src/Fl_FileChooser2.cxx | 669 +++++++++++++ src/Fl_FileIcon.cxx | 329 +++++++ src/Fl_Text_Buffer.cxx | 2287 +++++++++++++++++++++++++++++++++++++++++++ src/Fl_Text_Display.cxx | 1947 ++++++++++++++++++++++++++++++++++++ src/Fl_Text_Editor.cxx | 446 +++++++++ src/Fl_Tooltip.cxx | 157 +++ src/Fl_lock.cxx | 145 +++ src/allfiles.xbm | 6 + src/fl_dnd.cxx | 34 + src/fl_dnd_x.cxx | 170 ++++ src/new.xbm | 6 + src/up.xbm | 6 + test/tabs.cxx | 85 ++ test/threads.cxx | 65 ++ 27 files changed, 8538 insertions(+) create mode 100644 FL/Fl_Export.H create mode 100644 FL/Fl_FileBrowser.H create mode 100644 FL/Fl_FileChooser.H create mode 100644 FL/Fl_FileIcon.H create mode 100644 FL/Fl_Shared_Image.H create mode 100644 FL/Fl_Text_Buffer.H create mode 100644 FL/Fl_Text_Display.H create mode 100644 FL/Fl_Text_Editor.H create mode 100644 FL/Fl_Tooltip.H create mode 100644 documentation/Fl_Image.html create mode 100644 src/Fl_FileBrowser.cxx create mode 100644 src/Fl_FileChooser.cxx create mode 100644 src/Fl_FileChooser.fl create mode 100644 src/Fl_FileChooser2.cxx create mode 100644 src/Fl_FileIcon.cxx create mode 100644 src/Fl_Text_Buffer.cxx create mode 100644 src/Fl_Text_Display.cxx create mode 100644 src/Fl_Text_Editor.cxx create mode 100644 src/Fl_Tooltip.cxx create mode 100644 src/Fl_lock.cxx create mode 100644 src/allfiles.xbm create mode 100644 src/fl_dnd.cxx create mode 100644 src/fl_dnd_x.cxx create mode 100644 src/new.xbm create mode 100644 src/up.xbm create mode 100644 test/tabs.cxx create mode 100644 test/threads.cxx 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 +# include + + +// +// 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 +#include +#include +#include +#include +#include +#include +#include "filename.H" +#include +#include +#include + +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_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 +#include + +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 + +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 +#include +#include +#include +#include + +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 + +// 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 +#include + +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 @@ +Fl_Image + +

class Fl_Image

+ +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. + +

Methods

+ +

ulong id, mask;
+void _draw(int X, int Y, int W, int H, int cx, int cy);

+ +Subclasses may use these protected members of the base class to +draw a cached pixel array. They must first set id and +mask to the color and transparency offscreen windows, using +system-specific code. Then they can call _draw() to draw +them. + +

int w,h

+ +These members hold the width and height of the image. They are not +correct until measure() is called. These are public instance +variables for back comptability, but you should never set them. + +

virtual void Fl_Image::measure(int W, int H);

+ +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. + +

virtual void Fl_Image::draw(int x,int y,int w,int h, int cx,int +cy);

+ +Draw the image so the point cx,cy of the image is at +x,y. The image may be scaled or clipped to fit in the w,h +rectangle, but this is not necessary (although obeying the current +fl_clip value is!). + +

void Fl_Image::draw(int x,int y,int w,int h, Fl_Flags align);

+ +This non-virtual function uses measure() and the +align flags to figure out cx,cy and call the normal draw +function. This allows you to center or align any edge of the image +with a bounding box. + +

virtual Fl_Image::~Fl_Image();

+ +The destructor throws away any server-cached information, but in most +cases does not destroy the local data passed to a constructor. + +

class Fl_Bitmap : public Fl_Image

+ This object encapsulates the width, height, and bits of an X bitmap +(XBM), and allows you to make an Fl_Widget use a bitmap as a +label, or to just draw the bitmap directly. + +

Fl_Bitmap(const char *bits, int W, int H) +
Fl_Bitmap(const uchar *bits, int W, int H)

+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: +
    +
    +#include "foo.xbm"
    +...
    +Fl_Bitmap bitmap = new Fl_Bitmap(foo_bits, foo_width, foo_height);
    +
    +
+

~Fl_Bitmap()

+ The destructor will destroy any X pixmap created. It does not do +anything to the bits data. +

void draw(int x, int y, int w, int h, int ox = 0, int oy = 0)

+1 bits are drawn with the current color, 0 bits +are unchanged. +The image is clipped to the destination rectangle: the area +ox,oy,w,h is copied to x,y,w,h. +

void draw(int x, int y)

+Draws the bitmap with the upper-left corner at x,y. This is +the same as doing draw(x,y,this->w,this->h,0,0). + +

class Fl_Pixmap : public Fl_Image

+ +This object encapsulates the data from an XPM image, and allows you to +make an Fl_Widget use a pixmap as a label, or to just draw +the pixmap directly. + +

Fl_Pixmap(char *const* data)

+ 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: +
    +
    +#include <fltk/Fl_Pixmap.h>
    +#include "foo.xpm"
    +...
    +Fl_Pixmap pixmap = new Fl_Pixmap(foo);
    +
    +
+

~Fl_Pixmap()

+ The destructor will destroy any X pixmap created. It does not do +anything to the data. + +

void draw(int x, int y, int w, int h, int ox = 0, int oy = 0)

+The image is clipped to the destination rectangle: the area +ox,oy,w,h is copied to x,y,w,h. The current +implementation converts the pixmap to 24-bit RGB data and uses fl_draw_image() to draw it. Thus you +will get dithered colors on an 8 bit screen.

+ +

void draw(int x, int y)

+ Draws the image with the upper-left corner at x,y. This is +the same as doing draw(x,y,this->w,this->h,0,0). + +

class Fl_RGB_Image

+ +This object encapsulates a full-color RGB image, and allows you to +make an Fl_Widget use an image as a label, or to just draw the +image directly. + +

Fl_RGB_Image(const uchar *data, int W, int H, int D = 3, int LD = 0)

+ Construct using a pointer to RGB data. W and H are +the size of the image in pixels. D 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). LD is the delta between lines (it may +be more than D * W 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. +

~Fl_RGB_Image()

+ The destructor will destroy any X pixmap created. It does not do +anything to the data. +

void draw(int x, int y, int w, int h, int ox = 0, int oy = 0)

+The image is clipped to the destination rectangle: the area +ox,oy,w,h is copied to x,y,w,h. +

void draw(int x, int y)

+ Draws the image with the upper-left corner at x,y. This is +the same as doing draw(x,y,this->w,this->h,0,0). + + + 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 +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +# include +# include +#endif /* _WIN32 */ + +#if defined(__EMX__) +#define INCL_DOS +#define INCL_DOSMISC +#include +#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 +#include + +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 +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 } {} + +decl {\#include } {} + +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 } + code2 {\#include } + code3 {\#include } 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 } + 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 } + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +# include +# include +#else +# include +# include +#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 +#include +#include +#include +#include +#include +#ifdef _WIN32 +# include +# define F_OK 0 +#else +# include +#endif + +#include +#include +#include +#include + + +// +// 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 +#include +#include +#include +#include + + +#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, "" ); + return 5; + } else if ( c == nullSubsChar ) { + sprintf( outStr, "" ); + 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include + +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 +#include +#include + +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 +#include + +//////////////////////////////////////////////////////////////// +#if defined(_WIN32) + +#include +#include + +// 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 +#include + +#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 +#include +#include + +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 +#include +#include +#include +#include + +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; padd(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; +} -- cgit v1.2.3