diff --git a/kspread/CHANGES b/kspread/CHANGES index 64bfc66f9e0..5c4e497f26e 100644 --- a/kspread/CHANGES +++ b/kspread/CHANGES @@ -1,462 +1,463 @@ Changes since KSpread 2.2 ========================= Features -------- Manual page breaks. Enhancements ------------ Display status bar messages instead of notifications (in a few cases). +Fixation permutation for cell ranges, not only for locations (by F4 in editor). Bug Fixes --------- Fix: Unhiding a sheet depends on the map protection, not on the sheet's. Fix the context menu choice of selected text values. Do not crash/freeze on selection changes. Initially disable the apply and cancel button in the cell tool option widget. Fix the dependency calculation after region changes and after sheet additions/removals. Fix text/background/border color actions by using KoColorPopupAction (if pigment is built). Fix map/sheet protection action. 208868: Fix action triggering on selection changes, which resulted in style changes. Fix cell removal by shifting cells up. Fix column/row insertion/removal undo/redo for non-contiguous selections. Fix special paste operations (!= OverWrite) do not undo properly. Fix undo of pasting styles. Fix undo of pasting comments. Fix crash in cell insertion/removal dialog. Fix the cell shift command and its cell repainting for non-contiguous selections. Properly update the canvas on changing sheets. Fix right-to-left layouts (except for shapes). Prevent the canvas from staying blank at startup. Fix the reference size grip handling. Accelerate startup after opening a file of a foreign format. Reactivate the 'Insert series..' action. Fix repainting after sheet protection changes. Fix the border painting of the column/row headers and the select all button. Fix the right mouse button interaction for the column/row headers. Allow to select multiple cells after using a reference selection widget (e.g. in goal seek dialog). Fix the goal seek dialog/command. Change to the desired sheet, if an address is entered in the location combobox. Do not leave reference selection mode after selecting a cell (range). Fix the position of the function auto-completion popup. Fix default cell height: Let cell with two-lined text obscure only two rows. Fix the function auto-completion: Close the hint label properly and do not crash, if there's no help text. Developer-Visible Only ---------------------- Improved the function module loading/unloading. (@PACKAGERS: plugin interface changed!) Implement models for Map/Sheet/Region. Changes for KSpread 1.5 Beta 1 ============================== Features -------- Non-contiguous selection Rewritten dependency handling Embedded picture/image inside workbook Improved and faster formula calculation Manual line break (wish #9881) Syntax highlight and function autocomplete for formula editing Custom sheet selection and ordering in print dialog Enhancements ------------ QWhatsThis help is available to various dialogs Updated dialog screenshot in the handbook Default autofill increment is set to 1 if only one cell is selected Sum button automatically selects a suitable cell range Percent operator in formula (wish #101217) Improved zoomed printouts (bug #115879) Use standard KDE font dialog in cell format dialog (bug #116991) Faster redraw of merged cells (bug #118248) Bug Fixes --------- #120234: Freeze on circular reference #120232: Strange behaviour on creating new file #120204: Cell comment isn't wrapped #119675: Background brush color is not correctly loaded #119097: Right arrow for entry and validation #118769: Better wording cursor movement setting #117435: Incorrect result from COUNTIF function #117252: Fix date (and time) increment for autofill #116903: Indentation setting is not correctly handled in preferences #116901: Unclear unit setting in preferences #116702: Fix freeze with certain spreadsheet content #116546: Endless dependency calculation #116444: Fix cell protection #116236: Crash on OpenDocument with external file cell references #116144: Incorrect return value of ISBLANK function #115948: Formatting problem in automerged cells #115362: Crash when spellchecking #114693: Comment indicator in sheet properties dialog #114961: Enter does not move cursor #114635: Endless loop while loading #109633: OASIS format wrongly changes vertical align to Bottom #50192: Always allow date input in ISO 8601 format ??#483630: Prevent hiding all rows/columns Developer-Visible Only ---------------------- Manipulators used in commands KSpread::Cell is associated with KSpread::Format, no longer inherits it Setter and getter methods for KSpread::Point since 1.4.1 =========== - Fix slow scrolling left/right (#110551, #101234) since 1.4.0 =========== - Fix potential crash when rendering obscured cells (#108659) - Don't automatically make cells left aligned when increasing indent. since 1.4-beta1 =============== - Use General/Blank Worksheet as default template. - KSpread crashes on exit if there is a chart in the sheet (#101915). - The data editor is disabled for charts which has been loaded. - Don't use 600 dpi as default resolution (the chart printing is much faster now). - The chart/object printing is simplified and works correctly now. Unfortunately this breaks the transform feature but the ordinary rectangle printing is more important. - Paint the child document after it has been inserted. since 1.3 ========= - fix bug #63332: Pasting into a cell that has spilled out, pastes into all overwritten cells - fix bug #101926: kspread never prints the grid - Implement save/load Oasis format - Allow to sort without respect case - allow editing/removing of links - fixed bug 76861: bad rendering of hyperlink - Validation: allow to display or not message - Add new dcop function - Start to port KSpread_undo to kcommand - Add combobox to search area name - Add Help Message as in oocalc - multiple steps undo and redo - fix problem where hidden sheet is simply appended in the tab bar (when it is shown again). - fix bug #81026: SUMIF() function added - sheet layout direction can be explicitly set (fix bug #46831) - new dependency manager - prevent a very wide Insert Link dialog - fix bug #59291: potential crash on loading new template changes for 1.3.4 ================= - fix bug #87369: ctrl+C in formula bar doesn't copy contents changes for 1.3.3 ================= - fix bug #78747: problem with duration format (hh:mm:ss) - fix bug #74091: wrong special paste from cell with formula - fix bug #76679: time shown with one second offset changes for 1.3.2 ================= - fix condition cell attribute (multi condition) - fix bug #69798: SEXDEC parse error - fix enable/disable action into validation dialogbox - fix disable/enable "show table" menu item - fix bug #69066: crash when viewing a KSpread document in Konqueror changes for 1.3.1 ================= - fix bug #77844: undo not working after deleting multiple cells - fix bug #45822: fixed crash when opening a file over the network since 1.3 RC1 ============= - fix display of text starting with a single quote - fix problem formatting the whole row (#65504) - make the top/middle/bottom alignment buttons work - fix problem with reference from another sheet (bug #60455) - show indicator of chosen/selected cells (bug #58098) - hide in-place cell editing when choosing other sheet - proper zoom and display of formatted hyperlink (#66214) - fix flickering problem with many hyperlinks (#65501) - don't crash when leaving the preview in Konq (#65928) - fix redraw problem with Print Preview (#60147) - support only one zoom for two or more views - fix painting problem with multiple views - prevent possible crash with conditional formatting (#58713) - fix dependency problem with automatic recalculation (#58097) - better adjustment of cell references on cut&paste - fix crash on csv import (#66047) - fix problem for cells copy/cut to clipboard (#58712) - show frame when embedded object is selected (#37152) - better handling of "nothing to print" warning (#60147) - when embedded, use real zoom instead of scaling (#45503) - fix data sorting (#63317) since 1.3 beta4 =============== - #64794: kspread problem with calculating dates - #61570: cell border does not work with merged cells - fix unsorted zoom values (#64154) since 1.3 beta3 =============== - KSpread is slow during text to column conversion (#59999) - fix problem cycling absolute cell reference with F4 key (#46959) - the calculator plugin saves its configuration now (#49954) - fix detection of sheet direction (for rtl support) since 1.3 beta2 =============== - KSpread crash when changing paper orientation (#60279) since 1.3 beta1 =============== - adjust print zoom to limit printout to m x n pages - new functions: GETPIVOTDATA, BESSELI, BESSELJ, BESSELK, BESSELY, ERF, ERFC, CONVERT - new templates: Balance Sheet, Price Quotation, Packing Slip since 1.2 relase ( 'til 1.3 beta 1 :-) ====================================== - real more than one currency support - new date and time formats - enhanced the "Text to Columns", "Insert from file" and "Insert from clipboard" dialog to support "ignore double delimiters" - "Insert from file" now also adjusts the column width depending on imported cell content - enhanced the CSV import dlg - to support "ignore double delimiters" - to adjust cell width to imported cell content - you can specify the column and row range for import - enhanced the CSV export dialog so that you can choose - table to export - delimiters between tables - delimiter between cells - character for quotes - export selection only - Goal Seek message fixes + you can select the cells with the mouse now - enhanced Gnumeric import filter to support - background (color + patterns), borders (colors + style) - font formating, text alignment - format parsing (date, time, numbers,...), cell height, width - print range, page setup, hidden columns/row, comments - new OpenCalc export and import filter - Added subtotal dialog and formula - Paper layout can now be applied to all sheets - now you can define the default cell style - Autoscroll while selecting now also available for columns and row headers - When cell is too short, then the content is now available in the tooltip - Drag 'n' Drop of selections - Protection of sheets or of the document structure (with or without password) - Protection tab in layout dialog: you unprotect certain cells, hide their contents, hide the formulas, or select them for not being printed - FindNext, FindPrevious cell enhancement for find - Fill: Fill area from right to left, left to right, bottom to top, top to bottom - Full zoom support - Printing: + High resolution printing (600dpi) + Improved WYSIWYG + Zoom support on printout - New direction mode when pressing Enter: you can jump now to the first cell of the next row - hierachical style support - builtin styles - creation of custom styles - creation of custom styles using a cell as template - insert special characters - smaller, more efficent file format - many performance enhancements - more powerful conditional cell attributes (you can assign a whole style if condition matches) - conditions can be used for text and numbers now (was numbers only before) - new templates: Body-Mass Index Calculator, Calendar 2003, Student ID Card, Invoice, Expense Report New functions: Date/Time (16): - EDATE, EOMONTH, DAYS360, WEEKDAY, TIMEVALUE, DATEVALUE, YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, DAYNAME, MONTHNAME, EASTERSUNDAY, ISOWEEKNUM Math (16): - SUBTOTAL, POWER, TRUNC, CEILING, PRODUCT (now Excel compatible), KPRODUCT, RANDEXP, RANDBINOM, RANDNEGBINOM, RANDBERNOULLI, RANDPOISSON, MROUND, COUNTA, MINA, MAXA, SUMA Statistical (24): - VARP, STDEV, VAR, STDEVP, GEOMEAN, HARMEAN, KURT, LOGINV, DEVSQ, STANDARDIZE, HYPGEOMDIST, NEGBINOMDIST, KURTP, CORREL, COVAR, SMALL, LARGE STDEVA, STDEVPA, VARA, VARPA, SKEW, SKEWP, MODE Financial (19): - PPMT, DDB, DURATION, PMT, NPER, ISPMT, IPMT, TBILLEQ, TBILLPRICE, TBILLYIELD, ACCRINT, ACCRINTM, COUPNUM, DOLLARDE, DOLLARFR, DISC, EFFECT, INTRATE, RECEIVED Database (12): - DAVERAGE, DCOUNT, DCOUNTA, DGET, DMAX, DMIN, DPRODUCT, DSTDEV, DSTDEVP, DSUM, DVAR, DVARP Reference & Lookup (8): - ADDRESS, AREAS, CHOOSE, COLUMN, COLUMNS, INDIRECT, ROW, ROWS Engineering (2): - DELTA, GESTEP Logical (2): - FALSE, TRUE Information (2): - ISNONTEXT, ISNUMBER Obsolete formulas - stddev (was stddevp in fact) - day (is dayname now) - month (is monthname now) Obsolete but still usable formulas (do not show up in dialog ) - variance, pow, isnottext, isnum, effective ===================================================== After 1.2 above ===================================================== Changes from 1.1 to 1.2 ======================= Since RC1: ---------- Bugfixes: - Fix display and printout with special files (offset by 1 pixel). - Fix printout of thick cell borders on page borders - Fix printout of children, when they overlap one page - Fix #45967, #31130 - Fix #45944 (Normal): Paste special->text overwrite borders - Fix #42456 (Normal): merged cells with centered text do not survive insert/delete rows - Fix #45943 (Crash): crash after "money format" - Fix function documentation (YEAR(), FACTDOUBLE() ) - Fix parsing and error checking for spreadsheet names. - Fix #46045 (Normal): serious errors in forumulas - Fix Embedded chart changes titles when opening again - Fix #44628 (Grave): 1.1 document not read with 1.2 - circular references - Fix #40150 (Normal): Precision problem in calculations - Fix #29524: KSpread Formula editors tooltip should be changed - Fix #18083: KSpread rounding error - Fix #46530: crash on selecting validity - Fix #45395: Save as HTML is called "mime type" - Fix #34088: Precision of currency is too low or overflow is handled in a strange way - Fix #45324: consolidate function result reference area selecting unintuitive Until RC1: ---------- New: - Like all of KOffice: a readonly GUI for embedding into Konqueror - More DCOP calls Bugfixes: - bug fixes - Fix cancel add embedded document. - Fix mem leak - Fix copy/paste attribute. - Fix offset of rotated text in one case (top/middle) Until beta2: ------------ - bug fixes - perfomance enhancements - print functions: - added option for print grid, comment indicator and formula indicator - added print range definiton - added repeat columns & rows on printout - added "" macro in header/footer - paper layout now a property of sheet (not the whole workbook) - header/footer now a property of sheet (not the whole workbook) - Settings in paper layout are now undoable - Sort enhanced for - sorting by up to three rows or columns - possibility of putting the result of sorting some place else - you can use a custom lists (like days of week) as a primary key - option if you want to copy the layout or not - you can define a row header which gets copied but not sorted - auto continuation support for every direction and all types supported in KSpread and for more complex series like 1,3,4,6,... - just one "copy" instead of "copy" and "copy as text" - insertion of series supports now doubles and decreasing series - support for inserting data from SQL databases - implemented "Goal Seek" - implemented new shortcuts for cell formating - implemented "Insert from text file" and "Insert formated clipboard data" - Added "Text to Column" - improved Data Consolidate, with more choices: Sum, Average, Count, Min, Max, Product, Standard Deviation, Variance. - fixed some functions to be Excel-compatibel (FIND, REPLACE,LEFT, RIGHT,MID,REPT) - now it's "Sheet" everywhere, not more "Table" - added formula indicator (small blue rectangle on the corner, when the cell holds a formula). idea from Quattro Pro - added Greek to EURO() function - new function category: Engineering - added support with templates - added support for changing row/col and table of named areas - Multiple views work for spreadsheets (can change tables, select different parts of the sheet independantly in each open view or embedded object) - DCOP interface for a table has changed. Many of the operations have been moved to the 'view' class. Current DCOP scripts will need to be updated - Zoom new functions added: ISBLANK, CHAR, CODE, VALUE, DOLLAR, CONCATENATE, FIXED, T, TEXT, SUBSTITUTE, SEARCH, INFO, FILENAME, TYPE, COUNTBLANK, N, AVEDEV, ARABIC, AVERAGEA, ROT, FIB, BASE, FACTDOUBLE, SQRTPI, QUOTIENT, MULTINOMIAL, COUNTIF Until beta1: ------------ - bug fixes - performance enhancements - support up to 2^15 columns and rows, formerly it was 676 columns and 10000 rows - add support for spell-check - many new built-in functions (see detailed list below) - function name is now case-insensitive - "Related Function" in formula editor - move functions in kspread_interpreter into several kspread_functions_*.cc New functions added: conversion: INT2BOOL, CharToAscii, AsciiToChar, BOOL2STRING, NUM2STRING, BOOL2INT math: DIV, LCD, PRODUCT, LCM, TOGGLE, TRIM, ABS text: COMPARE, CLEAN, SLEEK, PROPER, REPLACE date/time: DAYS, WEEKS, MONTHS, YEARS financial: DB, SLN, SYD, EURO logical: XOR statistical: CHIDIST, FDIST, TDIST, CONFIDENCE, BETADIST, GAMMADIST, PHI, GAUSS, MEDIAN, POISSON, GAMMALN, NORMINV, NORMSINV, WEIBULL, EXPONDIST, NORMDIST, NORMSDIST, FISHER, FISHERINV, LOGNORMDIST diff --git a/kspread/ui/Editors.cpp b/kspread/ui/Editors.cpp index a5a8374ad8d..60cac6bff06 100644 --- a/kspread/ui/Editors.cpp +++ b/kspread/ui/Editors.cpp @@ -1,1861 +1,1947 @@ /* This file is part of the KDE project Copyright 1999-2006 The KSpread Team 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; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Editors.h" // KSpread #include "ApplicationSettings.h" #include "Cell.h" #include "CellStorage.h" #include "CellToolBase.h" #include "Formula.h" #include "Functions.h" #include "Map.h" #include "NamedAreaManager.h" #include "Selection.h" #include "Sheet.h" #include "Style.h" // KOffice #include #include #include // KDE #include #include #include #include #include #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "commands/NamedAreaCommand.h" using namespace KSpread; /***************************************************************************** * * FormulaEditorHighlighter * ****************************************************************************/ namespace KSpread { class FormulaEditorHighlighter::Private { public: Private() { selection = 0; tokens = Tokens(); rangeCount = 0; rangeChanged = false; } // source for cell reference checking Selection* selection; Tokens tokens; uint rangeCount; bool rangeChanged; }; FormulaEditorHighlighter::FormulaEditorHighlighter(QTextEdit* textEdit, Selection* selection) : QSyntaxHighlighter(textEdit) , d(new Private) { d->selection = selection; } FormulaEditorHighlighter::~FormulaEditorHighlighter() { delete d; } const Tokens& FormulaEditorHighlighter::formulaTokens() const { return d->tokens; } void FormulaEditorHighlighter::highlightBlock(const QString& text) { // reset syntax highlighting setFormat(0, text.length(), QApplication::palette().text().color()); // save the old ones to identify range changes Tokens oldTokens = d->tokens; // interpret the text as formula // we accept invalid/incomplete formulas Formula f; d->tokens = f.scan(text); QFont editorFont = document()->defaultFont(); QFont font; uint oldRangeCount = d->rangeCount; d->rangeCount = 0; QList colors = d->selection->colors(); QList alreadyFoundRanges; Sheet *const originSheet = d->selection->originSheet(); Map *const map = originSheet->map(); for (int i = 0; i < d->tokens.count(); ++i) { Token token = d->tokens[i]; Token::Type type = token.type(); switch (type) { case Token::Cell: case Token::Range: { // don't compare, if we have already found a change if (!d->rangeChanged && i < oldTokens.count() && token.text() != oldTokens[i].text()) { d->rangeChanged = true; } const Region newRange(token.text(), map, originSheet); if (!newRange.isValid()) { continue; } int index = alreadyFoundRanges.indexOf(newRange.name()); if (index == -1) /* not found */ { alreadyFoundRanges.append(newRange.name()); index = alreadyFoundRanges.count() - 1; } const QColor color(colors[index % colors.size()]); setFormat(token.pos() + 1, token.text().length(), color); ++d->rangeCount; } break; case Token::Boolean: // True, False (also i18n-ized) /* font = QFont(editorFont); font.setBold(true); setFormat(token.pos() + 1, token.text().length(), font);*/ break; case Token::Identifier: // function name or named area*/ /* font = QFont(editorFont); font.setBold(true); setFormat(token.pos() + 1, token.text().length(), font);*/ break; case Token::Unknown: case Token::Integer: // 14, 3, 1977 case Token::Float: // 3.141592, 1e10, 5.9e-7 case Token::String: // "KOffice", "The quick brown fox..." case Token::Error: break; case Token::Operator: { // +, *, /, - switch (token.asOperator()) { case Token::LeftPar: case Token::RightPar: //Check where this brace is in relation to the cursor and highlight it if necessary. handleBrace(i); break; default: break; } } break; } } if (oldRangeCount != d->rangeCount) d->rangeChanged = true; } void FormulaEditorHighlighter::handleBrace(uint index) { const Token& token = d->tokens.at(index); QTextEdit* textEdit = qobject_cast(parent()); Q_ASSERT(textEdit); int cursorPos = textEdit->textCursor().position(); int distance = cursorPos - token.pos(); int opType = token.asOperator(); bool highlightBrace = false; //Check where the cursor is in relation to this left or right parenthesis token. //Only one pair of braces should be highlighted at a time, and if the cursor //is between two braces, the inner-most pair should be highlighted. if (opType == Token::LeftPar) { //If cursor is directly to the left of this left brace, highlight it if (distance == 1) highlightBrace = true; else //Cursor is directly to the right of this left brace, highlight it unless //there is another left brace to the right (in which case that should be highlighted instead as it //is the inner-most brace) if (distance == 2) if ((index == (uint)d->tokens.count() - 1) || (d->tokens.at(index + 1).asOperator() != Token::LeftPar)) highlightBrace = true; } else { //If cursor is directly to the right of this right brace, highlight it if (distance == 2) highlightBrace = true; else //Cursor is directly to the left of this right brace, so highlight it unless //there is another right brace to the left (in which case that should be highlighted instead as it //is the inner-most brace) if (distance == 1) if ((index == 0) || (d->tokens.at(index - 1).asOperator() != Token::RightPar)) highlightBrace = true; } if (highlightBrace) { QFont font = QFont(document()->defaultFont()); font.setBold(true); setFormat(token.pos() + 1, token.text().length(), font); int matching = findMatchingBrace(index); if (matching != -1) { Token matchingBrace = d->tokens.at(matching); setFormat(matchingBrace.pos() + 1 , matchingBrace.text().length() , font); } } } int FormulaEditorHighlighter::findMatchingBrace(int pos) { int depth = 0; int step = 0; Tokens tokens = d->tokens; //If this is a left brace we need to step forwards through the text to find the matching right brace, //otherwise, it is a right brace so we need to step backwards through the text to find the matching left //brace. if (tokens.at(pos).asOperator() == Token::LeftPar) step = 1; else step = -1; for (int index = pos ; (index >= 0) && (index < (int) tokens.count()) ; index += step) { if (tokens.at(index).asOperator() == Token::LeftPar) depth++; if (tokens.at(index).asOperator() == Token::RightPar) depth--; if (depth == 0) { return index; } } return -1; } uint FormulaEditorHighlighter::rangeCount() const { return d->rangeCount; } bool FormulaEditorHighlighter::rangeChanged() const { return d->rangeChanged; } void FormulaEditorHighlighter::resetRangeChanged() { d->rangeChanged = false; } } // namespace KSpread /***************************************************************************** * * FunctionCompletion * ****************************************************************************/ class FunctionCompletion::Private { public: CellEditor* editor; QFrame *completionPopup; KListWidget *completionListBox; QLabel* hintLabel; }; FunctionCompletion::FunctionCompletion(CellEditor* editor) : QObject(editor) , d(new Private) { d->editor = editor; d->hintLabel = 0; d->completionPopup = new QFrame(editor->topLevelWidget(), Qt::Popup); d->completionPopup->setFrameStyle(QFrame::Box | QFrame::Plain); d->completionPopup->setLineWidth(1); d->completionPopup->installEventFilter(this); d->completionPopup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); QVBoxLayout *layout = new QVBoxLayout(d->completionPopup); layout->setMargin(0); layout->setSpacing(0); d->completionListBox = new KListWidget(d->completionPopup); d->completionPopup->setFocusProxy(d->completionListBox); d->completionListBox->setFrameStyle(QFrame::NoFrame); // d->completionListBox->setVariableWidth( true ); d->completionListBox->installEventFilter(this); connect(d->completionListBox, SIGNAL(currentRowChanged(int)), SLOT(itemSelected())); // When items are activated on single click, also change the help page on mouse-over, otherwise there is no (easy) way to get // the help (with the mouse) without inserting the function if (d->completionListBox->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, d->completionListBox)) { connect(d->completionListBox, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(itemSelected(QListWidgetItem*))); d->completionListBox->setMouseTracking(true); } connect(d->completionListBox, SIGNAL(itemActivated(QListWidgetItem*)), this, SLOT(doneCompletion())); layout->addWidget(d->completionListBox); d->hintLabel = new QLabel(0, Qt::FramelessWindowHint | Qt::Tool | Qt::X11BypassWindowManagerHint); d->hintLabel->setFrameStyle(QFrame::Plain | QFrame::Box); d->hintLabel->setPalette(QToolTip::palette()); d->hintLabel->setWordWrap(true); d->hintLabel->hide(); } FunctionCompletion::~FunctionCompletion() { delete d->hintLabel; delete d; } void FunctionCompletion::itemSelected(QListWidgetItem* listItem) { QString item; if (listItem) { item = listItem->text(); } else { listItem = d->completionListBox->currentItem(); if (listItem) { item = listItem->text(); } } KSpread::FunctionDescription* desc; desc = KSpread::FunctionRepository::self()->functionInfo(item); if (!desc) { d->hintLabel->hide(); return; } const QStringList helpTexts = desc->helpText(); QString helpText = helpTexts.isEmpty() ? QString() : helpTexts.first(); if (helpText.isEmpty()) { d->hintLabel->hide(); return; } helpText.append("").prepend(""); d->hintLabel->setText(helpText); d->hintLabel->adjustSize(); // reposition nicely QPoint pos = d->editor->mapToGlobal(QPoint(d->editor->width(), 0)); pos.setY(pos.y() - d->hintLabel->height() - 1); d->hintLabel->move(pos); d->hintLabel->show(); d->hintLabel->raise(); // do not show it forever //QTimer::singleShot( 5000, d->hintLabel, SLOT( hide()) ); } bool FunctionCompletion::eventFilter(QObject *obj, QEvent *ev) { if (obj != d->completionPopup && obj != d->completionListBox) return false; if (ev->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(ev); switch (ke->key()) { case Qt::Key_Enter: case Qt::Key_Return: doneCompletion(); return true; case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Home: case Qt::Key_End: case Qt::Key_PageUp: case Qt::Key_PageDown: return false; default: d->hintLabel->hide(); d->completionPopup->close(); d->editor->setFocus(); QApplication::sendEvent(d->editor, ev); return true; } } if (ev->type() == QEvent::Close) { d->hintLabel->hide(); } if (ev->type() == QEvent::MouseButtonDblClick) { doneCompletion(); return true; } return false; } void FunctionCompletion::doneCompletion() { d->hintLabel->hide(); d->completionPopup->close(); d->editor->setFocus(); emit selectedCompletion(d->completionListBox->currentItem()->text()); } void FunctionCompletion::showCompletion(const QStringList &choices) { if (!choices.count()) return; d->completionListBox->clear(); d->completionListBox->addItems(choices); d->completionListBox->setCurrentItem(0); // size of the pop-up d->completionPopup->setMaximumHeight(100); d->completionPopup->resize(d->completionListBox->sizeHint() + QSize(d->completionListBox->verticalScrollBar()->width() + 4, d->completionListBox->horizontalScrollBar()->height() + 4)); int h = d->completionListBox->height(); int w = d->completionListBox->width(); QPoint pos = d->editor->globalCursorPosition(); // if popup is partially invisible, move to other position // FIXME check it if it works in Xinerama multihead int screen_num = QApplication::desktop()->screenNumber(d->completionPopup); QRect screen = QApplication::desktop()->screenGeometry(screen_num); if (pos.y() + h > screen.y() + screen.height()) pos.setY(pos.y() - h - d->editor->height()); if (pos.x() + w > screen.x() + screen.width()) pos.setX(screen.x() + screen.width() - w); d->completionPopup->move(pos); d->completionListBox->setFocus(); d->completionPopup->show(); } /**************************************************************************** * * CellEditor * ****************************************************************************/ class CellEditor::Private { public: CellToolBase* cellTool; Selection* selection; KTextEdit* textEdit; FormulaEditorHighlighter* highlighter; FunctionCompletion* functionCompletion; QTimer* functionCompletionTimer; QPoint globalCursorPos; bool captureAllKeyEvents : 1; bool selectionChangedLocked : 1; int currentToken; public: void updateActiveSubRegion(const Tokens &tokens); void rebuildSelection(); }; void CellEditor::Private::updateActiveSubRegion(const Tokens &tokens) { // Index of the token, at which the text cursor is positioned. // For sub-regions it is the start range. currentToken = 0; if (tokens.isEmpty()) { selection->setActiveSubRegion(0, 0); // also set the active element return; } const int cursorPosition = textEdit->textCursor().position() - 1; // without '=' kDebug() << "cursorPosition:" << cursorPosition << "textLength:" << textEdit->toPlainText().length() - 1; uint rangeCounter = 0; // counts the ranges in the sub-region uint currentRange = 0; // range index denoting the current range int regionStart = 0; // range index denoting the sub-region start uint regionEnd = 0; // range index denoting the sub-region end enum { Anywhere, InRegion, BeyondCursor } state = Anywhere; Token token; Token::Type type; // Search the current range the text cursor is positioned to. // Determine the subregion start and end, in which the range is located. for (int i = 0; i < tokens.count(); ++i) { token = tokens[i]; type = token.type(); // If not in a subregion, we may already quit the loop here. if (state == Anywhere) { // Already beyond the cursor position? if (token.pos() > cursorPosition) { state = BeyondCursor; break; // for loop } } else if (state == InRegion) { // Loop to the end of the subregion. if (type == Token::Cell || type == Token::Range) { regionEnd = rangeCounter++; continue; // keep going until the referenced region ends } if (type == Token::Operator) { if (tokens[i].asOperator() == Token::Semicolon) { continue; // keep going until the referenced region ends } } state = Anywhere; continue; } // Can the token be replaced by a reference? switch (type) { case Token::Cell: case Token::Range: if (state == Anywhere) { currentToken = i; regionStart = rangeCounter; state = InRegion; } regionEnd = rangeCounter; // length = 1 currentRange = ++rangeCounter; // point behind the last continue; case Token::Unknown: case Token::Boolean: case Token::Integer: case Token::Float: case Token::String: case Token::Error: // Set the active sub-region start to the next range but // with a length of 0, which results in inserting a new range // to the selection on calling Selection::initialize() or // Selection::update(). currentToken = i; regionStart = rangeCounter; // position of the next range regionEnd = rangeCounter - 1; // length = 0 currentRange = rangeCounter; continue; case Token::Operator: case Token::Identifier: continue; } } // Cursor not reached? I.e. the cursor is placed at the last token's end. if (state == Anywhere) { token = tokens.last(); type = token.type(); // Check the last token. // It was processed, but maybe a reference can be placed behind it. // Check, if the token can be replaced by a reference. switch (type) { case Token::Operator: // Possible to place a reference behind the operator? switch (token.asOperator()) { case Token::Plus: case Token::Minus: case Token::Asterisk: case Token::Slash: case Token::Caret: case Token::LeftPar: case Token::Semicolon: case Token::Equal: case Token::NotEqual: case Token::Less: case Token::Greater: case Token::LessEqual: case Token::GreaterEqual: // Append new references by pointing behind the last. currentToken = tokens.count(); regionStart = rangeCounter; regionEnd = rangeCounter - 1; // length = 0 currentRange = rangeCounter; break; case Token::InvalidOp: case Token::RightPar: case Token::Comma: case Token::Ampersand: case Token::Percent: case Token::CurlyBra: case Token::CurlyKet: case Token::Pipe: // reference cannot be placed behind break; } break; case Token::Unknown: case Token::Boolean: case Token::Integer: case Token::Float: case Token::String: case Token::Identifier: case Token::Error: // currentToken = tokens.count() - 1; // already set // Set the active sub-region start to the end of the selection // with a length of 0, which results in appending a new range // to the selection on calling Selection::initialize() or // Selection::update(). regionStart = rangeCounter; regionEnd = rangeCounter - 1; // length = 0 currentRange = rangeCounter; break; case Token::Cell: case Token::Range: // currentToken = tokens.count() - 1; // already set // Set the last range as active one. It is not a sub-region, // otherwise the state would have been InRegion. regionStart = rangeCounter - 1; regionEnd = rangeCounter - 1; // length = 1 currentRange = rangeCounter; // point behind the last break; } } const int regionLength = regionEnd - regionStart + 1; kDebug() << "currentRange:" << currentRange << "regionStart:" << regionStart << "regionEnd:" << regionEnd << "regionLength:" << regionLength; selection->setActiveSubRegion(regionStart, regionLength, currentRange); } CellEditor::CellEditor(CellToolBase *cellTool, QWidget* parent) : KTextEdit(parent) , d(new Private) { d->cellTool = cellTool; d->selection = cellTool->selection(); d->textEdit = this; d->globalCursorPos = QPoint(); d->captureAllKeyEvents = d->selection->activeSheet()->map()->settings()->captureAllArrowKeys(); d->selectionChangedLocked = false; d->currentToken = 0; setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setFrameStyle(QFrame::NoFrame); setLineWidth(0); document()->setDocumentMargin(0); // setMinimumHeight(fontMetrics().height()); d->highlighter = new FormulaEditorHighlighter(this, d->selection); d->functionCompletion = new FunctionCompletion(this); d->functionCompletionTimer = new QTimer(this); connect(d->functionCompletion, SIGNAL(selectedCompletion(const QString&)), SLOT(functionAutoComplete(const QString&))); connect( this, SIGNAL( textChanged() ), SLOT( checkFunctionAutoComplete() ) ); connect(d->functionCompletionTimer, SIGNAL(timeout()), SLOT(triggerFunctionAutoComplete())); const Cell cell(d->selection->activeSheet(), d->selection->marker()); const bool wrapText = cell.style().wrapText(); d->textEdit->setWordWrapMode(wrapText ? QTextOption::WordWrap: QTextOption::NoWrap); #if 0 // FIXME Implement a completion aware KTextEdit. setCompletionMode(selection()->view()->doc()->completionMode()); setCompletionObject(&selection()->view()->doc()->map()->stringCompletion(), true); #endif connect( this, SIGNAL( cursorPositionChanged() ), this, SLOT (slotCursorPositionChanged())); connect( this, SIGNAL( textChanged() ), this, SLOT( slotTextChanged() ) ); // connect( this, SIGNAL(completionModeChanged( KGlobalSettings::Completion )),this,SLOT (slotCompletionModeChanged(KGlobalSettings::Completion))); } CellEditor::~CellEditor() { if (selection()) selection()->endReferenceSelection(); delete d; } Selection* CellEditor::selection() const { return d->selection; } QPoint CellEditor::globalCursorPosition() const { return d->globalCursorPos; } void CellEditor::checkFunctionAutoComplete() { // Nothing to do, if no focus or not in reference selection mode. if (!hasFocus() || !selection()->referenceSelection()) { return; } d->functionCompletionTimer->stop(); d->functionCompletionTimer->setSingleShot(true); d->functionCompletionTimer->start(2000); } void CellEditor::triggerFunctionAutoComplete() { // tokenize the expression (don't worry, this is very fast) int curPos = textCursor().position(); QString subtext = toPlainText().left( curPos ); KSpread::Formula f; KSpread::Tokens tokens = f.scan(subtext); if (!tokens.valid()) return; if (tokens.count() < 1) return; KSpread::Token lastToken = tokens[ tokens.count()-1 ]; // last token must be an identifier if (!lastToken.isIdentifier()) return; QString id = lastToken.text(); if (id.length() < 1) return; // find matches in function names QStringList fnames = KSpread::FunctionRepository::self()->functionNames(); QStringList choices; for (int i = 0; i < fnames.count(); i++) if (fnames[i].startsWith(id, Qt::CaseInsensitive)) choices.append(fnames[i]); choices.sort(); // no match, don't bother with completion if (!choices.count()) return; // single perfect match, no need to give choices if (choices.count() == 1) if (choices[0].toLower() == id.toLower()) return; // present the user with completion choices d->functionCompletion->showCompletion(choices); } void CellEditor::functionAutoComplete(const QString& item) { if (item.isEmpty()) return; QTextCursor textCursor = this->textCursor(); int curPos = textCursor.position(); QString subtext = toPlainText().left( curPos ); KSpread::Formula f; KSpread::Tokens tokens = f.scan(subtext); if (!tokens.valid()) return; if (tokens.count() < 1) return; KSpread::Token lastToken = tokens[ tokens.count()-1 ]; if (!lastToken.isIdentifier()) return; blockSignals( true ); // Select the incomplete function name in order to replace it. textCursor.setPosition(lastToken.pos() + 1); textCursor.setPosition(lastToken.pos() + lastToken.text().length() + 1, QTextCursor::KeepAnchor); setTextCursor( textCursor ); blockSignals( false ); // Replace the incomplete function name with the selected one. insertPlainText(item); } void CellEditor::slotCursorPositionChanged() { // Nothing to do, if not in reference selection mode. if (!selection()->referenceSelection()) { return; } // NOTE On text changes KTextEdit::cursorPositionChanged() is triggered // before KTextEdit::textChanged(). The text is already up-to-date. // Save the global position for the function auto-completion popup. d->globalCursorPos = mapToGlobal(cursorRect().bottomLeft()); // Needs up-to-date tokens; QSyntaxHighlighter::rehighlight() gets called // automatically on text changes, which does the update. d->updateActiveSubRegion(d->highlighter->formulaTokens()); } void CellEditor::Private::rebuildSelection() { // Do not react on selection changes, that update the formula's expression, // because the selection gets already build based on the current formula. selectionChangedLocked = true; Sheet *const originSheet = selection->originSheet(); Map *const map = originSheet->map(); // Rebuild the reference selection by using the formula tokens. Tokens tokens = highlighter->formulaTokens(); selection->update(); // set the old cursor dirty; updates the editors selection->clear(); //A list of regions which have already been highlighted on the spreadsheet. //This is so that we don't end up highlighting the same region twice in two different //colors. QSet alreadyUsedRegions; int counter = 0; for (int i = 0; i < tokens.count(); ++i) { const Token token = tokens[i]; const Token::Type type = token.type(); if (type == Token::Cell || type == Token::Range) { const Region region(token.text(), map, originSheet); if (!region.isValid() || region.isEmpty()) { continue; } if (alreadyUsedRegions.contains(region.name())) { continue; } alreadyUsedRegions.insert(region.name()); const QRect range = region.firstRange(); Sheet *const sheet = region.firstSheet(); selection->initialize(range, sheet); // Always append the next range by pointing behind the last item. selection->setActiveSubRegion(++counter, 0); } } // Set the active sub-region. // Needs up-to-date tokens; QSyntaxHighlighter::rehighlight() gets called // automatically on text changes, which does the update. updateActiveSubRegion(highlighter->formulaTokens()); selectionChangedLocked = false; } void CellEditor::setEditorFont(QFont const & font, bool updateSize, const KoViewConverter *viewConverter) { const qreal scaleY = POINT_TO_INCH(static_cast((KoDpi::dpiY()))); setFont(QFont(font.family(), viewConverter->documentToViewY(font.pointSizeF()) / scaleY)); if (updateSize) { QFontMetrics fontMetrics(this->font()); int width = fontMetrics.width(toPlainText()) + fontMetrics.averageCharWidth(); // don't make it smaller: then we would have to repaint the obscured cells if (width < this->width()) width = this->width(); int height = fontMetrics.height(); if (height < this->height()) height = this->height(); setGeometry(x(), y(), width, height); } } void CellEditor::slotCompletionModeChanged(KGlobalSettings::Completion _completion) { selection()->activeSheet()->map()->settings()->setCompletionMode(_completion); } void CellEditor::slotTextChanged() { // NOTE On text changes KTextEdit::cursorPositionChanged() is triggered // before KTextEdit::textChanged(). // Fix the position. verticalScrollBar()->setValue(1); const QString text = toPlainText(); const QFontMetricsF fontMetrics(font()); // TODO Adjust size depending on which cells can be obscured (see CellView)? // The following line would result in an unchanged width for cells with // enabled word wrapping, but after the user input got applied it may // obscure cells horizontally. // Passing no flags will only change the height, if a manual line break is // entered (Shift + Return). // const int flags = wordWrapMode() == QTextOption::NoWrap ? 0 : Qt::TextWordWrap; const QRectF rect = fontMetrics.boundingRect(this->rect(), 0, text); const int requiredWidth = rect.width(); const int requiredHeight = rect.height() - 1; // -1 to fit into a default cell if (text.isRightToLeft()) { setGeometry(x() - requiredWidth + width(), y(), requiredWidth, requiredHeight); } else { setGeometry(x(), y(), requiredWidth, requiredHeight); } // FIXME Stefan: Is this really wanted? The percent char does not get // removed on applying the user input. If the style changes afterwards, // the user input is still indicating a percent value. If the digit gets // deleted while editing the percent char also stays. Disabling for now. #if 0 // KSPREAD_WIP_EDITOR_OVERHAUL const Cell cell(d->selection->activeSheet(), d->selection->marker()); if ((cell.style().formatType()) == Format::Percentage) { if ((text.length() == 1) && text[0].isDigit()) { setPlainText(text + " %"); setCursorPosition(1); return; } } #endif // KSPREAD_WIP_EDITOR_OVERHAUL // update the external editor, but only if we have focus if (hasFocus()) { emit textChanged(text); } // Enable/disable the reference selection. if (!text.isEmpty() && text[0] == '=') { selection()->startReferenceSelection(); } else { selection()->endReferenceSelection(); return; } // The expression highlighting got updated automatically. // If a reference has changed since the last update ... if (d->highlighter->rangeChanged()) { // Reset the flag, that indicates range changes after text changes. d->highlighter->resetRangeChanged(); // Rebuild the reference selection by using the formula tokens. d->rebuildSelection(); } } // Called on selection (and sheet) changes. void CellEditor::selectionChanged() { if (d->selectionChangedLocked) { return; } Selection* choice = selection(); if (choice->isEmpty()) return; const QString text = toPlainText(); const int textLength = text.length(); // Find the start text cursor position for the active sub-region within // the formula's expression and determine the length of the sub-region. Tokens tokens = d->highlighter->formulaTokens(); uint start = 1; uint length = 0; if (!tokens.empty()) { if (d->currentToken < tokens.count()) { Token token = tokens[d->currentToken]; Token::Type type = token.type(); if (type == Token::Cell || type == Token::Range) { start = token.pos() + 1; // don't forget the '='! length = token.text().length(); // Iterate to the end of the sub-region. for (int i = d->currentToken + 1; i < tokens.count(); ++i) { token = tokens[i]; type = token.type(); switch (type) { case Token::Cell: case Token::Range: length += token.text().length(); continue; case Token::Operator: if (token.asOperator() == Token::Semicolon) { ++length; continue; } default: break; } break; } start = token.pos() + 1; // don't forget the '='! length = token.text().length(); } } else { // sanitize d->currentToken = tokens.count(); start = textLength; } } // Replace the formula's active sub-region with the selection's one. const QString address = choice->activeSubRegionName(); const QString newExpression = QString(text).replace(start, length, address); // The expression highlighting gets updated automatically by the next call, // even though signals are blocked (must be connected to QTextDocument). blockSignals(true); setText(newExpression, start + address.length()); blockSignals(false); // Ranges have changed. // Reset the flag, that indicates range changes after text changes. d->highlighter->resetRangeChanged(); // Mirror the behaviour of slotCursorPositionChanged(), but here the tokens // are already up-to-date. d->globalCursorPos = mapToGlobal(cursorRect().bottomLeft()); // Set the active sub-region. // Needs up-to-date tokens; QSyntaxHighlighter::rehighlight() gets called // automatically on text changes, which does the update. d->updateActiveSubRegion(d->highlighter->formulaTokens()); // Always emit, because this editor may be hidden or does not have focus, // but the external one needs an update. emit textChanged(toPlainText()); } void CellEditor::keyPressEvent(QKeyEvent *event) { - if (event->key() == Qt::Key_F4) { - QRegExp exp("(\\$?)([a-zA-Z]+)(\\$?)([0-9]+)$"); - - int cur = textCursor().position(); - QString tmp, tmp2; - int n = -1; - - // this is ugly, and sort of hack - // FIXME rewrite to use the real Tokenizer - unsigned i; - for (i = 0; i < 10; i++) { - tmp = toPlainText().left( cur+i ); - tmp2 = toPlainText().right( toPlainText().length() - cur - i ); - - n = exp.indexIn(tmp); - if (n >= 0) break; - } - - if (n == -1) return; - - QString newPart; - if ((exp.cap(1) == "$") && (exp.cap(3) == "$")) - newPart = '$' + exp.cap(2) + exp.cap(4); - else if ((exp.cap(1) != "$") && (exp.cap(3) != "$")) - newPart = '$' + exp.cap(2) + '$' + exp.cap(4); - else if ((exp.cap(1) == "$") && (exp.cap(3) != "$")) - newPart = exp.cap(2) + '$' + exp.cap(4); - else if ((exp.cap(1) != "$") && (exp.cap(3) == "$")) - newPart = exp.cap(2) + exp.cap(4); - - QString newString = tmp.left(n); - newString += newPart; - cur = newString.length() - i; - newString += tmp2; - - setPlainText(newString); - setCursorPosition(cur); - - event->accept(); // QKeyEvent - return; - } switch (event->key()) { + case Qt::Key_F4: + if (!d->selection->referenceSelection()) { + return; // do NOT pass to KTextEdit + } + permuteFixation(); + event->accept(); + return; case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_PageDown: case Qt::Key_PageUp: case Qt::Key_Tab: case Qt::Key_Backtab: // Forward left/right arrows to parent, so that pressing left/right // in this editor leaves editing mode, unless this editor has been // set to capture arrow key events. if (d->captureAllKeyEvents) { break; // pass to KTextEdit } event->ignore(); // pass to parent return; case Qt::Key_Return: case Qt::Key_Enter: // Shift + Return: manual line wrap if (event->modifiers() & Qt::ShiftModifier) { break; // pass to KTextEdit } event->ignore(); // pass to parent return; } KTextEdit::keyPressEvent(event); } void CellEditor::focusInEvent(QFocusEvent *event) { // If the focussing is user induced. if (event->reason() != Qt::OtherFocusReason) { kDebug() << "induced by user"; d->cellTool->setLastEditorWithFocus(CellToolBase::EmbeddedEditor); } KTextEdit::focusInEvent(event); } void CellEditor::focusOutEvent(QFocusEvent *event) { KTextEdit::focusOutEvent(event); } void CellEditor::setText(const QString& text, int cursorPos) { if (text == toPlainText()) return; setPlainText(text); // Only update the cursor position, if a non-negative value was set. // The default parameter value is -1, i.e. the cursor does not get touched. if (cursorPos >= 0) { if (cursorPos > text.length()) { // Usability: It is usually more convenient, if the cursor is // positioned at the end of the text so it can be quickly deleted // using the backspace key. cursorPos = text.length(); } setCursorPosition(cursorPos); } } +// helper for CellEditor::permuteFixation() +QString permuteLocationFixation(const QString ®ionName, int &i, + bool columnFixed, bool rowFixed) +{ + QString result; + if (columnFixed) { + result += '$'; + } + // copy the column letter(s) + while (i < regionName.count()) { + if (!regionName[i].isLetter()) { + if (regionName[i] == '$') { + // swallow the old fixation + ++i; + continue; + } + // stop, if not a column letter + break; + } + result += regionName[i++]; + } + if (rowFixed) { + result += '$'; + } + // copy the row number(s) + while (i < regionName.count()) { + if (!regionName[i].isNumber()) { + if (regionName[i] == '$') { + // swallow the old fixation + ++i; + continue; + } + // stop, if not a row number + break; + } + result += regionName[i++]; + } + return result; +} + +void CellEditor::permuteFixation() +{ + // Search for the last range before or the range at the cursor. + int index = -1; + const int cursorPosition = textCursor().position() - 1; // - '=' + const Tokens tokens = d->highlighter->formulaTokens(); + for (int i = 0; i < tokens.count(); ++i) { + const Token token = tokens[i]; + if (token.pos() > cursorPosition) { + break; // for loop + } + if (token.type() == Token::Cell || token.type() == Token::Range) { + index = i; + } + } + // Quit, if no range was found. + if (index == -1) { + return; + } + + const Token token = tokens[index]; + Map *const map = d->selection->activeSheet()->map(); + QString regionName = token.text(); + // Filter sheet; truncates regionName; range without sheet name resides. + Sheet *const sheet = Region(QString(), map).filterSheetName(regionName); + const Region region(regionName, map, 0); + // TODO Stefan: Skip named areas. + if (!region.isValid()) { + return; + } + // FIXME Stefan: need access to fixation, thus to Region::Range; must use iterator + Region::Element *range = (*region.constBegin()); + QString result(sheet ? (sheet->sheetName() + '!') : QString()); + // Permute fixation. + if (region.isSingular()) { + char fixation = 0x00; + if (range->isRowFixed()) { + fixation += 0x01; + } + if (range->isColumnFixed()) { + fixation += 0x02; + } + fixation += 0x01; + + int i = 0; + result += permuteLocationFixation(regionName, i, fixation & 0x02, fixation & 0x01); + } else { + char fixation = 0x00; + if (range->isBottomFixed()) { + fixation += 0x01; + } + if (range->isRightFixed()) { + fixation += 0x02; + } + if (range->isTopFixed()) { + fixation += 0x04; + } + if (range->isLeftFixed()) { + fixation += 0x08; + } + fixation += 0x01; + + int i = 0; + result += permuteLocationFixation(regionName, i, fixation & 0x08, fixation & 0x04); + Q_ASSERT(regionName[i] == ':'); + ++i; + result += ':'; + result += permuteLocationFixation(regionName, i, fixation & 0x02, fixation & 0x01); + } + // Replace the range in the formula's expression. + QString text = toPlainText(); + const int start = token.pos() + 1; // + '=' + const int length = token.text().length(); + setPlainText(text.replace(start, length, result)); + // Set the cursor to the end of the range. + QTextCursor textCursor = this->textCursor(); + textCursor.setPosition(start + result.length()); + setTextCursor(textCursor); +} + int CellEditor::cursorPosition() const { return textCursor().position(); } void CellEditor::setCursorPosition(int pos) { QTextCursor textCursor( this->textCursor() ); textCursor.setPosition(pos); setTextCursor( textCursor ); } // Called by the cell tool when setting the active element with a cell location. void CellEditor::setActiveSubRegion(int index) { index = qBound(0, index, (int)d->highlighter->rangeCount()); int counter = 0; bool subRegion = false; const Tokens tokens = d->highlighter->formulaTokens(); for (int i = 0; i < tokens.count(); ++i) { const Token token = tokens[i]; switch (token.type()) { case Token::Cell: case Token::Range: if (!subRegion) { d->currentToken = i; subRegion = true; } if (counter == index) { setCursorPosition(token.pos() + token.text().length() + 1); return; } ++counter; continue; case Token::Operator: if (token.asOperator() == Token::Semicolon) { if (subRegion) { continue; } } subRegion = false; continue; default: subRegion = false; continue; } } } /***************************************************************************** * * LocationComboBox * ****************************************************************************/ LocationComboBox::LocationComboBox(CellToolBase *cellTool, QWidget *_parent) : KComboBox(true, _parent) , m_cellTool(cellTool) { setCompletionObject(&completionList, true); setCompletionMode(KGlobalSettings::CompletionAuto); insertItem(0, QString()); updateAddress(); Map *const map = cellTool->selection()->activeSheet()->map(); const QList areaNames = map->namedAreaManager()->areaNames(); for (int i = 0; i < areaNames.count(); ++i) slotAddAreaName(areaNames[i]); connect(this, SIGNAL(activated(const QString &)), this, SLOT(slotActivateItem())); } void LocationComboBox::updateAddress() { QString address; Selection *const selection = m_cellTool->selection(); const QList< QPair > names = selection->activeSheet()->cellStorage()->namedAreas(*selection); { QRect range; if (selection->isSingular()) range = QRect(selection->marker(), QSize(1, 1)); else range = selection->lastRange(); for (int i = 0; i < names.size(); i++) { if (names[i].first.toRect() == range) { address = names[i].second; } } } if (selection->activeSheet()->getLcMode()) { if (selection->isSingular()) { address = 'L' + QString::number(selection->marker().y()) + 'C' + QString::number(selection->marker().x()); } else { const QRect lastRange = selection->lastRange(); address = QString::number(lastRange.height()) + "Lx"; address += QString::number(lastRange.width()) + 'C'; } } else { address = selection->name(); } setItemText(0, address); setCurrentItem(0); lineEdit()->setText(address); } void LocationComboBox::slotAddAreaName(const QString &_name) { insertItem(count(), _name); addCompletionItem(_name); } void LocationComboBox::slotRemoveAreaName(const QString &_name) { for (int i = 0; i < count(); i++) { if (itemText(i) == _name) { removeItem(i); break; } } removeCompletionItem(_name); } void LocationComboBox::addCompletionItem( const QString &_item ) { if (completionList.items().contains(_item) == 0) { completionList.addItem(_item); kDebug(36005) << _item; } } void LocationComboBox::removeCompletionItem( const QString &_item ) { completionList.removeItem(_item); } void LocationComboBox::slotActivateItem() { if (activateItem()) { m_cellTool->scrollToCell(m_cellTool->selection()->cursor()); } } bool LocationComboBox::activateItem() { Selection *const selection = m_cellTool->selection(); // Set the focus back on the canvas. parentWidget()->setFocus(); const QString text = lineEdit()->text(); // check whether an already existing named area was entered Region region = selection->activeSheet()->map()->namedAreaManager()->namedArea(text); if (region.isValid()) { // TODO Stefan: Merge the sheet change into Selection. if (region.firstSheet() != selection->activeSheet()) { selection->emitVisibleSheetRequested(region.firstSheet()); } selection->initialize(region); return true; } // check whether a valid cell region was entered region = Region(text, selection->activeSheet()->map(), selection->activeSheet()); if (region.isValid()) { // TODO Stefan: Merge the sheet change into Selection. if (region.firstSheet() != selection->activeSheet()) { selection->emitVisibleSheetRequested(region.firstSheet()); } selection->initialize(region); return true; } // A name for an area entered? // FIXME Stefan: allow all characters bool validName = true; for (int i = 0; i < text.length(); ++i) { if (!text[i].isLetter()) { validName = false; break; } } if (validName) { NamedAreaCommand* command = new NamedAreaCommand(); command->setSheet(selection->activeSheet()); command->setAreaName(text); command->add(Region(selection->lastRange(), selection->activeSheet())); if (command->execute()) return true; else delete command; } return false; } void LocationComboBox::keyPressEvent( QKeyEvent * _ev ) { Selection *const selection = m_cellTool->selection(); // Do not handle special keys and accelerators. This is // done by KComboBox. if (_ev->modifiers() & (Qt::AltModifier | Qt::ControlModifier)) { KComboBox::keyPressEvent(_ev); // Never allow that keys are passed on to the parent. _ev->accept(); // QKeyEvent return; } // Handle some special keys here. Eve switch (_ev->key()) { case Qt::Key_Return: case Qt::Key_Enter: { if (activateItem()) { m_cellTool->scrollToCell(selection->cursor()); return; } _ev->accept(); // QKeyEvent } break; // Escape pressed, restore original value case Qt::Key_Escape: updateAddress(); parentWidget()->setFocus(); _ev->accept(); // QKeyEvent break; default: KComboBox::keyPressEvent(_ev); // Never allow that keys are passed on to the parent. _ev->accept(); // QKeyEvent } } /**************************************************************** * * ExternalEditor * The editor that appears in the tool option widget and allows * to edit the cell's content. * ****************************************************************/ class ExternalEditor::Private { public: CellToolBase* cellTool; FormulaEditorHighlighter* highlighter; bool isArray; }; ExternalEditor::ExternalEditor(QWidget *parent) : KTextEdit(parent) , d(new Private) { d->cellTool = 0; d->highlighter = 0; d->isArray = false; setCurrentFont(KGlobalSettings::generalFont()); // Try to immitate KLineEdit regarding the margins and size. document()->setDocumentMargin(1); setMinimumHeight(fontMetrics().height() + 2 * frameWidth() + 1); connect(this, SIGNAL(textChanged()), this, SLOT(slotTextChanged())); connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged())); } ExternalEditor::~ExternalEditor() { delete d->highlighter; delete d; } QSize ExternalEditor::sizeHint() const { return KTextEdit::sizeHint(); // document()->size().toSize(); } void ExternalEditor::setCellTool(CellToolBase* cellTool) { d->cellTool = cellTool; d->highlighter = new FormulaEditorHighlighter(this, cellTool->selection()); } void ExternalEditor::applyChanges() { Q_ASSERT(d->cellTool); d->cellTool->deleteEditor(true, d->isArray); // save changes d->isArray = false; } void ExternalEditor::discardChanges() { Q_ASSERT(d->cellTool); clear(); d->cellTool->deleteEditor(false); // discard changes d->cellTool->selection()->update(); } void ExternalEditor::setText(const QString &text) { Q_ASSERT(d->cellTool); if (toPlainText() == text) { return; } // This method is called from the embedded editor. Do not send signals back. blockSignals(true); KTextEdit::setPlainText(text); QTextCursor textCursor = this->textCursor(); textCursor.setPosition(d->cellTool->editor()->cursorPosition()); setTextCursor(textCursor); blockSignals(false); } void ExternalEditor::keyPressEvent(QKeyEvent *event) { Q_ASSERT(d->cellTool); if (!d->cellTool->selection()->activeSheet()->map()->isReadWrite()) { return; } // Create the embedded editor, if necessary. if (!d->cellTool->editor()) { d->cellTool->createEditor(false /* keep content */, false /* no focus */); } // the Enter and Esc key are handled by the embedded editor if ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter) || (event->key() == Qt::Key_Escape)) { d->cellTool->editor()->setFocus(); QApplication::sendEvent(d->cellTool->editor(), event); event->accept(); return; } if (event->key() == Qt::Key_F2) { // Switch the focus back to the embedded editor. d->cellTool->editor()->setFocus(); } // call inherited handler KTextEdit::keyPressEvent(event); } void ExternalEditor::focusInEvent(QFocusEvent* event) { Q_ASSERT(d->cellTool); // If the focussing is user induced. if (event->reason() != Qt::OtherFocusReason) { kDebug() << "induced by user"; d->cellTool->setLastEditorWithFocus(CellToolBase::ExternalEditor); } // when the external editor gets focus, create also the internal editor // this in turn means that ranges will be instantly highlighted right if (!d->cellTool->editor()) d->cellTool->createEditor(false /* keep content */, false /* no focus */); KTextEdit::focusInEvent(event); } void ExternalEditor::focusOutEvent(QFocusEvent* event) { Q_ASSERT(d->cellTool); KTextEdit::focusOutEvent(event); } void ExternalEditor::slotTextChanged() { if (!hasFocus()) return; // only report change if we have focus emit textChanged(toPlainText()); // Update the cursor position again, because this slot is invoked after // slotCursorPositionChanged(). if (d->cellTool->editor()) { d->cellTool->editor()->setCursorPosition(textCursor().position()); } } void ExternalEditor::slotCursorPositionChanged() { if (!hasFocus() || !d->cellTool->editor()) { return; } // Suppress updates, if this slot got invoked by a text change. It is done // later by slotTextChanged(). if (d->cellTool->editor()->toPlainText() == toPlainText()) { d->cellTool->editor()->setCursorPosition(textCursor().position()); } } #if 0 // KSPREAD_DISCARD_FORMULA_BAR /**************************************************************** * * EditWidget * The line-editor that appears above the sheet and allows to * edit the cells content. * ****************************************************************/ EditWidget::EditWidget(QWidget *_parent, Canvas *_canvas, QAbstractButton *cancelButton, QAbstractButton *okButton) : KLineEdit(_parent) { m_pCanvas = _canvas; Q_ASSERT(m_pCanvas != 0); // Those buttons are created by the caller, so that they are inserted // properly in the layout - but they are then managed here. m_pCancelButton = cancelButton; m_pOkButton = okButton; m_isArray = false; installEventFilter(m_pCanvas); if (!m_pCanvas->doc()->isReadWrite() || !m_pCanvas->activeSheet()) setEnabled(false); QObject::connect(m_pCancelButton, SIGNAL(clicked()), this, SLOT(slotAbortEdit())); QObject::connect(m_pOkButton, SIGNAL(clicked()), this, SLOT(slotDoneEdit())); setEditMode(false); // disable buttons setCompletionMode(m_pCanvas->doc()->completionMode()); setCompletionObject(&m_pCanvas->doc()->map()->stringCompletion(), true); } void EditWidget::showEditWidget(bool _show) { if (_show) { m_pCancelButton->show(); m_pOkButton->show(); show(); } else { m_pCancelButton->hide(); m_pOkButton->hide(); hide(); } } void EditWidget::slotAbortEdit() { m_pCanvas->deleteEditor(false /*discard changes*/); // will take care of the buttons } void EditWidget::slotDoneEdit() { m_pCanvas->deleteEditor(true /*keep changes*/, m_isArray); m_isArray = false; // will take care of the buttons } void EditWidget::keyPressEvent(QKeyEvent* _ev) { // Don't handle special keys and accelerators, except Enter ones if (((_ev->modifiers() & (Qt::AltModifier | Qt::ControlModifier)) || (_ev->modifiers() & Qt::ShiftModifier) || (_ev->key() == Qt::Key_Shift) || (_ev->key() == Qt::Key_Control)) && (_ev->key() != Qt::Key_Return) && (_ev->key() != Qt::Key_Enter)) { KLineEdit::keyPressEvent(_ev); _ev->accept(); return; } if (!m_pCanvas->doc()->isReadWrite()) return; if (!m_pCanvas->editor()) { // Start editing the current cell m_pCanvas->createEditor(true /* clear content */, false /* no focus */); } CellEditor * cellEditor = (CellEditor*) m_pCanvas->editor(); switch (_ev->key()) { case Qt::Key_Down: case Qt::Key_Up: case Qt::Key_Return: case Qt::Key_Enter: cellEditor->setText(text()); // Don't allow to start a chooser when pressing the arrow keys // in this widget, since only up and down would work anyway. // This is why we call slotDoneEdit now, instead of sending // to the canvas. //QApplication::sendEvent( m_pCanvas, _ev ); m_isArray = (_ev->modifiers() & Qt::AltModifier) && (_ev->modifiers() & Qt::ControlModifier); slotDoneEdit(); _ev->accept(); break; case Qt::Key_F2: cellEditor->setFocus(); cellEditor->setText(text()); cellEditor->setCursorPosition(cursorPosition()); break; default: KLineEdit::keyPressEvent(_ev); setFocus(); cellEditor->setCheckChoice(false); cellEditor->setText(text()); cellEditor->setCheckChoice(true); cellEditor->setCursorPosition(cursorPosition()); } } void EditWidget::setEditMode(bool mode) { m_pCancelButton->setEnabled(mode); m_pOkButton->setEnabled(mode); } void EditWidget::focusOutEvent(QFocusEvent* ev) { //kDebug(36001) <<"EditWidget lost focus"; // See comment about setLastEditorWithFocus m_pCanvas->setLastEditorWithFocus(Canvas::EditWidget); KLineEdit::focusOutEvent(ev); } void EditWidget::setText(const QString& t) { if (t == text()) // Why this? (David) return; KLineEdit::setText(t); } #endif /***************************************************************************** * * RegionSelector * ****************************************************************************/ class RegionSelector::Private { public: Selection* selection; QDialog* parentDialog; KDialog* dialog; KTextEdit* textEdit; QToolButton* button; FormulaEditorHighlighter* highlighter; DisplayMode displayMode; SelectionMode selectionMode; static RegionSelector* s_focussedSelector; }; RegionSelector* RegionSelector::Private::s_focussedSelector = 0; RegionSelector::RegionSelector(QWidget* parent) : QWidget(parent), d(new Private) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); d->displayMode = Widget; d->parentDialog = 0; d->selection = 0; d->dialog = 0; d->button = new QToolButton(this); d->button->setCheckable(true); d->button->setIcon(KIcon("selection")); d->highlighter = 0; d->textEdit = new KTextEdit(this); d->textEdit->setLineWrapMode(QTextEdit::NoWrap); d->textEdit->setWordWrapMode(QTextOption::NoWrap); d->textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); d->textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->textEdit->setFixedHeight(d->button->height() - 2*d->textEdit->frameWidth()); // FIXME d->textEdit->setTabChangesFocus(true); QHBoxLayout* layout = new QHBoxLayout(this); layout->setMargin(0); layout->setSpacing(2); layout->addWidget(d->textEdit); layout->addWidget(d->button); d->button->installEventFilter(this); d->textEdit->installEventFilter(this); connect(d->button, SIGNAL(toggled(bool)), this, SLOT(switchDisplayMode(bool))); } RegionSelector::~RegionSelector() { d->selection->endReferenceSelection(); d->selection->setSelectionMode(Selection::MultipleCells); delete d; } void RegionSelector::setSelectionMode(SelectionMode mode) { d->selectionMode = mode; // TODO adjust selection } void RegionSelector::setSelection(Selection* selection) { d->selection = selection; d->highlighter = new FormulaEditorHighlighter(d->textEdit, d->selection); connect(d->selection, SIGNAL(changed(const Region&)), this, SLOT(choiceChanged())); } void RegionSelector::setDialog(QDialog* dialog) { d->parentDialog = dialog; } KTextEdit* RegionSelector::textEdit() const { return d->textEdit; } bool RegionSelector::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::Close) { if (object == d->dialog && d->button->isChecked()) { // TODO Stefan: handle as button click // d->button->toggle(); event->ignore(); return true; // eat it } } else if (event->type() == QEvent::FocusIn) { Private::s_focussedSelector = this; d->selection->startReferenceSelection(); if (d->selectionMode == SingleCell) { d->selection->setSelectionMode(Selection::SingleCell); } else { d->selection->setSelectionMode(Selection::MultipleCells); } // TODO Stefan: initialize choice } return QObject::eventFilter(object, event); } void RegionSelector::switchDisplayMode(bool state) { Q_UNUSED(state) kDebug() ; if (d->displayMode == Widget) { d->displayMode = Dialog; d->dialog = new KDialog(d->parentDialog->parentWidget(), Qt::Tool); d->dialog->resize(d->parentDialog->width(), 20); d->dialog->move(d->parentDialog->pos()); d->dialog->setButtons(0); d->dialog->setModal(false); if (d->selectionMode == SingleCell) { d->dialog->setCaption(i18n("Select Single Cell")); } else { // if ( d->selectionMode == MultipleCells ) d->dialog->setCaption(i18n("Select Multiple Cells")); } QWidget* widget = new QWidget(d->dialog); QHBoxLayout* layout = new QHBoxLayout(widget); layout->setMargin(0); layout->setSpacing(0); layout->addWidget(d->textEdit); layout->addWidget(d->button); d->dialog->setMainWidget(widget); d->dialog->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); d->dialog->installEventFilter(this); d->dialog->show(); d->parentDialog->hide(); } else { d->displayMode = Widget; layout()->addWidget(d->textEdit); layout()->addWidget(d->button); d->parentDialog->move(d->dialog->pos()); d->parentDialog->show(); delete d->dialog; d->dialog = 0; } } void RegionSelector::choiceChanged() { if (Private::s_focussedSelector != this) return; if (d->selection->isValid()) { QString area = d->selection->name(); d->textEdit->setPlainText(area); } } #include "Editors.moc" diff --git a/kspread/ui/Editors.h b/kspread/ui/Editors.h index 3c52d30fd49..a89ce458b89 100644 --- a/kspread/ui/Editors.h +++ b/kspread/ui/Editors.h @@ -1,397 +1,403 @@ /* This file is part of the KDE project Copyright 1999-2006 The KSpread Team 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; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __EDITORS_H__ #define __EDITORS_H__ #include #include #include //Added by qt3to4: #include #include #include #include #include #include #include #include #include "kspread_export.h" class KTextEdit; class QFont; class QAbstractButton; class QTextEdit; class QListWidgetItem; class KoViewConverter; namespace KSpread { class Canvas; class Cell; class CellEditor; class CellToolBase; class LocationEditWidget; class Region; class Selection; class TextEdit; class Tokens; class View; /** * Colors cell references in formulas. Installed by CellEditor instances in * the constructor. */ class FormulaEditorHighlighter : public QSyntaxHighlighter { public: /** * Constructs a FormulaHighlighter to color-code cell references in a QTextEdit. * * @param textEdit The QTextEdit widget which the highlighter should operate on * @param selection The Selection object */ FormulaEditorHighlighter(QTextEdit* textEdit, Selection* selection); virtual ~FormulaEditorHighlighter(); /** * Called automatically by KTextEditor to highlight text when modified. */ virtual void highlightBlock(const QString& text); /** * */ const Tokens& formulaTokens() const; /** * */ uint rangeCount() const; /** * Returns true if any of the ranges or cells in the Formula.have changed since the * last call to @ref FormulaEditorHighlighter::rangeChanged() */ bool rangeChanged() const; /** * Sets the highlighter's range changed flag to false. */ void resetRangeChanged(); protected: /** * Returns the position of the brace matching the one found at position pos */ int findMatchingBrace(int pos); /** * Examines the brace (Token::LeftPar or Token::RightPar) operator token at the given index in the token vector * ( as returned by formulaTokens() ) and if the cursor is next to it, the token plus any matching brace will be highlighted */ void handleBrace(uint index); private: Q_DISABLE_COPY(FormulaEditorHighlighter) class Private; Private * const d; }; /** * Provides autocompletition facilities in formula editors. * When the user types in the first few characters of a * function name in a CellEditor which has a FunctionCompletion * object installed on it, the FunctionCompletion object * creates and displays a list of possible names which the user * can select from. If the user selects a function name from the list, * the @ref FunctionCompletion::selectedCompletion() signal is emitted */ class FunctionCompletion : public QObject { Q_OBJECT public: FunctionCompletion(CellEditor* editor); ~FunctionCompletion(); /** * Handles various keyboard and mouse actions which may occur on the autocompletion popup list */ bool eventFilter(QObject *o, QEvent *e); /** * Populates the autocompletion list box with the specified choices and shows it so that the user can view and select a function name. * @param choices A list of possible function names which match the characters that the user has already entered. */ void showCompletion(const QStringList &choices); public slots: /** * Hides the autocompletion list box if it is visible and emits the @ref selectedCompletion signal. */ void doneCompletion(); private slots: void itemSelected(QListWidgetItem* item = 0); signals: /** * Emitted, if the user selects a function name from the list. */ void selectedCompletion(const QString& item); private: class Private; Private * const d; FunctionCompletion(const FunctionCompletion&); FunctionCompletion& operator=(const FunctionCompletion&); }; /** * class CellEditor */ class CellEditor : public KTextEdit { Q_OBJECT public: /** * Creates a new CellEditor. * \param cellTool the cell tool * \param parent the parent widget */ explicit CellEditor(CellToolBase *cellTool, QWidget *parent = 0); ~CellEditor(); Selection* selection() const; void setEditorFont(QFont const & font, bool updateSize, const KoViewConverter *viewConverter); int cursorPosition() const; void setCursorPosition(int pos); QPoint globalCursorPosition() const; /** * Replaces the current formula token(/reference) with the name of the * selection's active sub-region name. * This is called after selection changes to sync the formula expression. */ void selectionChanged(); /** * Activates the sub-region belonging to the \p index 'th range. */ void setActiveSubRegion(int index); Q_SIGNALS: void textChanged(const QString &text); public slots: void setText(const QString& text, int cursorPos = -1); + /** + * Permutes the fixation of the reference, at which the editor's cursor + * is placed. It is only active, if a formula is edited. + */ + void permuteFixation(); + private slots: void slotTextChanged(); void slotCompletionModeChanged(KGlobalSettings::Completion _completion); void slotCursorPositionChanged(); protected: // reimplementations virtual void keyPressEvent(QKeyEvent *event); virtual void focusInEvent(QFocusEvent *event); virtual void focusOutEvent(QFocusEvent *event); protected slots: void checkFunctionAutoComplete(); void triggerFunctionAutoComplete(); void functionAutoComplete(const QString& item); private: Q_DISABLE_COPY(CellEditor) class Private; Private * const d; }; /** * \class LocationComboBox * \ingroup UI * The combobox, that shows the address string of the current cell selection. * * Depending on the sheet settings the address is displayed in normal form * (e.g. A1 or B1:C3) or in LC (Line/Column) form (e.g. L1xC1 or 3Lx2C). */ class LocationComboBox : public KComboBox { Q_OBJECT public: LocationComboBox(CellToolBase *cellTool, QWidget *parent = 0); void addCompletionItem(const QString &_item); void removeCompletionItem(const QString &_item); /** * Updates the address string according to the current cell selection * and the current address mode (normal or LC mode). */ void updateAddress(); public slots: void slotAddAreaName( const QString & ); void slotRemoveAreaName( const QString & ); protected: // reimplementations virtual void keyPressEvent(QKeyEvent *event); private Q_SLOTS: void slotActivateItem(); private: bool activateItem(); private: CellToolBase *m_cellTool; KCompletion completionList; }; class ExternalEditor : public KTextEdit { Q_OBJECT public: ExternalEditor(QWidget* parent = 0); ~ExternalEditor(); virtual QSize sizeHint() const; void setCellTool(CellToolBase* cellTool); Q_SIGNALS: void textChanged(const QString &text); public Q_SLOTS: void applyChanges(); void discardChanges(); void setText(const QString &text); protected: void keyPressEvent(QKeyEvent *event); void focusInEvent(QFocusEvent *event); void focusOutEvent(QFocusEvent *event); private slots: void slotTextChanged(); void slotCursorPositionChanged(); private: Q_DISABLE_COPY(ExternalEditor) class Private; Private * const d; }; #if 0 // KSPREAD_DISCARD_FORMULA_BAR /** * The widget that appears above the sheet and allows to * edit the cells content. */ class EditWidget : public KLineEdit { Q_OBJECT public: EditWidget(QWidget *parent, Canvas *canvas, QAbstractButton *cancelButton, QAbstractButton *okButton); virtual void setText(const QString& t); // Go into edit mode (enable the buttons) void setEditMode(bool mode); void showEditWidget(bool _show); public slots: void slotAbortEdit(); void slotDoneEdit(); protected: virtual void keyPressEvent(QKeyEvent* _ev); virtual void focusOutEvent(QFocusEvent* ev); private: QAbstractButton* m_pCancelButton; QAbstractButton* m_pOkButton; Canvas* m_pCanvas; bool m_isArray; }; #endif /** * A minimizable line edit for choosing cell regions. * \author Stefan Nikolaus */ class KSPREAD_EXPORT RegionSelector : public QWidget { Q_OBJECT public: enum SelectionMode { SingleCell = 0, MultipleCells = 1 }; // TODO Stefan: merge with Selection::Mode enum DisplayMode { Widget, Dialog }; RegionSelector(QWidget* parent = 0); ~RegionSelector(); void setSelectionMode(SelectionMode mode); void setSelection(Selection* selection); void setDialog(QDialog* dialog); void setLabel(const QString& text); KTextEdit* textEdit() const; protected: bool eventFilter(QObject* obj, QEvent* event); protected Q_SLOTS: void switchDisplayMode(bool state); void choiceChanged(); private: Q_DISABLE_COPY(RegionSelector) class Private; Private * const d; }; } // namespace KSpread #endif