// // Copyright (C) 1996 Ben Ross // // You may distribute under the terms of the GNU General Public // License as specified in the COPYING file. // // $Id: xed.C,v 1.14 2001/09/05 19:00:51 ben Exp $ // // Sample program written using the Xenon API // - Eventually to be a full-blown programmers text editor! // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef linux #include #endif #ifdef _SGI_SOURCE #include #endif #ifdef linux // Seems some compiler setups need this. // Thanks to Jamie Honan (jhonan@mpx.com.au) for pointing this out. extern "C" { #include } #endif #include "icon.xpm" struct Prefs { Prefs(void) : windowName("%m*%%F%r [view-only]%"), iconName("%r[ %%m*%%f%r ]%"), lines(30), columns(80), font("-b&h-lucidatypewriter-medium-r-normal-sans-12*"), breakChars("=(),*.;{}[]<>\"-+/&|~^?!:"), lineNumbers(TRUE), autoIndent(TRUE), tab(8), softTab(4), tabToggle(4), maxUndos(100), scrollTimeout(0.03), scrollAccelH(3.0), scrollAccelV(1.0), scrollAmt(2), runOnce(FALSE), rightScroll(FALSE), usePlanemask(TRUE) {} char* windowName; char* iconName; int lines; int columns; char* font; char* breakChars; bool lineNumbers; bool autoIndent; int tab; int softTab; int tabToggle; int maxUndos; double scrollTimeout; double scrollAccelH; double scrollAccelV; int scrollAmt; bool runOnce; bool rightScroll; bool usePlanemask; }; // // Globals: // Prefs gPrefs; // // Function prototypes: // void loadPrefs(void); int testFile(const char*); // // Class declarations // class PromptDialog : public XeDialog { public: PromptDialog(XeWindow* parent, const char*, const char*, const char*, bool includeCwd = TRUE); ~PromptDialog(void); void setCwd(const char*); protected: void doShow(); std::string _prompt; XeLabel* _promptLabel; XeButton* _okButton; XeButton* _cancelButton; XeButton* _clearButton; XeTextField* _textField; XeContainer* _textFrame; void buttonCB(const void*); virtual void button(XeObject* ob, ulong event, void* callData); }; class OpenDialog : public PromptDialog { public: OpenDialog(XeWindow* parent, const char* name) : PromptDialog(parent, name, "Open", "Open:") {} ~OpenDialog(void) {} private: void button(XeObject* ob, ulong event, void* callData); }; class SaveDialog : public PromptDialog { public: SaveDialog(XeWindow* parent, const char* name) : PromptDialog(parent, name, "Save As", "Save As:") {} ~SaveDialog(void) {} private: void button(XeObject* ob, ulong event, void* callData); }; class JumpDialog : public PromptDialog { public: JumpDialog(XeWindow* parent, const char* name) : PromptDialog(parent, name, "Jump", "Jump to line:", FALSE) {} ~JumpDialog(void) {} private: void button(XeObject* ob, ulong event, void* callData); }; // ----------------------------------------------------------------------------- class SearchDialog : public XeDialog { public: SearchDialog(XeWindow* parent, const char* name, const char* title); ~SearchDialog(void); void setSearchType(bool which); XeToggle* _caseToggle, *_directionToggle; XeToggle* _subRadio, *_regRadio, *_active; private: void doShow(); XeButton* _okButton; XeButton* _cancelButton; XeButton* _clearButton; XeTextField* _textField; XeContainer* _textFrame; void buttonCB(const void*); void radioCB(const void*); }; // ----------------------------------------------------------------------------- class MainWindow : public XeWindow, public KrLink { public: MainWindow(const char* name, const char* title); virtual ~MainWindow(void); enum ConfirmType { CONFIRM_QUIT, CONFIRM_QUIT_ANYWAY, CONFIRM_CLOSE, CONFIRM_MESSAGE }; void resizeEvent(uint width, uint height); void closeEvent(void); void findSelected(bool backwards); void find(const std::string& expr); void postMessage(const char* filename, int code, ConfirmType type=CONFIRM_MESSAGE, bool global=FALSE); int readFile(const KrAtom& cwd, const char* filename, bool reopen = FALSE); int writeFile(int& sys_errno); void setWindowNames(void); void setFilename(const std::string&); void jumpToLine(int lineno); const KrAtom& getCwd() { return _cwd; } private: // File status variables: KrAtom _cwd; std::string _filename; const char* _basename; bool _dirty; bool _readOnly; // Widgets: XeMenuBar* _menuBar; XeMenu* _fileMenu; XeMenu* _editMenu; XeMenu* _searchMenu; XeMenu* _optionMenu; XeScrollBar* _vscroll; XeText* _text; // Menu item representing this window in window menu: XeTextItem* _wMenuItem; void getWName(const char* format, char* buf); const char* basename(const char*); int doSave(void); int jumpTo(void); int subSearch(const char* subStr, const char* s, int len, int& si, int& ei, bool insen); int tryMatch(const char* s, const char* p, int o, int len, bool insen); int subSearchBack(const char* subStr, const char* s, int len, int& si, int& ei, bool insen); void mapDialog(XeDialog*); // inline Functions to provide 'lazy' creation of dialog boxes: // void getSearchDialog(); void getSaveDialog(); void getOpenDialog(); void getJumpDialog(); void getMessageDialog(ConfirmType); void getConfirmDialog(ConfirmType); // Callback functions: void scrollCB(const void*); void textCB(const void*); void menuCB(const void*); static void confirmQuitCB(void*, const void*); static void confirmCloseCB(void*, const void*); static void windowMenuCB(void*, const void*); // // Static variables that are shared between all open windows: // // Dialog boxes: static SearchDialog* _searchDialog; static SaveDialog* _saveDialog; static OpenDialog* _openDialog; static JumpDialog* _jumpDialog; static XePromptDialog* _confirmDialog; static XeMessageDialog* _messageDialog; // Search states: static bool _regexSearch; static bool _caseInsen; static bool _searchBack; // Search pattern buffers: #ifdef linux static regex_t _expr; static bool _havePattern; #endif static std::string _searchString; // Window List: // static KrLink _windowList; static int _numWindows; // Window List menu: static XeMenu* _windowMenu; friend class SearchDialog; }; ////////////////////////////////////////////////////////////////// // PromptDialog implementation: // PromptDialog::PromptDialog(XeWindow* parent, const char* name , const char* title, const char* prompt, bool includeCwd) : XeDialog(parent, name,title) { _prompt = prompt; _promptLabel = new XeLabel(this, "prompt"); _promptLabel->setLabel(prompt); _promptLabel->move(10,5); _promptLabel->show(); _textFrame = new XeContainer(this, "textFrame"); _textFrame->setBorder(2); _textField = new XeTextField(_textFrame, "textField"); _textField->setFont(gPrefs.font); _textField->setFocusWindow(); _textField->resize(45, 1); _textField->move(_textFrame->border(), _textFrame->border()); _textField->getResources(); // load X resources _textField->show(); _textFrame->resize(_textField->width() + _textFrame->border()*2, _textField->height() + _textFrame->border()*2); _textFrame->move(10, _promptLabel->y() + _promptLabel->height() + 5); _textFrame->show(); // Create some buttons. _cancelButton = new XeButton(this, "cancelButton"); _cancelButton->setAutoSize(TRUE); _cancelButton->setLabel("Cancel"); _cancelButton->addCallback(KrPMF(&PromptDialog::buttonCB, this), XE_CB_ACTIVATE); _cancelButton->move(_textFrame->x() + _textFrame->width() - _cancelButton->width(), _textFrame->y() + _textFrame->height() + 5); _cancelButton->resize(_cancelButton->width() + 15, _cancelButton->height()); _okButton = new XeButton(this, "okButton"); _okButton->setLabel("Ok"); _okButton->addCallback(KrPMF(&PromptDialog::buttonCB, this), XE_CB_ACTIVATE); _okButton->setXBorderWidth(2); _okButton->resize(_cancelButton->width(), _cancelButton->height()); _okButton->move(_textFrame->x() + _textFrame->width() - _okButton->width() - _okButton->borderWidth(), _cancelButton->y() - _okButton->borderWidth()); _okButton->show(); _cancelButton->move(_okButton->x() - _cancelButton->width() - 10, _cancelButton->y()); _cancelButton->show(); _clearButton = new XeButton(this, "clearButton"); _clearButton->setLabel("Clear"); _clearButton->addCallback(KrPMF(&PromptDialog::buttonCB, this), XE_CB_ACTIVATE); _clearButton->resize(_cancelButton->width(), _cancelButton->height()); _clearButton->move(10, _cancelButton->y()); _clearButton->show(); addKeyBinding(_clearButton, XK_c, Mod1Mask); addKeyBinding(_cancelButton, XK_Escape, 0L); addKeyBinding(_okButton, XK_Return, 0L); addKeyBinding(_okButton, XK_KP_Enter, 0L); resize(_textFrame->x() + _textFrame->width() + 10, _cancelButton->y() + _cancelButton->height() + 5); } PromptDialog::~PromptDialog(void) { delete _promptLabel; delete _textField; delete _textFrame; delete _okButton; delete _cancelButton; } void PromptDialog::setCwd(const char* cwd) { const char* cwd_ = (cwd) ? cwd : gCwd.constCharP(); std::string prompt = _prompt + " (Cwd: " + cwd_ + ")"; _promptLabel->setLabel(prompt.c_str()); } void PromptDialog::buttonCB(const void* data) { XeObject::CBData* cbdata = (XeObject::CBData*)data; button(cbdata->ob, cbdata->event, cbdata->callData); } void PromptDialog::button(XeObject* ob, ulong, void*) { if (ob == _clearButton) _textField->clearText(); else hide(); } void PromptDialog::doShow() { XeDialog::doShow(); _textField->selectAll(FALSE); } void SaveDialog::button(XeObject* ob, ulong event, void* cbdata) { PromptDialog::button(ob, event, cbdata); if (ob == _okButton) ((MainWindow*)_parentWindow)->setFilename(_textField->getLine(0)); } void JumpDialog::button(XeObject* ob, ulong event, void* cbdata) { PromptDialog::button(ob, event, cbdata); if (ob == _okButton) { int lineno = ::atoi(_textField->getLine(0).c_str()); if (lineno > 0) ((MainWindow*)_parentWindow)->jumpToLine(lineno); } } void OpenDialog::button(XeObject* ob, ulong, void*) { if (ob == _clearButton) { _textField->clearText(); return; } else if (ob == _cancelButton) { hide(); return; } MainWindow* theWindow; std::string s = _textField->getLine(0); if (s.length()) { int result; if ((result = testFile(s.c_str()))) { ((MainWindow*)_parentWindow)->postMessage(s.c_str(), result); } else { hide(); theWindow = new MainWindow("mainWindow", "Xenon"); // // $$$ probably a good idea to check if readFile doesnt return 3 // KrAtom cwd = ((MainWindow*)_parentWindow)->getCwd(); theWindow->readFile(cwd.isNull() ? gCwd : cwd, s.c_str()); theWindow->setWindowNames(); theWindow->show(); } } else { hide(); theWindow = new MainWindow("mainWindow", "Xenon"); theWindow->setWindowNames(); theWindow->show(); } } ////////////////////////////////////////////////////////////////// // SearchDialog implementation: // SearchDialog::SearchDialog(XeWindow* parent, const char* name, const char* title) : XeDialog(parent, name, title) { XeLabel* label = new XeLabel(this, "label"); label->setLabel("Find string:"); label->move(10,5); label->show(); _textFrame = new XeContainer(this, "textFrame"); _textFrame->setBorder(2); _textField = new XeTextField(_textFrame, "textField"); _textField->setFont(gPrefs.font); _textField->setFocusWindow(); _textField->resize(45, 1); _textField->move(_textFrame->border(), _textFrame->border()); _textField->getResources(); _textField->show(); _textFrame->resize(_textField->width() + _textFrame->border()*2, _textField->height() + _textFrame->border()*2); _textFrame->move(10, label->y() + label->height() + 5); _textFrame->show(); _caseToggle = new XeToggle(this, "case"); _caseToggle->setLabel("Case Insensitive"); _caseToggle->setState(TRUE); _caseToggle->move(10, _textFrame->y() + _textFrame->height() + 5); _caseToggle->show(); _directionToggle = new XeToggle(this, "dir"); _directionToggle->setLabel("Search Backwards"); _directionToggle->setState(FALSE); _directionToggle->move(_caseToggle->x(), _caseToggle->y() + _caseToggle->height() + 5); _directionToggle->show(); _subRadio = new XeToggle(this, "sub"); _subRadio->setType(XeToggle::XE_RADIO); _subRadio->setLabel("Substring"); _subRadio->addCallback(KrPMF(&SearchDialog::radioCB, this), XE_CB_ACTIVATE); _subRadio->move(_directionToggle->x() + _directionToggle->width() + 10, _caseToggle->y()); _subRadio->show(); _regRadio = new XeToggle(this, "reg"); _regRadio->setType(XeToggle::XE_RADIO); _regRadio->setLabel("Regex"); _regRadio->addCallback(KrPMF(&SearchDialog::radioCB, this), XE_CB_ACTIVATE); //_regRadio->move(_subRadio->x() + _subRadio->width() + 10, // _subRadio->y()); _regRadio->move(_subRadio->x(), _subRadio->y() + _subRadio->height() + 5); _regRadio->show(); // Create some buttons. _cancelButton = new XeButton(this, "cancelButton"); _cancelButton->setAutoSize(TRUE); _cancelButton->setLabel("Cancel"); _cancelButton->addCallback(KrPMF(&SearchDialog::buttonCB, this), XE_CB_ACTIVATE); _cancelButton->move(_textFrame->x() + _textFrame->width() - _cancelButton->width(), _directionToggle->y() + _directionToggle->height() + 5); _cancelButton->resize(_cancelButton->width() + 15, _cancelButton->height()); _okButton = new XeButton(this, "okButton"); _okButton->setLabel("Ok"); _okButton->addCallback(KrPMF(&SearchDialog::buttonCB, this), XE_CB_ACTIVATE); _okButton->setXBorderWidth(2); _okButton->resize(_cancelButton->width(), _cancelButton->height()); _okButton->move(_textFrame->x() + _textFrame->width() - _okButton->width() - _okButton->borderWidth(), _cancelButton->y() - _okButton->borderWidth()); _okButton->show(); _cancelButton->move(_okButton->x() - _cancelButton->width() - 10, _cancelButton->y()); _cancelButton->show(); _clearButton = new XeButton(this, "clearButton"); _clearButton->setLabel("Clear"); _clearButton->addCallback(KrPMF(&SearchDialog::buttonCB, this), XE_CB_ACTIVATE); _clearButton->resize(_cancelButton->width(), _cancelButton->height()); _clearButton->move(10, _cancelButton->y()); _clearButton->show(); addKeyBinding(_clearButton, XK_c, Mod1Mask); addKeyBinding(_cancelButton, XK_Escape, 0L); addKeyBinding(_okButton, XK_Return, 0L); addKeyBinding(_okButton, XK_KP_Enter, 0L); resize(_textFrame->x() + _textFrame->width() + 10, _cancelButton->y() + _cancelButton->height() + 5); } SearchDialog::~SearchDialog(void) { delete _textField; delete _textFrame; delete _okButton; delete _cancelButton; delete _clearButton; } void SearchDialog::buttonCB(const void* data) { XeObject::CBData* cbdata = (XeObject::CBData*)data; if(cbdata->ob == _okButton) { hide(); ((MainWindow*)_parentWindow)->_caseInsen = _caseToggle->getState(); ((MainWindow*)_parentWindow)->_searchBack = _directionToggle->getState(); ((MainWindow*)_parentWindow)->_regexSearch = _regRadio->getState(); std::string s = _textField->getLine(0); if (s.length()) ((MainWindow*)_parentWindow)->find(s); } else if(cbdata->ob == _cancelButton) { hide(); } else if(cbdata->ob == _clearButton) { _textField->clearText(); } } void SearchDialog::radioCB(const void* data) { XeObject::CBData* cbdata = (XeObject::CBData*)data; _active->setState(FALSE); _active = (XeToggle*)cbdata->ob; } void SearchDialog::setSearchType(bool which) { _active = (which) ? _regRadio : _subRadio; _subRadio->setState(!which); _regRadio->setState(which); } void SearchDialog::doShow() { XeDialog::doShow(); _textField->selectAll(FALSE); } ////////////////////////////////////////////////////////////////// // MainWindow implementation: // #define SCROLL_WIDTH 15 // Static initializers: // std::string MainWindow::_searchString; bool MainWindow::_regexSearch = FALSE; bool MainWindow::_caseInsen = TRUE; bool MainWindow::_searchBack = FALSE; #ifdef linux bool MainWindow::_havePattern = FALSE; regex_t MainWindow::_expr; #endif SearchDialog* MainWindow::_searchDialog = NULL; SaveDialog* MainWindow::_saveDialog = NULL; OpenDialog* MainWindow::_openDialog = NULL; JumpDialog* MainWindow::_jumpDialog = NULL; XePromptDialog* MainWindow::_confirmDialog = NULL; XeMessageDialog* MainWindow::_messageDialog = NULL; KrLink MainWindow::_windowList; XeMenu* MainWindow::_windowMenu = NULL; int MainWindow::_numWindows = 0; //// inline const char* MainWindow::basename(const char* filename) { char* tail = strrchr(filename, '/'); return (tail) ? tail + 1 : filename; } inline void MainWindow::getSearchDialog(void) { if (!_searchDialog) _searchDialog = new SearchDialog(this, "searchDialog", "Find"); } inline void MainWindow::getOpenDialog(void) { if (!_openDialog) _openDialog = new OpenDialog(this, "openDialog"); } inline void MainWindow::getSaveDialog(void) { if (!_saveDialog) _saveDialog = new SaveDialog(this, "saveDialog"); } inline void MainWindow::getJumpDialog(void) { if (!_jumpDialog) _jumpDialog = new JumpDialog(this, "jumpDialog"); } inline void MainWindow::getMessageDialog(ConfirmType type) { if (!_messageDialog) _messageDialog = new XeMessageDialog(this, "messageDialog", "Message"); _messageDialog->clearCallbacks(); switch (type) { case CONFIRM_CLOSE: // close window _messageDialog->addCallback(KrPF(&confirmCloseCB), XE_CB_ACTIVATE); break; case CONFIRM_MESSAGE: break; default: abort(); } } inline void MainWindow::getConfirmDialog(ConfirmType type) { if (!_confirmDialog) _confirmDialog = new XePromptDialog(this, "confirmDialog", "Confirm"); _confirmDialog->clearCallbacks(); switch (type) { case CONFIRM_QUIT: // confirm quit if more than one window open: _confirmDialog->addCallback(KrPF(&confirmQuitCB), XE_CB_ACTIVATE); _confirmDialog->setMessage("There is more than one window open.\nQuit anyway?"); break; case CONFIRM_QUIT_ANYWAY: // confirm quit if something is unsaved _confirmDialog->addCallback(KrPF(&confirmQuitCB), XE_CB_ACTIVATE); _confirmDialog->setMessage("There are unsaved documents.\nQuit anyway?"); break; case CONFIRM_CLOSE: // confirm close window _confirmDialog->addCallback(KrPF(&confirmCloseCB), XE_CB_ACTIVATE); _confirmDialog->setMessage("This window contains unsaved data.\nClose anyway?"); break; default: abort(); } } MainWindow::MainWindow(const char* name, const char* title) : XeWindow(name, title) { //XeTimeFunc time("MainWindow"); setIcon(xeIcon); if (!_windowMenu) { _windowMenu = new XeMenu(NULL, "window"); _windowMenu->addCallback(KrPF(&windowMenuCB), XE_CB_ACTIVATE); _windowList.init(); } KrLink::insertBefore(&_windowList); _dirty = FALSE; _readOnly = FALSE; _basename = NULL; _numWindows++; // Create menu-bar _menuBar = new XeMenuBar(this, "menuBar"); _menuBar->addCallback(KrPMF(&MainWindow::menuCB, this), XE_CB_ACTIVATE); _menuBar->move(0, 0); _fileMenu = new XeMenu(_menuBar, "file"); _fileMenu->insertTextItem("new", "New", "n", XK_n, Mod1Mask); _fileMenu->insertTextItem("open", "Open...", "o", XK_o, Mod1Mask); _fileMenu->insertSeparator(); _fileMenu->insertTextItem("close", "Close", "w", XK_w, Mod1Mask); _fileMenu->insertTextItem("reopen", "ReOpen", "r", XK_r, Mod1Mask|ShiftMask); _fileMenu->insertTextItem("save", "Save", "s", XK_s, Mod1Mask); _fileMenu->insertSeparator(); _fileMenu->insertTextItem("quit", "Quit", "q", XK_q, Mod1Mask); _menuBar->insertMenu(_fileMenu, "File"); _editMenu = new XeMenu(_menuBar, "edit"); _editMenu->insertTextItem("undo", "Undo", "z", XK_z, Mod1Mask); _editMenu->insertTextItem("redo", "Redo", "r", XK_r, Mod1Mask); _editMenu->insertSeparator(); _editMenu->insertTextItem("cut", "Cut", "x", XK_x, Mod1Mask); _editMenu->insertTextItem("copy", "Copy", "c", XK_c, Mod1Mask); _editMenu->insertTextItem("paste", "Paste", "v", XK_v, Mod1Mask); _editMenu->insertTextItem("clear", "Clear"); _editMenu->insertTextItem("selectAll", "Select All", "a", XK_a, Mod1Mask); _editMenu->insertSeparator(); _editMenu->insertTextItem("xpaste", "XPaste", "p",XK_p, Mod1Mask); _editMenu->insertSeparator(); _editMenu->insertTextItem("tabToggle", "Tab Toggle", "t", XK_t, Mod1Mask); _editMenu->insertTextItem("cutline", "Cut Line", "y", XK_y, Mod1Mask); //_editMenu->insertTextItem("sleft", "Indent", "]", XK_bracketright, Mod1Mask); //_editMenu->insertTextItem("sright", "Exdent", "[", XK_bracketleft, Mod1Mask); _menuBar->insertMenu(_editMenu, "Edit"); _searchMenu = new XeMenu(_menuBar, "search"); _searchMenu->insertTextItem("find", "Find...", "h", XK_h, Mod1Mask); _searchMenu->insertTextItem("findAgain", "Find Again", "g", XK_g, Mod1Mask); _searchMenu->insertTextItem("findForward", "Find Forward", "f", XK_f, Mod1Mask); _searchMenu->insertTextItem("findBackwards", "Find Backwards", "b", XK_b, Mod1Mask); _searchMenu->insertSeparator(); _searchMenu->insertTextItem("jumpTo", "Jump to line...", "j", XK_j, Mod1Mask); _searchMenu->insertTextItem("braceMatch", "Brace Match", "m", XK_m, Mod1Mask); _menuBar->insertMenu(_searchMenu, "Search"); _wMenuItem = new XeTextItem(_windowMenu, "witem", NULL); _windowMenu->insertItem(_wMenuItem); _menuBar->insertMenu(_windowMenu, "Window", TRUE); //_optionMenu = new XeMenu(_menuBar, "options"); //_optionMenu->insertTextItem("tt", "Tab [4<->8]", "t", XK_t, Mod1Mask); //_menuBar->insertMenu(_optionMenu, "Options"); // Create text widget _text = new XeText(this, "text"); _text->setFont(gPrefs.font); _text->setBreakChars(gPrefs.breakChars); _text->setTabSize(gPrefs.tab); _text->setSoftTab(gPrefs.softTab); _text->setLineNumbers(gPrefs.lineNumbers); _text->setAutoIndent(gPrefs.autoIndent); _text->setMaxUndos(gPrefs.maxUndos); _text->setScrollTimeout(gPrefs.scrollTimeout); _text->setScrollAccelV(gPrefs.scrollAccelV); _text->setScrollAmt(gPrefs.scrollAmt); _text->setNoPlanemask(!gPrefs.usePlanemask); _text->addCallback(KrPMF(&MainWindow::textCB, this), XE_CB_LINETOTAL_CHANGED | XE_CB_TEXTKEY); if (gPrefs.rightScroll) _text->move(0, _menuBar->height()); else _text->move(SCROLL_WIDTH, _menuBar->height()); _text->resize(gPrefs.columns, gPrefs.lines); _text->setFocusWindow(); _text->getResources(); _text->show(); // Create vertical scroll bar _vscroll = new XeScrollBar(this, "vscroll"); _vscroll->addCallback(KrPMF(&MainWindow::scrollCB, this), XE_CB_VALUE_CHANGED); if (gPrefs.rightScroll) _vscroll->move(_text->width(), _menuBar->height()); else _vscroll->move(0, _menuBar->height()); _vscroll->resize(SCROLL_WIDTH, _text->height()); _vscroll->setMax(_text->numLines()); _vscroll->setVisibleRange(_text->visibleLines()); _vscroll->show(); _menuBar->resize(_text->width() + _vscroll->width(), _menuBar->height()); _menuBar->show(); int winWidth = _menuBar->width(); int winHeight = _vscroll->height() + _menuBar->height(); XSizeHints sizeHints; sizeHints.flags = 0; if (gGeometryFlags & XValue || gGeometryFlags & YValue) { sizeHints.flags |= USPosition | PWinGravity; if (gGeometryFlags & XValue) if (gGeometryFlags & XNegative) sizeHints.x = DisplayWidth(gDisplay, gScreenNum) - winWidth + gX; else sizeHints.x = gX; else sizeHints.x = 0; if (gGeometryFlags & YValue) if (gGeometryFlags & YNegative) sizeHints.y = DisplayHeight(gDisplay, gScreenNum) - winHeight + gY; else sizeHints.y = gY; else sizeHints.y = 0; if (gGeometryFlags & XNegative && gGeometryFlags & YNegative) sizeHints.win_gravity = SouthEastGravity; else if (gGeometryFlags & XNegative) sizeHints.win_gravity = NorthEastGravity; else if (gGeometryFlags & YNegative) sizeHints.win_gravity = SouthWestGravity; else sizeHints.win_gravity = NorthWestGravity; move(sizeHints.x, sizeHints.y); } sizeHints.flags |= PResizeInc | PBaseSize; sizeHints.width_inc = _text->cwidth(); sizeHints.height_inc = _text->lheight(); sizeHints.base_width = _vscroll->width() + (_text->width() - _text->textWidth()); sizeHints.base_height = _menuBar->height() + (_text->height() - _text->textHeight()); setNormalHints(&sizeHints); resize(winWidth, winHeight); } MainWindow::~MainWindow() { _numWindows--; KrLink::remove(); _windowMenu->removeItem(_wMenuItem); delete _menuBar; delete _text; delete _vscroll; } void MainWindow::resizeEvent(uint width, uint height) { _menuBar->resize(width, _menuBar->height()); // resize this using pixel values: if (gPrefs.rightScroll) { _text->XeWidget::resize(width - _text->x() - SCROLL_WIDTH, height - _text->y()); _vscroll->move(_text->width(), _menuBar->height()); } else { _text->XeWidget::resize(width - _text->x(), height - _text->y()); } _vscroll->resize(_vscroll->width(), height - _vscroll->y()); _vscroll->setVisibleRange(_text->visibleLines()); } void MainWindow::closeEvent(void) { if (_numWindows > 1) { if (_dirty) { getConfirmDialog(CONFIRM_CLOSE); mapDialog(_confirmDialog); } else { delete this; } } else if (_dirty) { getConfirmDialog(CONFIRM_QUIT_ANYWAY); mapDialog(_confirmDialog); } else { exit(0); } } void MainWindow::confirmQuitCB(void*, const void* data) { XeObject::CBData* cbdata = (XeObject::CBData*)data; bool* result = (bool*)cbdata->callData; if (*result) exit(0); // exiting!! } void MainWindow::confirmCloseCB(void*, const void* data) { XeObject::CBData* cbdata = (XeObject::CBData*)data; bool* result = (bool*)cbdata->callData; if (*result) { // close parent window XeDialog* d = (XeDialog*)cbdata->ob; delete d->getParentWindow(); // No more windows, might as well exit if (_numWindows == 0) exit(0); } } void MainWindow::windowMenuCB(void*, const void* data) { XeObject::CBData* cbdata = (XeObject::CBData*)data; XeMenuCBData* mcbdata = (XeMenuCBData*)cbdata->callData; XeMenuItem* item = mcbdata->_item; KrLink* l = _windowList.next(); while (l != &_windowList) { if (item == ((MainWindow*)l)->_wMenuItem) { ((MainWindow*)l)->raise(); break; } l = l->next(); } } // // Member CB functions. // void MainWindow::scrollCB(const void* data) { XeObject::CBData* cbdata = (XeObject::CBData*)data; int *value = (int*)cbdata->callData; _text->scrollVTo(*value); } void MainWindow::menuCB(const void* data) { XeObject::CBData* cbdata = (XeObject::CBData*)data; XeMenuCBData* mcbdata = (XeMenuCBData*)cbdata->callData; XeMenuItem* item = mcbdata->_item; XeMenu* menu = mcbdata->_menu; if(menu == _fileMenu) { switch(item->index()) { case 0: { // New MainWindow* theWindow = new MainWindow("mainWindow", "Xenon"); theWindow->setWindowNames(); theWindow->show(); } break; case 1: // Open getOpenDialog(); _openDialog->setCwd(_cwd.constCharP()); mapDialog(_openDialog); break; case 2: // Close if (this->_dirty) { getConfirmDialog(CONFIRM_CLOSE); mapDialog(_confirmDialog); } else if (_numWindows > 1) { delete this; } else { exit(0); // no more windows, exit } break; case 3: // Re-Open if (!readFile(_cwd, _filename.c_str(), TRUE)) setWindowNames(); break; case 4: // Save doSave(); break; case 5: // Exit; // traverse window list to see if any are unsaved. // KrLink* l = _windowList.next(); while (l != &_windowList) { if (((MainWindow*)l)->_dirty) { getConfirmDialog(CONFIRM_QUIT_ANYWAY); mapDialog(_confirmDialog); return; } l = l->next(); } // Prompt user if more than one window open: // if (_numWindows > 1) { getConfirmDialog(CONFIRM_QUIT); mapDialog(_confirmDialog); } else { exit(0); } break; } } else if(menu == _editMenu) { switch(item->index()) { case 0: switch(_text->undo()) { case 1: _dirty = FALSE; setWindowNames(); break; case 2: _dirty = TRUE; setWindowNames(); break; } break; case 1: switch(_text->redo()) { case 1: _dirty = FALSE; setWindowNames(); break; case 2: _dirty = TRUE; setWindowNames(); break; } break; case 2: _text->cut(); break; case 3: _text->copy(); break; case 4: _text->paste(); break; case 5: _text->clear(); break; case 6: _text->selectAll(); break; case 7: _text->xpaste(); break; case 8: if(_text->getTabSize() != gPrefs.tabToggle) _text->setTabSize(gPrefs.tabToggle, TRUE); else _text->setTabSize(gPrefs.tab, TRUE); break; case 9: _text->cutLine(); break; } } else if(menu == _searchMenu) { switch(item->index()) { case 0: // Find... getSearchDialog(); _searchDialog->_directionToggle->setState(_searchBack); _searchDialog->_caseToggle->setState(_caseInsen); _searchDialog->setSearchType(_regexSearch); mapDialog(_searchDialog); break; case 1: // Find Again find(std::string()); break; case 2: // Find Forward findSelected(FALSE); break; case 3: // Find Backwards findSelected(TRUE); break; case 4: // Find Backwards jumpTo(); break; case 5: _text->braceMatchSelect(); break; } } } void MainWindow::textCB(const void* data) { XeObject::CBData* cbdata = (XeObject::CBData*)data; XeText* t = (XeText*)cbdata->ob; if(cbdata->event == XE_CB_LINETOTAL_CHANGED || cbdata->event == XE_CB_TEXTKEY) { ulong spos = _vscroll->getPosition(); ulong smax = _vscroll->getMax(); ulong tl = t->topLine(); ulong nl = t->numLines(); if(smax != nl) _vscroll->setMax(nl); if(spos != tl) _vscroll->setPosition(tl); } if(cbdata->event == XE_CB_TEXTKEY && !_dirty) { t->changeEvent(); _dirty = TRUE; setWindowNames(); } } void MainWindow::findSelected(bool backwards) { SelStruct sel = _text->getSel(); std::string str; if (sel.lineStart == sel.lineEnd) str = _text->getSelected(); _regexSearch = FALSE; _searchBack = backwards; find(str); } #if 0 void MainWindow::find(String& expr) { long hash = 0; if (_regexSearch) { return; } else { if (!expr.length() && !_searchString.length()) return; if (expr.length() && _searchString.length()) _searchString = String(); if (expr.length() && !_searchString.length()) _searchString = expr; // create hash of search string const char* s = _searchString.c_str(); for (; *s != 0; s++) hash += _caseInsen ? tolower(*s) : *s; SelStruct sel = _text->getSel(); long len = _text->textLength(); if (!len) return; const char* t = _text->textBase(); t += sel.selStart; } } #endif // // This function uses the given string to search forward // from end of the selection in the text widget line at a time. // If given a null argument find() uses the given precompiled // pattern _expr to find the same pattern. If both the argument // and _expr are null then find() does nothing. // #ifndef linux #define REG_NOMATCH 1 #endif void MainWindow::find(const std::string& expr) { if(_regexSearch) { #ifdef linux if(!expr.length() && !_havePattern) return; if(expr.length() && _havePattern) { regfree(&_expr); _havePattern = FALSE; } if(expr.length() && !_havePattern) { // Compile expression into pattern buffer int flags = REG_EXTENDED | ((_caseInsen) ? REG_ICASE : 0 ); int err = regcomp(&_expr, expr.c_str(), flags); if(err) { char errbuf[256]; regerror(err, &_expr, errbuf, 256); regfree(&_expr); // $$$ should pop up a dialog box here. fprintf(stderr, "MainWindow::find - regcomp error: %s\n", errbuf); return; } _havePattern = TRUE; } #else return; #endif } else { if (!expr.length() && !_searchString.length()) return; if (expr.length()) _searchString = expr; } SelStruct sel = _text->getSel(); bool loop = FALSE; ulong currLine, testLine; ulong offset; #ifdef linux regmatch_t offsets[1]; #endif const char* line; int error; int start, end, len; if (_searchBack) { currLine = sel.lineStart; offset = sel.selStart - _text->lineIndex(currLine); } else { currLine = sel.lineEnd; offset = sel.selEnd - _text->lineIndex(currLine); } testLine = currLine; if (_regexSearch) { #ifdef linux // $$$ get line length and use alloca() // $$$ if GNU regex use regnexec() std::string s = _text->getLine(currLine); std::string::iterator iter = s.begin(); if (_searchBack) s[offset] = 0; else iter += offset; error = regexec(&_expr, &*iter, 1, offsets, 0); // REG_NOTBOL | REG_NOTEOL); #endif } else { line = _text->getLine(currLine, len); if (_searchBack) { len = offset; error = subSearchBack(_searchString.c_str(), line, len, start, end, _caseInsen); } else { line += offset; len -= offset; error = subSearch(_searchString.c_str(), line, len, start, end, _caseInsen); } } if (error == REG_NOMATCH || _searchBack) offset = 0; // loop for each line // while (error == REG_NOMATCH) { if (_searchBack) { if (currLine == 0) { currLine = _text->numLines() - 1; loop = TRUE; } else { currLine--; } } else { if(currLine == _text->numLines() - 1) { currLine = 0; loop = TRUE; } else { currLine++; } } if(_regexSearch) { #ifdef linux std::string s = _text->getLine(currLine); error = regexec(&_expr, s.c_str(), 1, offsets, 0); // REG_NOTBOL | REG_NOTEOL); #endif } else { line = _text->getLine(currLine, len); if (_searchBack) error = subSearchBack(_searchString.c_str(), line, len, start, end, _caseInsen); else error = subSearch(_searchString.c_str(), line, len, start, end, _caseInsen); } // Check for loop-around after no matches // if (currLine == testLine && error == REG_NOMATCH && loop) return; } // If we get to this point we have a match! yay! #ifdef linux if(_regexSearch) { start = offsets[0].rm_so; end = offsets[0].rm_eo; } #endif long index = _text->lineIndex(currLine); _text->setSelection(start + offset + index, end + offset + index, currLine); _text->autoScroll(); if((ulong)_vscroll->getPosition() != _text->topLine()) _vscroll->setPosition(_text->topLine()); // Blah blah blah.... whee! } static inline char lowerChar(bool insen, const char* s) { return (insen) ? tolower(*s) : *s; } int MainWindow::subSearch(const char* subStr, const char* s, int len, int& si, int& ei, bool insen) { if (!s) return REG_NOMATCH; int offset = 0; while (offset < len) { if (lowerChar(insen, s) == lowerChar(insen, subStr)) { si = offset; if ((ei = tryMatch(++s, subStr + 1, ++offset, len, insen)) != -1) return 0; // success; else continue; } offset++; s++; } return REG_NOMATCH; } int MainWindow::subSearchBack(const char* subStr, const char* s, int len, int& si, int& ei, bool insen) { if (!s) return REG_NOMATCH; int offset = len - 1; s += offset; while (offset >= 0) { if (lowerChar(insen, s) == lowerChar(insen, subStr)) { si = offset; if ((ei = tryMatch(s + 1, subStr + 1, offset + 1, len, insen)) != -1) return 0; // success; } offset--; s--; } return REG_NOMATCH; } int MainWindow::tryMatch(const char* s, const char* p, int o, int len, bool insen) { while (*p) { if (o == len) return -1; if (lowerChar(insen, s) != lowerChar(insen, p)) return -1; s++; o++; p++; } return o; // success } void MainWindow::postMessage(const char* filename, int code, ConfirmType type, bool global) { if (code < 1 || code > 2) return; char buf[PATH_MAX]; switch(code) { case 1: sprintf(buf, "Can't open \"%s\".\nNot a regular file.", filename); break; case 2: sprintf(buf, "Can't open \"%s\".\nAccess denied.\n", filename); break; } getMessageDialog(type); _messageDialog->setParentWindow(this); _messageDialog->setMessage(buf); if (global) { _messageDialog->move(DisplayWidth(gDisplay, gScreenNum) / 2 - _messageDialog->width() / 2, DisplayHeight(gDisplay, gScreenNum) / 2 - _messageDialog->height() / 2); } else { _messageDialog->move(_x + (_width / 2) - (_messageDialog->width()/2), _y + (_height / 2) - (_messageDialog->height()/2)); } _messageDialog->show(); } int MainWindow::readFile(const KrAtom& cwd, const char* filename, bool reopen) { if (reopen && !filename) return -1; if (!reopen && *filename != '/') _cwd = cwd; FILE* fp; struct stat fstuff; char* textbuf; const char* pcwd = _cwd.length() ? _cwd.constCharP() : ""; char* fullPath = (char*)alloca(_cwd.length() + 1 + strlen(filename) + 1); sprintf(fullPath, "%s/%s", pcwd, filename); if(stat(fullPath, &fstuff) < 0) { if (reopen) return -1; // If file doesn't exist then open a fresh window // with nothing in it as that file. // $$$ - should check if we can write in this directory or not // using access() _readOnly = FALSE; _filename = filename; _basename = basename(_filename.c_str()); return 0; } if(!S_ISREG(fstuff.st_mode)) return 1; if(access(fullPath, R_OK) < 0) return 2; if(!access(fullPath, W_OK)) _readOnly = FALSE; else _readOnly = TRUE; fp = fopen(fullPath, "r"); if(!fp) return 3; if (reopen) _text->setText(NULL, 0); textbuf = (char*)malloc(fstuff.st_size); fread(textbuf, sizeof(char), fstuff.st_size, fp); fclose(fp); _dirty = FALSE; _text->setText(textbuf, fstuff.st_size); _vscroll->setMax(_text->numLines()); if (!reopen) { _filename = filename; _basename = basename(_filename.c_str()); } else { _text->reset(); } if(_readOnly) { _fileMenu->getItem(4)->disable(); _editMenu->getItem(2)->disable(); _editMenu->getItem(4)->disable(); _editMenu->getItem(5)->disable(); _editMenu->getItem(7)->disable(); _text->setReadOnly(_readOnly); } else if (reopen) { _fileMenu->getItem(4)->enable(); _editMenu->getItem(2)->enable(); _editMenu->getItem(4)->enable(); _editMenu->getItem(5)->enable(); _editMenu->getItem(7)->enable(); _text->setReadOnly(_readOnly); } return 0; } int MainWindow::writeFile(int& sys_errno) { if(_readOnly) return 1; const char* pcwd = _cwd.length() ? _cwd.constCharP() : ""; char* fullPath = (char*)alloca(_cwd.length() + 1 + _filename.length() + 1); sprintf(fullPath, "%s/%s", pcwd, _filename.c_str()); FILE* fp; fp = fopen(fullPath, "w"); if(!fp) { sys_errno = errno; return 2; } const char* text; if((text = _text->textBase())) fwrite(text, sizeof(char), _text->textLength(), fp); fclose(fp); return 0; } void MainWindow::setWindowNames(void) { char buf[1024]; getWName(gPrefs.windowName, buf); setTitleName(buf); getWName(gPrefs.iconName, buf); setIconName(buf); _wMenuItem->setLabel(buf); } void MainWindow::getWName(const char* format, char* buf) { int i =0; char* p = (char*)format; bool inBraces = FALSE; bool ignore = FALSE; i = 0; while(*p) { if(*p == '%') { p++; if(inBraces) { inBraces = FALSE; ignore = FALSE; continue; } if(*p == 0) break; if(*p == '%') { buf[i++] = *p++; continue; } switch(*p) { case 'F': if(_filename.length()) { strcpy(&buf[i], _filename.c_str()); i += _filename.length(); } else { strcpy(&buf[i], "Untitled"); i += 8; } break; case 'f': if(_basename) { strcpy(&buf[i], _basename); i += strlen(_basename); } else { strcpy(&buf[i], "Untitled"); i += 8; } break; case 'm': inBraces = TRUE; ignore = !_dirty; break; case 'r': inBraces = TRUE; ignore = !_readOnly; break; } p++; continue; } if(!ignore) buf[i++] = *p; p++; } buf[i] = 0; } int MainWindow::doSave(void) { if(!_filename.length()) { getSaveDialog(); _saveDialog->setCwd(_cwd.constCharP()); mapDialog(_saveDialog); return 1; } int sys_errno; if(writeFile(sys_errno) == 2) { getMessageDialog(CONFIRM_MESSAGE); _messageDialog->setMessage(krFormat("Error opening file \"%s\" for writing.\n%s", _filename.c_str(), strerror(sys_errno))); mapDialog(_messageDialog); return -1; } if(_dirty) { _dirty = FALSE; setWindowNames(); _text->saveEvent(); } return 0; } int MainWindow::jumpTo(void) { getJumpDialog(); mapDialog(_jumpDialog); return 0; } void MainWindow::jumpToLine(int lineno) { _text->setSelection(lineno - 1, 0); _text->autoScroll(); if((ulong)_vscroll->getPosition() != _text->topLine()) _vscroll->setPosition(_text->topLine()); } void MainWindow::setFilename(const std::string& filename) { _filename = filename; _basename = basename(filename.c_str()); // $$$ _cwd should be settable from a function argument if (*(filename.c_str()) != '/') _cwd = gCwd; if(doSave() < 0) { // arrgh couldnt save file reset _filename and _basename _filename = std::string(); _basename = NULL; } } void MainWindow::mapDialog(XeDialog* dialog) { dialog->setParentWindow(this); dialog->move(_x + (_width / 2) - (dialog->width()/2), _y + (_height / 2) - (dialog->height()/2)); dialog->show(); } void loadPrefs(void) { char pathBuf[PATH_MAX]; getHomeDir(pathBuf); strcat(pathBuf, "/.xerc"); TokenParser parser(pathBuf, 1024, 2); while (parser.readLine() == TokenParser::OK) { const char* prefName = parser.getArg(0); if (parser.getNumArgs() <= 1) continue; switch (*prefName) { case 'w': // windowName if (!strcmp(parser.getArg(0), "windowName")) gPrefs.windowName = strdup(parser.getArg(1)); break; case 'i': // iconName if (!strcmp(parser.getArg(0), "iconName")) gPrefs.iconName = strdup(parser.getArg(1)); break; case 'f': // font if (!strcmp(parser.getArg(0), "font")) gPrefs.font = strdup(parser.getArg(1)); break; case 'a': // autoIndent if (!strcmp(parser.getArg(0), "autoIndent")) gPrefs.autoIndent = (*(parser.getArg(1)) == 't'); break; case 'b': // breakChars if (!strcmp(parser.getArg(0), "breakChars")) gPrefs.breakChars = strdup(parser.getArg(1)); break; case 'c': // columns if (!strcmp(parser.getArg(0), "columns")) gPrefs.columns = atoi(parser.getArg(1)); break; case 'l': if (!strcmp(parser.getArg(0), "lines")) gPrefs.lines = atoi(parser.getArg(1)); else if (!strcmp(parser.getArg(0), "lineNumbers")) gPrefs.lineNumbers = (*(parser.getArg(1)) == 't'); break; case 'm': if (!strcmp(parser.getArg(0), "maxUndos")) gPrefs.maxUndos = atoi(parser.getArg(1)); break; case 't': if (!strcmp(parser.getArg(0), "tab")) gPrefs.tab = atoi(parser.getArg(1)); else if (!strcmp(parser.getArg(0), "tabToggle")) gPrefs.tabToggle = atoi(parser.getArg(1)); break; case 's': if (!strcmp(parser.getArg(0), "softTab")) gPrefs.softTab = atoi(parser.getArg(1)); else if (!strcmp(parser.getArg(0), "scrollTimeout")) gPrefs.scrollTimeout = atof(parser.getArg(1)); else if (!strcmp(parser.getArg(0), "scrollAccel")) gPrefs.scrollAccelV = atof(parser.getArg(1)); else if (!strcmp(parser.getArg(0), "scrollAmt")) gPrefs.scrollAmt = atoi(parser.getArg(1)); break; case 'u': if (!strcmp(parser.getArg(0), "usePlanemask")) gPrefs.usePlanemask = (*(parser.getArg(1)) == 't'); break; case 'r': if (!strcmp(parser.getArg(0), "runOnce")) gPrefs.runOnce = (*(parser.getArg(1)) == 't'); if (!strcmp(parser.getArg(0), "rightScroll")) gPrefs.rightScroll = (*(parser.getArg(1)) == 't'); } } // Any global X geometry specifications override values in // gPrefs. if (gGeometryFlags & WidthValue) gPrefs.columns = gWidth; if (gGeometryFlags & HeightValue) gPrefs.lines = gHeight; } int testFile(const char* filename) { struct stat fstuff; if (stat(filename, &fstuff) < 0) return 0; if (!S_ISREG(fstuff.st_mode)) return 1; if (access(filename, R_OK) < 0) return 2; return 0; } //////////////////////////////////////////////////////////////////////// // Main Program // void runCB(void*, const void* data) { XeApp::RunOnceCBData* cbdata = (XeApp::RunOnceCBData*)data; #if 0 cerr << "runCB: cwd = " << cbdata->cwd << " argc = " << cbdata->argc << " argv = "; for (int i=0; i < cbdata->argc; i++) cerr << "'" << cbdata->argv[i] << "' "; cerr << endl; #endif MainWindow* theWindow; if (!cbdata->argc) { theWindow = new MainWindow("mainWindow", "Xenon"); theWindow->setWindowNames(); theWindow->show(); return; } for (int i = 0; i < cbdata->argc; i++) { MainWindow* theWindow = new MainWindow("mainWindow", "Xenon"); int res = theWindow->readFile(cbdata->cwd, cbdata->argv[i]); if (res != 0) { theWindow->postMessage(cbdata->argv[i], res, MainWindow::CONFIRM_CLOSE, TRUE); break; } theWindow->setWindowNames(); theWindow->show(); } } int main(int argc, char** argv) { XeApp* theApp = new XeApp("Xe", argc, argv); loadPrefs(); // $$$ use getopt_long() ... int index = 1; bool nofork = FALSE; while (argc > index) { if (!strcmp(argv[index], "-v")) { extern const char* xeVersion; std::cerr << gProgname << ": Xenon version: " << xeVersion << std::endl; exit(0); } else if (!strcmp(argv[index], "-nofork")) { nofork = TRUE; index++; } else if (!strcmp(argv[index], "-orphan")) { gPrefs.runOnce = FALSE; index++; } else { break; } } if (gPrefs.runOnce && !theApp->setRunOnce(KrPF(&runCB), argc - index, &argv[index])) { delete theApp; return 0; } if (!nofork) { int pid = fork(); if (pid < 0) { std::cerr << gProgname << " - fork error: " << strerror(errno) << std::endl; exit(1); } else if (pid) { // In parent, exit exit(0); } } XeApp::RunOnceCBData cbdata; cbdata.cwd = gCwd.constCharP(); cbdata.argc = argc - index; cbdata.argv = &argv[index]; runCB(NULL, &cbdata); theApp->run(); return 0; }