]> git.lyx.org Git - features.git/blobdiff - src/frontends/qt/GuiWorkArea.cpp
Avoid deprecation warnings
[features.git] / src / frontends / qt / GuiWorkArea.cpp
index 6a7c995da9b3ab4859134d4b399403714f85e598..02e03aaaeb780565d741ffd284345b3c09a852e9 100644 (file)
@@ -15,7 +15,6 @@
 #include "GuiWorkArea_Private.h"
 
 #include "ColorCache.h"
-#include "FontLoader.h"
 #include "GuiApplication.h"
 #include "GuiCompleter.h"
 #include "GuiKeySymbol.h"
@@ -33,7 +32,6 @@
 #include "Font.h"
 #include "FuncRequest.h"
 #include "KeySymbol.h"
-#include "Language.h"
 #include "LyX.h"
 #include "LyXRC.h"
 #include "LyXVC.h"
 #include "Undo.h"
 #include "version.h"
 
-#include "graphics/GraphicsImage.h"
-#include "graphics/GraphicsLoader.h"
-
 #include "support/convert.h"
 #include "support/debug.h"
 #include "support/lassert.h"
 #include "support/TempFile.h"
 
 #include "frontends/Application.h"
+#include "frontends/CaretGeometry.h"
+
 #include "frontends/FontMetrics.h"
 #include "frontends/WorkAreaManager.h"
 
@@ -79,8 +76,6 @@
 #include <cmath>
 #include <iostream>
 
-int const TabIndicatorWidth = 3;
-
 #undef KeyPress
 #undef NoModifier
 
@@ -98,7 +93,7 @@ static mouse_button::state q_button_state(Qt::MouseButton button)
                case Qt::LeftButton:
                        b = mouse_button::button1;
                        break;
-               case Qt::MidButton:
+               case Qt::MiddleButton:
                        b = mouse_button::button2;
                        break;
                case Qt::RightButton:
@@ -117,7 +112,7 @@ mouse_button::state q_motion_state(Qt::MouseButtons state)
        mouse_button::state b = mouse_button::none;
        if (state & Qt::LeftButton)
                b |= mouse_button::button1;
-       if (state & Qt::MidButton)
+       if (state & Qt::MiddleButton)
                b |= mouse_button::button2;
        if (state & Qt::RightButton)
                b |= mouse_button::button3;
@@ -127,106 +122,6 @@ mouse_button::state q_motion_state(Qt::MouseButtons state)
 
 namespace frontend {
 
-class CaretWidget {
-public:
-       CaretWidget() : rtl_(false), l_shape_(false), completable_(false),
-               x_(0), caret_width_(0)
-       {}
-
-       void draw(QPainter & painter)
-       {
-               if (!rect_.isValid())
-                       return;
-
-               int y = rect_.top();
-               int l = x_ - rect_.left();
-               int r = rect_.right() - x_;
-               int bot = rect_.bottom();
-
-               // draw vertical line
-               painter.fillRect(x_, y, caret_width_, rect_.height(), color_);
-
-               // draw RTL/LTR indication
-               painter.setPen(color_);
-               if (l_shape_) {
-                       if (rtl_)
-                               painter.drawLine(x_, bot, x_ - l + 1, bot);
-                       else
-                               painter.drawLine(x_, bot, x_ + caret_width_ + r - 1, bot);
-               }
-
-               // draw completion triangle
-               if (completable_) {
-                       int m = y + rect_.height() / 2;
-                       int d = TabIndicatorWidth - 1;
-                       if (rtl_) {
-                               painter.drawLine(x_ - 1, m - d, x_ - 1 - d, m);
-                               painter.drawLine(x_ - 1, m + d, x_ - 1 - d, m);
-                       } else {
-                               painter.drawLine(x_ + caret_width_, m - d, x_ + caret_width_ + d, m);
-                               painter.drawLine(x_ + caret_width_, m + d, x_ + caret_width_ + d, m);
-                       }
-               }
-       }
-
-       void update(int x, int y, int h, bool l_shape,
-               bool rtl, bool completable)
-       {
-               color_ = guiApp->colorCache().get(Color_cursor);
-               l_shape_ = l_shape;
-               rtl_ = rtl;
-               completable_ = completable;
-               x_ = x;
-
-               // extension to left and right
-               int l = 0;
-               int r = 0;
-
-               // RTL/LTR indication
-               if (l_shape_) {
-                       if (rtl)
-                               l += h / 3;
-                       else
-                               r += h / 3;
-               }
-
-               // completion triangle
-               if (completable_) {
-                       if (rtl)
-                               l = max(l, TabIndicatorWidth);
-                       else
-                               r = max(r, TabIndicatorWidth);
-               }
-
-               //FIXME: LyXRC::cursor_width should be caret_width
-               caret_width_ = lyxrc.cursor_width
-                       ? lyxrc.cursor_width
-                       : 1 + int((lyxrc.currentZoom + 50) / 200.0);
-
-               // compute overall rectangle
-               rect_ = QRect(x - l, y, caret_width_ + r + l, h);
-       }
-
-       QRect const & rect() { return rect_; }
-
-private:
-       /// caret is in RTL or LTR text
-       bool rtl_;
-       /// indication for RTL or LTR
-       bool l_shape_;
-       /// triangle to show that a completion is available
-       bool completable_;
-       ///
-       QColor color_;
-       /// rectangle, possibly with l_shape and completion triangle
-       QRect rect_;
-       /// x position (were the vertical line is drawn)
-       int x_;
-       /// the width of the vertical blinking bar
-       int caret_width_;
-};
-
-
 // This is a 'heartbeat' generating synthetic mouse move events when the
 // cursor is at the top or bottom edge of the viewport. One scroll per 0.2 s
 SyntheticMouseEvent::SyntheticMouseEvent()
@@ -235,26 +130,15 @@ SyntheticMouseEvent::SyntheticMouseEvent()
 
 
 GuiWorkArea::Private::Private(GuiWorkArea * parent)
-: p(parent), buffer_view_(0), lyx_view_(0), caret_(0),
-  caret_visible_(false), need_resize_(false), preedit_lines_(1),
-  last_pixel_ratio_(1.0), completer_(new GuiCompleter(p, p)),
-  dialog_mode_(false), shell_escape_(false), read_only_(false),
-  clean_(true), externally_modified_(false)
-{
-/* Qt on macOS and Wayland does not respect the
- * Qt::WA_OpaquePaintEvent attribute and resets the widget backing
- * store at each update. Therefore, we use our own backing store in
- * these two cases. */
-#if QT_VERSION >= 0x050000
+       : p(parent), completer_(new GuiCompleter(p, p))
+{
+       /* Qt on macOS and Wayland does not respect the
+        * Qt::WA_OpaquePaintEvent attribute and resets the widget backing
+        * store at each update. Therefore, we use our own backing store
+        * in these two cases.
+        */
        use_backingstore_ = guiApp->platformName() == "cocoa"
                || guiApp->platformName().contains("wayland");
-#else
-#  ifdef Q_OS_MAC
-       use_backingstore_ = true;
-#  else
-       use_backingstore_ = false;
-#  endif
-#endif
 
        int const time = QApplication::cursorFlashTime() / 2;
        if (time > 0) {
@@ -274,7 +158,6 @@ GuiWorkArea::Private::~Private()
                buffer_view_->buffer().workAreaManager().remove(p);
        } catch(...) {}
        delete buffer_view_;
-       delete caret_;
        // Completer has a QObject parent and is thus automatically destroyed.
        // See #4758.
        // delete completer_;
@@ -324,7 +207,6 @@ void GuiWorkArea::init()
        // With Qt4.5 a mouse event will happen before the first paint event
        // so make sure that the buffer view has an up to date metrics.
        d->buffer_view_->resize(viewport()->width(), viewport()->height());
-       d->caret_ = new frontend::CaretWidget();
 
        setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        setAcceptDrops(true);
@@ -376,7 +258,7 @@ void GuiWorkArea::setBuffer(Buffer & buffer)
        buffer.workAreaManager().add(this);
 
        // HACK: Prevents an additional redraw when the scrollbar pops up
-       // which regularily happens on documents with more than one page.
+       // which regularly happens on documents with more than one page.
        // The policy  should be set to "Qt::ScrollBarAsNeeded" soon.
        // Since we have no geometry information yet, we assume that
        // a document needs a scrollbar if there is more then four
@@ -404,12 +286,9 @@ void GuiWorkArea::close()
 void GuiWorkArea::setFullScreen(bool full_screen)
 {
        d->buffer_view_->setFullScreen(full_screen);
-       setFrameStyle(QFrame::NoFrame);
-       if (full_screen) {
-               setFrameStyle(QFrame::NoFrame);
-               if (lyxrc.full_screen_scrollbar)
-                       setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-       } else
+       if (full_screen && lyxrc.full_screen_scrollbar)
+               setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+       else
                setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
 }
 
@@ -439,8 +318,10 @@ void GuiWorkArea::startBlinkingCaret()
        if (view().busy())
                return;
 
-       // Don't start blinking if the cursor isn't on screen.
-       if (!d->buffer_view_->caretInView())
+       // Don't start blinking if the cursor isn't on screen, unless we
+       // are not ready to know whether the cursor is on screen.
+       if (!d->buffer_view_->buffer().undo().activeUndoGroup()
+           && !d->buffer_view_->caretInView())
                return;
 
        d->showCaret();
@@ -484,7 +365,7 @@ void GuiWorkArea::scheduleRedraw(bool update_metrics)
 
        // update caret position, because otherwise it has to wait until
        // the blinking interval is over
-       d->updateCaretGeometry();
+       d->resetCaret();
 
        LYXERR(Debug::WORKAREA, "WorkArea::redraw screen");
        viewport()->update();
@@ -584,12 +465,12 @@ void GuiWorkArea::Private::resizeBufferView()
        buffer_view_->resize(p->viewport()->width(), p->viewport()->height());
        if (caret_in_view)
                buffer_view_->scrollToCursor();
-       updateCaretGeometry();
+       resetCaret();
 
        // Update scrollbars which might have changed due different
        // BufferView dimension. This is especially important when the
        // BufferView goes from zero-size to the real-size for the first time,
-       // as the scrollbar paramters are then set for the first time.
+       // as the scrollbar parameters are then set for the first time.
        updateScrollbar();
 
        need_resize_ = false;
@@ -602,43 +483,36 @@ void GuiWorkArea::Private::resizeBufferView()
 }
 
 
-void GuiWorkArea::Private::updateCaretGeometry()
+void GuiWorkArea::Private::resetCaret()
 {
+       // Don't start blinking if the cursor isn't on screen.
        if (!buffer_view_->caretInView())
                return;
 
-       Point point;
-       int h = 0;
-       buffer_view_->caretPosAndHeight(point, h);
-
-       // RTL or not RTL
-       bool l_shape = false;
-       Font const & realfont = buffer_view_->cursor().real_current_font;
-       BufferParams const & bp = buffer_view_->buffer().params();
-       bool const samelang = realfont.language() == bp.language;
-       bool const isrtl = realfont.isVisibleRightToLeft();
-
-       if (!samelang || isrtl != bp.language->rightToLeft())
-               l_shape = true;
-
-       // The ERT language hack needs fixing up
-       if (realfont.language() == latex_language)
-               l_shape = false;
-
-       // show caret on screen
-       Cursor & cur = buffer_view_->cursor();
-       bool completable = cur.inset().showCompletionCursor()
+       // completion indicator
+       Cursor const & cur = buffer_view_->cursor();
+       bool const completable = cur.inset().showCompletionCursor()
                && completer_->completionAvailable()
                && !completer_->popupVisible()
                && !completer_->inlineVisible();
+
+       buffer_view_->buildCaretGeometry(completable);
+
+       needs_caret_geometry_update_ = true;
        caret_visible_ = true;
+}
 
-       //int cur_x = buffer_view_->getPos(cur).x_;
-       // We may have decided to slide the cursor row so that caret
-       // is visible.
-       point.x_ -= buffer_view_->horizScrollOffset();
 
-       caret_->update(point.x_, point.y_, h, l_shape, isrtl, completable);
+void GuiWorkArea::Private::updateCaretGeometry()
+{
+       // we cannot update geometry if not ready and we do not need to if
+       // caret is not in view.
+       if (buffer_view_->buffer().undo().activeUndoGroup()
+           || !buffer_view_->caretInView())
+               return;
+
+
+       needs_caret_geometry_update_ = false;
 }
 
 
@@ -647,7 +521,7 @@ void GuiWorkArea::Private::showCaret()
        if (caret_visible_)
                return;
 
-       updateCaretGeometry();
+       resetCaret();
        p->viewport()->update();
 }
 
@@ -663,6 +537,33 @@ void GuiWorkArea::Private::hideCaret()
 }
 
 
+/* Draw the caret. Parameter \c horiz_offset is not 0 when there
+ * has been horizontal scrolling in current row
+ */
+void GuiWorkArea::Private::drawCaret(QPainter & painter, int horiz_offset) const
+{
+       if (buffer_view_->caretGeometry().shapes.empty())
+               return;
+
+       QColor const color = guiApp->colorCache().get(Color_cursor);
+       painter.setPen(color);
+       painter.setRenderHint(QPainter::Antialiasing, true);
+       for (auto const & shape : buffer_view_->caretGeometry().shapes) {
+               bool first = true;
+               QPainterPath path;
+               for (Point const & p : shape) {
+                       if (first) {
+                               path.moveTo(p.x_ - horiz_offset, p.y_);
+                               first = false;
+                       } else
+                               path.lineTo(p.x_ - horiz_offset, p.y_);
+               }
+               painter.fillPath(path, color);
+       }
+       painter.setRenderHint(QPainter::Antialiasing, false);
+}
+
+
 void GuiWorkArea::Private::updateScrollbar()
 {
        // Prevent setRange() and setSliderPosition from causing recursive calls via
@@ -848,6 +749,10 @@ void GuiWorkArea::mouseReleaseEvent(QMouseEvent * e)
 
        FuncRequest const cmd(LFUN_MOUSE_RELEASE, e->x(), e->y(),
                        q_button_state(e->button()), q_key_state(e->modifiers()));
+#if (QT_VERSION > QT_VERSION_CHECK(5,10,1) && \
+       QT_VERSION < QT_VERSION_CHECK(5,15,1))
+       d->synthetic_mouse_event_.cmd = cmd; // QtBug QAbstractScrollArea::mouseMoveEvent
+#endif
        d->dispatch(cmd);
        e->accept();
 }
@@ -855,6 +760,21 @@ void GuiWorkArea::mouseReleaseEvent(QMouseEvent * e)
 
 void GuiWorkArea::mouseMoveEvent(QMouseEvent * e)
 {
+#if (QT_VERSION > QT_VERSION_CHECK(5,10,1) && \
+       QT_VERSION < QT_VERSION_CHECK(5,15,1))
+       // cancel the event if the coordinates didn't change, this is due to QtBug
+       // QAbstractScrollArea::mouseMoveEvent, the event is triggered falsely when quickly
+       // double tapping a touchpad. To test: try to select a word by quickly double tapping
+       // on a touchpad while hovering the cursor over that word in the work area.
+       // This bug does not occur on Qt versions 5.10.1 and below. Only Windows seems to be affected.
+       // ML thread: https://www.mail-archive.com/lyx-devel@lists.lyx.org/msg211699.html
+       // Qt bugtracker: https://bugreports.qt.io/browse/QTBUG-85431
+       // Bug was fixed in Qt 5.15.1
+       if (e->x() == d->synthetic_mouse_event_.cmd.x() && // QtBug QAbstractScrollArea::mouseMoveEvent
+                       e->y() == d->synthetic_mouse_event_.cmd.y()) // QtBug QAbstractScrollArea::mouseMoveEvent
+               return; // QtBug QAbstractScrollArea::mouseMoveEvent
+#endif
+
        // we kill the triple click if we move
        doubleClickTimeout();
        FuncRequest cmd(LFUN_MOUSE_MOTION, e->x(), e->y(),
@@ -1056,7 +976,6 @@ void GuiWorkArea::generateSyntheticMouseEvent()
        cur.boundary(bound);
 
        d->buffer_view_->buffer().changed(false);
-       return;
 }
 
 
@@ -1192,9 +1111,11 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
        // FIXME: shall we use real_current_font here? (see #10478)
        FontInfo const font = buffer_view_->cursor().getFont().fontInfo();
        FontMetrics const & fm = theFontMetrics(font);
-       int const height = fm.maxHeight();
-       int cur_x = caret_->rect().left();
-       int cur_y = caret_->rect().bottom();
+       Point point;
+       Dimension dim;
+       buffer_view_->caretPosAndDim(point, dim);
+       int cur_x = point.x_;
+       int cur_y = point.y_ + dim.height();
 
        // get attributes of input method cursor.
        // cursor_pos : cursor position in preedit string.
@@ -1202,7 +1123,7 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
        bool cursor_is_visible = false;
        for (auto const & attr : preedit_attr_) {
                if (attr.type == QInputMethodEvent::Cursor) {
-                       cursor_pos = attr.start;
+                       cursor_pos = size_t(attr.start);
                        cursor_is_visible = attr.length != 0;
                        break;
                }
@@ -1211,7 +1132,7 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
        size_t const preedit_length = preedit_string_.length();
 
        // get position of selection in input method.
-       // FIXME: isn't there a way to do this simplier?
+       // FIXME: isn't there a simpler way to do this?
        // rStart : cursor position in selected string in IM.
        size_t rStart = 0;
        // rLength : selected string length in IM.
@@ -1221,8 +1142,8 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
                        if (attr.type == QInputMethodEvent::TextFormat) {
                                if (attr.start <= int(cursor_pos)
                                        && int(cursor_pos) < attr.start + attr.length) {
-                                               rStart = attr.start;
-                                               rLength = attr.length;
+                                               rStart = size_t(attr.start);
+                                               rLength = size_t(attr.length);
                                                if (!cursor_is_visible)
                                                        cursor_pos += rLength;
                                                break;
@@ -1247,7 +1168,7 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
                // if we reached the right extremity of the screen, go to next line.
                if (cur_x + fm.width(typed_char) > p->viewport()->width() - right_margin) {
                        cur_x = right_margin;
-                       cur_y += height + 1;
+                       cur_y += dim.height() + 1;
                        ++preedit_lines_;
                }
                // preedit strings are displayed with dashed underline
@@ -1331,7 +1252,7 @@ void GuiWorkArea::paintEvent(QPaintEvent * ev)
 
        d->last_pixel_ratio_ = pixelRatio();
 
-       GuiPainter pain(d->screenDevice(), pixelRatio());
+       GuiPainter pain(d->screenDevice(), pixelRatio(), d->lyx_view_->develMode());
 
        d->buffer_view_->draw(pain, d->caret_visible_);
 
@@ -1339,8 +1260,14 @@ void GuiWorkArea::paintEvent(QPaintEvent * ev)
        d->paintPreeditText(pain);
 
        // and the caret
-       if (d->caret_visible_)
-               d->caret_->draw(pain);
+       // FIXME: the code would be a little bit simpler if caret geometry
+       // was updated unconditionally. Some profiling is required to see
+       // how expensive this is (especially when idle).
+       if (d->caret_visible_) {
+               if (d->needs_caret_geometry_update_)
+                       d->updateCaretGeometry();
+               d->drawCaret(pain, d->buffer_view_->horizScrollOffset());
+       }
 
        d->updateScreen(ev->rect());
 
@@ -1360,7 +1287,7 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
                                FuncRequest::KEYBOARD);
                dispatch(cmd);
                // FIXME: this is supposed to remove traces from preedit
-               // string. Can we avoid calling it explicitely?
+               // string. Can we avoid calling it explicitly?
                d->buffer_view_->updateMetrics();
        }
 
@@ -1382,10 +1309,11 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
 
 
        // redraw area of preedit string.
-       int height = d->caret_->rect().height();
-       int cur_y = d->caret_->rect().bottom();
-       viewport()->update(0, cur_y - height, viewport()->width(),
-               (height + 1) * d->preedit_lines_);
+       // int height = d->caret_->dim.height();
+       // int cur_y = d->caret_->y;
+       // viewport()->update(0, cur_y, viewport()->width(),
+       //      (height + 1) * d->preedit_lines_);
+       viewport()->update();
 
        if (d->preedit_string_.empty()) {
                d->preedit_lines_ = 1;
@@ -1400,20 +1328,21 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
 
 QVariant GuiWorkArea::inputMethodQuery(Qt::InputMethodQuery query) const
 {
-       QRect cur_r(0, 0, 0, 0);
        switch (query) {
                // this is the CJK-specific composition window position and
                // the context menu position when the menu key is pressed.
-               case Qt::ImMicroFocus:
-                       cur_r = d->caret_->rect();
-                       if (d->preedit_lines_ != 1)
-                               cur_r.moveLeft(10);
-                       cur_r.moveBottom(cur_r.bottom()
-                               + cur_r.height() * (d->preedit_lines_ - 1));
-                       // return lower right of caret in LyX.
-                       return cur_r;
-               default:
-                       return QWidget::inputMethodQuery(query);
+#if (QT_VERSION < 0x050000)
+       case Qt::ImMicroFocus: {
+#else
+       case Qt::ImCursorRectangle: {
+#endif
+               CaretGeometry const & cg = bufferView().caretGeometry();
+               return QRect(cg.left - 10 * (d->preedit_lines_ != 1),
+                            cg.top + cg.height() * d->preedit_lines_,
+                            cg.width(), cg.height());
+       }
+       default:
+               return QWidget::inputMethodQuery(query);
        }
 }
 
@@ -1620,7 +1549,7 @@ TabWorkArea::TabWorkArea(QWidget * parent)
 
 void TabWorkArea::mousePressEvent(QMouseEvent *me)
 {
-       if (me->button() == Qt::MidButton)
+       if (me->button() == Qt::MiddleButton)
                midpressed_tab_ = tabBar()->tabAt(me->pos());
        else
                QTabWidget::mousePressEvent(me);
@@ -1629,7 +1558,7 @@ void TabWorkArea::mousePressEvent(QMouseEvent *me)
 
 void TabWorkArea::mouseReleaseEvent(QMouseEvent *me)
 {
-       if (me->button() == Qt::MidButton) {
+       if (me->button() == Qt::MiddleButton) {
                int const midreleased_tab = tabBar()->tabAt(me->pos());
                if (midpressed_tab_ == midreleased_tab && posIsTab(me->pos()))
                        closeTab(midreleased_tab);
@@ -1748,11 +1677,11 @@ GuiWorkArea * TabWorkArea::workArea(Buffer & buffer) const
        // showing the same buffer.
        for (int i = 0; i != count(); ++i) {
                GuiWorkArea * wa = workArea(i);
-               LASSERT(wa, return 0);
+               LASSERT(wa, return nullptr);
                if (&wa->bufferView().buffer() == &buffer)
                        return wa;
        }
-       return 0;
+       return nullptr;
 }
 
 
@@ -2007,7 +1936,7 @@ bool operator==(DisplayPath const & a, DisplayPath const & b)
 
 void TabWorkArea::updateTabTexts()
 {
-       size_t n = count();
+       int const n = count();
        if (n == 0)
                return;
        std::list<DisplayPath> paths;
@@ -2015,7 +1944,7 @@ void TabWorkArea::updateTabTexts()
 
        // collect full names first: path into postfix, empty prefix and
        // filename without extension
-       for (size_t i = 0; i < n; ++i) {
+       for (int i = 0; i < n; ++i) {
                GuiWorkArea * i_wa = workArea(i);
                FileName const fn = i_wa->bufferView().buffer().fileName();
                paths.push_back(DisplayPath(i, fn));
@@ -2189,7 +2118,7 @@ void GuiWorkAreaContainer::updateDisplay()
 }
 
 
-void GuiWorkAreaContainer::dispatch(FuncRequest f) const
+void GuiWorkAreaContainer::dispatch(FuncRequest const & f) const
 {
        lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
                                  wa_->bufferView().buffer().absFileName()));