1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sal/macros.h>
21 #include <sal/log.hxx>
22 #include <helpids.h>
23 #include <svx/gridctrl.hxx>
24 #include <gridcell.hxx>
25 #include <svx/fmtools.hxx>
26 #include <svtools/stringtransfer.hxx>
27 #include <connectivity/dbtools.hxx>
28 #include <connectivity/dbconversion.hxx>
29 
30 #include <fmprop.hxx>
31 #include <com/sun/star/sdbc/ResultSetConcurrency.hpp>
32 #include <com/sun/star/accessibility/XAccessible.hpp>
33 #include <com/sun/star/sdb/XResultSetAccess.hpp>
34 #include <com/sun/star/sdb/RowChangeAction.hpp>
35 #include <com/sun/star/sdb/XRowsChangeBroadcaster.hpp>
36 #include <com/sun/star/sdbc/SQLException.hpp>
37 #include <com/sun/star/sdbc/XResultSetUpdate.hpp>
38 #include <com/sun/star/sdbcx/Privilege.hpp>
39 #include <com/sun/star/container/XChild.hpp>
40 #include <com/sun/star/util/NumberFormatter.hpp>
41 #include <com/sun/star/util/XNumberFormatsSupplier.hpp>
42 #include <com/sun/star/util/XCloneable.hpp>
43 #include <com/sun/star/beans/XPropertySet.hpp>
44 #include <com/sun/star/beans/PropertyChangeEvent.hpp>
45 #include <com/sun/star/container/XIndexAccess.hpp>
46 #include <tools/diagnose_ex.h>
47 #include <tools/debug.hxx>
48 #include <tools/fract.hxx>
49 #include <vcl/builder.hxx>
50 #include <vcl/menu.hxx>
51 #include <vcl/settings.hxx>
52 #include <vcl/commandevent.hxx>
53 #include <vcl/svapp.hxx>
54 
55 #include <svx/strings.hrc>
56 
57 #include <svx/svxids.hrc>
58 #include <svx/dialmgr.hxx>
59 #include <fmservs.hxx>
60 #include <sdbdatacolumn.hxx>
61 
62 #include <comphelper/property.hxx>
63 #include <comphelper/types.hxx>
64 #include <cppuhelper/implbase.hxx>
65 
66 #include <algorithm>
67 #include <cstdlib>
68 #include <map>
69 #include <memory>
70 
71 using namespace ::dbtools;
72 using namespace ::dbtools::DBTypeConversion;
73 using namespace ::svxform;
74 using namespace ::svt;
75 using namespace ::com::sun::star::beans;
76 using namespace ::com::sun::star::lang;
77 using namespace ::com::sun::star::uno;
78 using namespace ::com::sun::star::sdbc;
79 using namespace ::com::sun::star::sdbcx;
80 using namespace ::com::sun::star::sdb;
81 using namespace ::com::sun::star::datatransfer;
82 using namespace ::com::sun::star::container;
83 using namespace com::sun::star::accessibility;
84 
85 #define ROWSTATUS(row) (!row.is() ? "NULL" : row->GetStatus() == GridRowStatus::Clean ? "CLEAN" : row->GetStatus() == GridRowStatus::Modified ? "MODIFIED" : row->GetStatus() == GridRowStatus::Deleted ? "DELETED" : "INVALID")
86 
87 static constexpr auto DEFAULT_BROWSE_MODE =
88               BrowserMode::COLUMNSELECTION
89             | BrowserMode::MULTISELECTION
90             | BrowserMode::KEEPHIGHLIGHT
91             | BrowserMode::TRACKING_TIPS
92             | BrowserMode::HLINES
93             | BrowserMode::VLINES
94             | BrowserMode::HEADERBAR_NEW;
95 
96 class RowSetEventListener : public ::cppu::WeakImplHelper<XRowsChangeListener>
97 {
98     VclPtr<DbGridControl> m_pControl;
99 public:
RowSetEventListener(DbGridControl * i_pControl)100     explicit RowSetEventListener(DbGridControl* i_pControl) : m_pControl(i_pControl)
101     {
102     }
103 
104 private:
105     // XEventListener
disposing(const css::lang::EventObject &)106     virtual void SAL_CALL disposing(const css::lang::EventObject& /*i_aEvt*/) override
107     {
108     }
rowsChanged(const css::sdb::RowsChangeEvent & i_aEvt)109     virtual void SAL_CALL rowsChanged(const css::sdb::RowsChangeEvent& i_aEvt) override
110     {
111         if ( i_aEvt.Action == RowChangeAction::UPDATE )
112         {
113             ::DbGridControl::GrantControlAccess aAccess;
114             CursorWrapper* pSeek = m_pControl->GetSeekCursor(aAccess);
115             const DbGridRowRef& rSeekRow = m_pControl->GetSeekRow(aAccess);
116             for(const Any& rBookmark : i_aEvt.Bookmarks)
117             {
118                 pSeek->moveToBookmark(rBookmark);
119                 // get the data
120                 rSeekRow->SetState(pSeek, true);
121                 sal_Int32 nSeekPos = pSeek->getRow() - 1;
122                 m_pControl->SetSeekPos(nSeekPos,aAccess);
123                 m_pControl->RowModified(nSeekPos);
124             }
125         }
126     }
127 };
128 
129 class GridFieldValueListener;
130 typedef std::map<sal_uInt16, GridFieldValueListener*> ColumnFieldValueListeners;
131 
132 class GridFieldValueListener : protected ::comphelper::OPropertyChangeListener
133 {
134     osl::Mutex                          m_aMutex;
135     DbGridControl&                      m_rParent;
136     rtl::Reference<::comphelper::OPropertyChangeMultiplexer> m_pRealListener;
137     sal_uInt16 const                    m_nId;
138     sal_Int16                           m_nSuspended;
139     bool                                m_bDisposed : 1;
140 
141 public:
142     GridFieldValueListener(DbGridControl& _rParent, const Reference< XPropertySet >& xField, sal_uInt16 _nId);
143     virtual ~GridFieldValueListener() override;
144 
145     virtual void _propertyChanged(const PropertyChangeEvent& evt) override;
146 
suspend()147     void suspend() { ++m_nSuspended; }
resume()148     void resume() { --m_nSuspended; }
149 
150     void dispose();
151 };
152 
GridFieldValueListener(DbGridControl & _rParent,const Reference<XPropertySet> & _rField,sal_uInt16 _nId)153 GridFieldValueListener::GridFieldValueListener(DbGridControl& _rParent, const Reference< XPropertySet >& _rField, sal_uInt16 _nId)
154     :OPropertyChangeListener(m_aMutex)
155     ,m_rParent(_rParent)
156     ,m_nId(_nId)
157     ,m_nSuspended(0)
158     ,m_bDisposed(false)
159 {
160     if (_rField.is())
161     {
162         m_pRealListener = new ::comphelper::OPropertyChangeMultiplexer(this, _rField);
163         m_pRealListener->addProperty(FM_PROP_VALUE);
164     }
165 }
166 
~GridFieldValueListener()167 GridFieldValueListener::~GridFieldValueListener()
168 {
169     dispose();
170 }
171 
_propertyChanged(const PropertyChangeEvent &)172 void GridFieldValueListener::_propertyChanged(const PropertyChangeEvent& /*_evt*/)
173 {
174     DBG_ASSERT(m_nSuspended>=0, "GridFieldValueListener::_propertyChanged : resume > suspend !");
175     if (m_nSuspended <= 0)
176         m_rParent.FieldValueChanged(m_nId);
177 }
178 
dispose()179 void GridFieldValueListener::dispose()
180 {
181     if (m_bDisposed)
182     {
183         DBG_ASSERT(m_pRealListener.get() == nullptr, "GridFieldValueListener::dispose : inconsistent !");
184         return;
185     }
186 
187     if (m_pRealListener.is())
188     {
189         m_pRealListener->dispose();
190         m_pRealListener.clear();
191     }
192 
193     m_bDisposed = true;
194     m_rParent.FieldListenerDisposing(m_nId);
195 }
196 
197 class DisposeListenerGridBridge : public FmXDisposeListener
198 {
199     DbGridControl&          m_rParent;
200     rtl::Reference<FmXDisposeMultiplexer>  m_xRealListener;
201 
202 public:
203     DisposeListenerGridBridge(  DbGridControl& _rParent, const Reference< XComponent >& _rxObject);
204     virtual ~DisposeListenerGridBridge() override;
205 
disposing(sal_Int16 _nId)206     virtual void disposing(sal_Int16 _nId) override { m_rParent.disposing(_nId); }
207 };
208 
DisposeListenerGridBridge(DbGridControl & _rParent,const Reference<XComponent> & _rxObject)209 DisposeListenerGridBridge::DisposeListenerGridBridge(DbGridControl& _rParent, const Reference< XComponent >& _rxObject)
210     :FmXDisposeListener()
211     ,m_rParent(_rParent)
212 {
213 
214     if (_rxObject.is())
215     {
216         m_xRealListener = new FmXDisposeMultiplexer(this, _rxObject);
217     }
218 }
219 
~DisposeListenerGridBridge()220 DisposeListenerGridBridge::~DisposeListenerGridBridge()
221 {
222     if (m_xRealListener.is())
223     {
224         m_xRealListener->dispose();
225     }
226 }
227 
228 static const DbGridControlNavigationBarState ControlMap[] =
229     {
230         DbGridControlNavigationBarState::Text,
231         DbGridControlNavigationBarState::Absolute,
232         DbGridControlNavigationBarState::Of,
233         DbGridControlNavigationBarState::Count,
234         DbGridControlNavigationBarState::First,
235         DbGridControlNavigationBarState::Next,
236         DbGridControlNavigationBarState::Prev,
237         DbGridControlNavigationBarState::Last,
238         DbGridControlNavigationBarState::New,
239         DbGridControlNavigationBarState::NONE
240     };
241 
CompareBookmark(const Any & aLeft,const Any & aRight)242 bool CompareBookmark(const Any& aLeft, const Any& aRight)
243 {
244     return aLeft == aRight;
245 }
246 
247 class FmXGridSourcePropListener : public ::comphelper::OPropertyChangeListener
248 {
249     VclPtr<DbGridControl> m_pParent;
250 
251     // a DbGridControl has no mutex, so we use our own as the base class expects one
252     osl::Mutex          m_aMutex;
253     sal_Int16           m_nSuspended;
254 
255 public:
256     explicit FmXGridSourcePropListener(DbGridControl* _pParent);
257 
suspend()258     void suspend() { ++m_nSuspended; }
resume()259     void resume() { --m_nSuspended; }
260 
261     virtual void _propertyChanged(const PropertyChangeEvent& evt) override;
262 };
263 
FmXGridSourcePropListener(DbGridControl * _pParent)264 FmXGridSourcePropListener::FmXGridSourcePropListener(DbGridControl* _pParent)
265     :OPropertyChangeListener(m_aMutex)
266     ,m_pParent(_pParent)
267     ,m_nSuspended(0)
268 {
269     DBG_ASSERT(m_pParent, "FmXGridSourcePropListener::FmXGridSourcePropListener : invalid parent !");
270 }
271 
_propertyChanged(const PropertyChangeEvent & evt)272 void FmXGridSourcePropListener::_propertyChanged(const PropertyChangeEvent& evt)
273 {
274     DBG_ASSERT(m_nSuspended>=0, "FmXGridSourcePropListener::_propertyChanged : resume > suspend !");
275     if (m_nSuspended <= 0)
276         m_pParent->DataSourcePropertyChanged(evt);
277 }
278 
AbsolutePos(vcl::Window * pParent,WinBits nStyle)279 DbGridControl::NavigationBar::AbsolutePos::AbsolutePos(vcl::Window* pParent, WinBits nStyle)
280                    :NumericField(pParent, nStyle)
281 {
282     SetMin(1);
283     SetFirst(1);
284     SetSpinSize(1);
285 
286     SetDecimalDigits(0);
287     SetStrictFormat(true);
288 }
289 
KeyInput(const KeyEvent & rEvt)290 void DbGridControl::NavigationBar::AbsolutePos::KeyInput(const KeyEvent& rEvt)
291 {
292     if (rEvt.GetKeyCode() == KEY_RETURN && !GetText().isEmpty())
293     {
294         sal_Int64 nRecord = GetValue();
295         if (nRecord < GetMin() || nRecord > GetMax())
296             return;
297         else
298             static_cast<NavigationBar*>(GetParent())->PositionDataSource(static_cast<sal_Int32>(nRecord));
299     }
300     else if (rEvt.GetKeyCode() == KEY_TAB)
301         GetParent()->GetParent()->GrabFocus();
302     else
303         NumericField::KeyInput(rEvt);
304 }
305 
LoseFocus()306 void DbGridControl::NavigationBar::AbsolutePos::LoseFocus()
307 {
308     NumericField::LoseFocus();
309     sal_Int64 nRecord = GetValue();
310     if (nRecord < GetMin() || nRecord > GetMax())
311         return;
312     else
313     {
314         static_cast<NavigationBar*>(GetParent())->PositionDataSource(static_cast<sal_Int32>(nRecord));
315         static_cast<NavigationBar*>(GetParent())->InvalidateState(DbGridControlNavigationBarState::Absolute);
316     }
317 }
318 
PositionDataSource(sal_Int32 nRecord)319 void DbGridControl::NavigationBar::PositionDataSource(sal_Int32 nRecord)
320 {
321     if (m_bPositioning)
322         return;
323     // the MoveToPosition may cause a LoseFocus which would lead to a second MoveToPosition,
324     // so protect against this recursion
325     m_bPositioning = true;
326     static_cast<DbGridControl*>(GetParent())->MoveToPosition(nRecord - 1);
327     m_bPositioning = false;
328 }
329 
NavigationBar(vcl::Window * pParent)330 DbGridControl::NavigationBar::NavigationBar(vcl::Window* pParent)
331           :Control(pParent, 0)
332           ,m_aRecordText(VclPtr<FixedText>::Create(this, WB_VCENTER))
333           ,m_aAbsolute(VclPtr<DbGridControl::NavigationBar::AbsolutePos>::Create(this, WB_CENTER | WB_VCENTER))
334           ,m_aRecordOf(VclPtr<FixedText>::Create(this, WB_VCENTER))
335           ,m_aRecordCount(VclPtr<FixedText>::Create(this, WB_VCENTER))
336           ,m_aFirstBtn(VclPtr<ImageButton>::Create(this, WB_RECTSTYLE|WB_NOPOINTERFOCUS))
337           ,m_aPrevBtn(VclPtr<ImageButton>::Create(this, WB_REPEAT|WB_RECTSTYLE|WB_NOPOINTERFOCUS))
338           ,m_aNextBtn(VclPtr<ImageButton>::Create(this, WB_REPEAT|WB_RECTSTYLE|WB_NOPOINTERFOCUS))
339           ,m_aLastBtn(VclPtr<ImageButton>::Create(this, WB_RECTSTYLE|WB_NOPOINTERFOCUS))
340           ,m_aNewBtn(VclPtr<ImageButton>::Create(this, WB_RECTSTYLE|WB_NOPOINTERFOCUS))
341           ,m_nCurrentPos(-1)
342           ,m_bPositioning(false)
343 {
344     m_aFirstBtn->SetSymbol(SymbolType::FIRST);
345     m_aPrevBtn->SetSymbol(SymbolType::PREV);
346     m_aNextBtn->SetSymbol(SymbolType::NEXT);
347     m_aLastBtn->SetSymbol(SymbolType::LAST);
348     m_aNewBtn->SetModeImage(static_cast<DbGridControl*>(pParent)->GetImage(EditBrowseBox::NEW));
349 
350     m_aFirstBtn->SetHelpId(HID_GRID_TRAVEL_FIRST);
351     m_aPrevBtn->SetHelpId(HID_GRID_TRAVEL_PREV);
352     m_aNextBtn->SetHelpId(HID_GRID_TRAVEL_NEXT);
353     m_aLastBtn->SetHelpId(HID_GRID_TRAVEL_LAST);
354     m_aNewBtn->SetHelpId(HID_GRID_TRAVEL_NEW);
355     m_aAbsolute->SetHelpId(HID_GRID_TRAVEL_ABSOLUTE);
356     m_aRecordCount->SetHelpId(HID_GRID_NUMBEROFRECORDS);
357 
358     // set handlers for buttons
359     m_aFirstBtn->SetClickHdl(LINK(this,NavigationBar,OnClick));
360     m_aPrevBtn->SetClickHdl(LINK(this,NavigationBar,OnClick));
361     m_aNextBtn->SetClickHdl(LINK(this,NavigationBar,OnClick));
362     m_aLastBtn->SetClickHdl(LINK(this,NavigationBar,OnClick));
363     m_aNewBtn->SetClickHdl(LINK(this,NavigationBar,OnClick));
364 
365     m_aRecordText->SetText(SvxResId(RID_STR_REC_TEXT));
366     m_aRecordOf->SetText(SvxResId(RID_STR_REC_FROM_TEXT));
367     m_aRecordCount->SetText(OUString('?'));
368 
369     m_aFirstBtn->Disable();
370     m_aPrevBtn->Disable();
371     m_aNextBtn->Disable();
372     m_aLastBtn->Disable();
373     m_aNewBtn->Disable();
374     m_aRecordText->Disable();
375     m_aRecordOf->Disable();
376     m_aRecordCount->Disable();
377     m_aAbsolute->Disable();
378 
379     AllSettings aSettings = m_aNextBtn->GetSettings();
380     MouseSettings aMouseSettings = aSettings.GetMouseSettings();
381     aMouseSettings.SetButtonRepeat(aMouseSettings.GetButtonRepeat() / 4);
382     aSettings.SetMouseSettings(aMouseSettings);
383     m_aNextBtn->SetSettings(aSettings, true);
384     m_aPrevBtn->SetSettings(aSettings, true);
385 
386     m_aFirstBtn->Show();
387     m_aPrevBtn->Show();
388     m_aNextBtn->Show();
389     m_aLastBtn->Show();
390     m_aNewBtn->Show();
391     m_aRecordText->Show();
392     m_aRecordOf->Show();
393     m_aRecordCount->Show();
394     m_aAbsolute->Show();
395 }
396 
397 
~NavigationBar()398 DbGridControl::NavigationBar::~NavigationBar()
399 {
400     disposeOnce();
401 }
402 
dispose()403 void DbGridControl::NavigationBar::dispose()
404 {
405     m_aRecordText.disposeAndClear();
406     m_aAbsolute.disposeAndClear();
407     m_aRecordOf.disposeAndClear();
408     m_aRecordCount.disposeAndClear();
409     m_aFirstBtn.disposeAndClear();
410     m_aPrevBtn.disposeAndClear();
411     m_aNextBtn.disposeAndClear();
412     m_aLastBtn.disposeAndClear();
413     m_aNewBtn.disposeAndClear();
414     Control::dispose();
415 }
416 
417 namespace
418 {
SetPosAndSize(Button & _rButton,Point & _rPos,const Size & _rSize)419     void SetPosAndSize(Button& _rButton,Point& _rPos,const Size& _rSize)
420     {
421         _rButton.SetPosPixel( _rPos );
422         _rButton.SetSizePixel( _rSize );
423         _rPos.AdjustX(static_cast<sal_uInt16>(_rSize.Width()) );
424     }
425 }
426 
ArrangeControls()427 sal_uInt16 DbGridControl::NavigationBar::ArrangeControls()
428 {
429     // positioning of the controls
430     // calculate base size
431     tools::Rectangle   aRect(static_cast<DbGridControl*>(GetParent())->GetControlArea());
432     long nH = aRect.GetSize().Height();
433     long nW = GetParent()->GetOutputSizePixel().Width();
434     Size aBorder = LogicToPixel(Size(2, 2), MapMode(MapUnit::MapAppFont));
435     aBorder = Size(CalcZoom(aBorder.Width()), CalcZoom(aBorder.Height()));
436     sal_uInt16      nX = 1;
437     sal_uInt16      nY = 0;
438 
439     {
440         vcl::Font aApplFont(GetSettings().GetStyleSettings().GetToolFont());
441         m_aAbsolute->SetControlFont( aApplFont );
442         aApplFont.SetTransparent( true );
443         m_aRecordText->SetControlFont( aApplFont );
444         m_aRecordOf->SetControlFont( aApplFont );
445         m_aRecordCount->SetControlFont( aApplFont );
446     }
447 
448     // set size and position of the control
449     OUString aText = m_aRecordText->GetText();
450     long nTextWidth = m_aRecordText->GetTextWidth(aText);
451     m_aRecordText->SetPosPixel(Point(nX,nY));
452     m_aRecordText->SetSizePixel(Size(nTextWidth,nH));
453     nX = sal::static_int_cast< sal_uInt16 >(nX + nTextWidth + aBorder.Width());
454 
455     // count an extra hairspace (U+200A) left and right
456     const OUString sevenDigits(m_aAbsolute->CreateFieldText(6000000));
457     const OUString hairSpace(u'\x200A');
458     OUString textPattern = hairSpace + sevenDigits + hairSpace;
459     nTextWidth = m_aAbsolute->GetTextWidth(textPattern);
460     m_aAbsolute->SetPosPixel(Point(nX,nY));
461     m_aAbsolute->SetSizePixel(Size(nTextWidth, nH));
462     nX = sal::static_int_cast< sal_uInt16 >(nX + nTextWidth + aBorder.Width());
463 
464     aText      = m_aRecordOf->GetText();
465     nTextWidth = m_aRecordOf->GetTextWidth(aText);
466     m_aRecordOf->SetPosPixel(Point(nX,nY));
467     m_aRecordOf->SetSizePixel(Size(nTextWidth,nH));
468     nX = sal::static_int_cast< sal_uInt16 >(nX + nTextWidth + aBorder.Width());
469 
470     textPattern = sevenDigits + " * (" + sevenDigits + ")";
471     nTextWidth = m_aRecordCount->GetTextWidth(textPattern);
472     m_aRecordCount->SetPosPixel(Point(nX,nY));
473     m_aRecordCount->SetSizePixel(Size(nTextWidth,nH));
474     nX = sal::static_int_cast< sal_uInt16 >(nX + nTextWidth + aBorder.Width());
475 
476     Point aButtonPos(nX,nY);
477     const Size  aButtonSize(nH,nH);
478     SetPosAndSize(*m_aFirstBtn.get(), aButtonPos, aButtonSize);
479     SetPosAndSize(*m_aPrevBtn.get(), aButtonPos, aButtonSize);
480     SetPosAndSize(*m_aNextBtn.get(), aButtonPos, aButtonSize);
481     SetPosAndSize(*m_aLastBtn.get(), aButtonPos, aButtonSize);
482     SetPosAndSize(*m_aNewBtn.get(), aButtonPos, aButtonSize);
483 
484     nX = sal::static_int_cast< sal_uInt16 >(aButtonPos.X() + 1);
485 
486     nW = std::max(nW - GetSettings().GetStyleSettings().GetScrollBarSize(), 0L);
487 
488     if (nX > nW)
489     {
490         aButtonPos.setX( nW-nH );
491         m_aNewBtn->SetPosPixel(aButtonPos);
492         aButtonPos.AdjustX( -nH );
493         m_aLastBtn->SetPosPixel(aButtonPos);
494         aButtonPos.AdjustX( -nH );
495         m_aNextBtn->SetPosPixel(aButtonPos);
496         aButtonPos.AdjustX( -nH );
497         m_aPrevBtn->SetPosPixel(aButtonPos);
498         aButtonPos.AdjustX( -nH );
499         m_aFirstBtn->SetPosPixel(aButtonPos);
500 
501         auto nDiff = nX - nW;
502 
503         Size aSize = m_aAbsolute->GetSizePixel();
504         aSize.AdjustWidth( -(nDiff/3.0) );
505         m_aAbsolute->SetSizePixel(aSize);
506 
507         aSize = m_aRecordCount->GetSizePixel();
508         aSize.AdjustWidth( -(nDiff/3.0*2) );
509         m_aRecordCount->SetSizePixel(aSize);
510 
511         Point aPos = m_aRecordOf->GetPosPixel();
512         aPos.AdjustX( -(nDiff/3.0) );
513         m_aRecordOf->SetPosPixel(aPos);
514 
515         aPos = m_aRecordCount->GetPosPixel();
516         aPos.AdjustX( -(nDiff/3.0) );
517         m_aRecordCount->SetPosPixel(aPos);
518 
519         vcl::Window* pWindows[] =
520         {
521             m_aRecordText.get(),
522             m_aAbsolute.get(),
523             m_aRecordOf.get(),
524             m_aRecordCount.get(),
525             m_aFirstBtn.get(),
526             m_aPrevBtn.get(),
527             m_aNextBtn.get(),
528             m_aLastBtn.get(),
529             m_aNewBtn.get()
530         };
531 
532         for (vcl::Window* pWindow : pWindows)
533         {
534             if (pWindow->GetPosPixel().X() < 0)
535                 pWindow->SetSizePixel(Size(0, nH));
536             aSize = pWindow->GetSizePixel();
537             auto nExcess = (pWindow->GetPosPixel().X() + aSize.Width()) - nW;
538             if (nExcess > 0)
539             {
540                 aSize.AdjustWidth( -nExcess );
541                 pWindow->SetSizePixel(aSize);
542             }
543         }
544 
545         nX = nW;
546     }
547 
548     return nX;
549 }
550 
IMPL_LINK(DbGridControl::NavigationBar,OnClick,Button *,pButton,void)551 IMPL_LINK(DbGridControl::NavigationBar, OnClick, Button *, pButton, void )
552 {
553     DbGridControl* pParent = static_cast<DbGridControl*>(GetParent());
554 
555     if (pParent->m_aMasterSlotExecutor.IsSet())
556     {
557         bool lResult = false;
558         if (pButton == m_aFirstBtn.get())
559             lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::First);
560         else if( pButton == m_aPrevBtn.get() )
561             lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::Prev);
562         else if( pButton == m_aNextBtn.get() )
563             lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::Next);
564         else if( pButton == m_aLastBtn.get() )
565             lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::Last);
566         else if( pButton == m_aNewBtn.get() )
567             lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::New);
568 
569         if (lResult)
570             // the link already handled it
571             return;
572     }
573 
574     if (pButton == m_aFirstBtn.get())
575         pParent->MoveToFirst();
576     else if( pButton == m_aPrevBtn.get() )
577         pParent->MoveToPrev();
578     else if( pButton == m_aNextBtn.get() )
579         pParent->MoveToNext();
580     else if( pButton == m_aLastBtn.get() )
581         pParent->MoveToLast();
582     else if( pButton == m_aNewBtn.get() )
583         pParent->AppendNew();
584 }
585 
InvalidateAll(sal_Int32 nCurrentPos,bool bAll)586 void DbGridControl::NavigationBar::InvalidateAll(sal_Int32 nCurrentPos, bool bAll)
587 {
588     if (m_nCurrentPos != nCurrentPos || nCurrentPos < 0 || bAll)
589     {
590         DbGridControl* pParent = static_cast<DbGridControl*>(GetParent());
591 
592         sal_Int32 nAdjustedRowCount = pParent->GetRowCount() - ((pParent->GetOptions() & DbGridControlOptions::Insert) ? 2 : 1);
593 
594         // check if everything needs to be invalidated
595         bAll = bAll || m_nCurrentPos <= 0;
596         bAll = bAll || nCurrentPos <= 0;
597         bAll = bAll || m_nCurrentPos >= nAdjustedRowCount;
598         bAll = bAll || nCurrentPos >= nAdjustedRowCount;
599 
600         if ( bAll )
601         {
602             m_nCurrentPos = nCurrentPos;
603             int i = 0;
604             while (ControlMap[i] != DbGridControlNavigationBarState::NONE)
605                 SetState(ControlMap[i++]);
606         }
607         else    // is in the center
608         {
609             m_nCurrentPos = nCurrentPos;
610             SetState(DbGridControlNavigationBarState::Count);
611             SetState(DbGridControlNavigationBarState::Absolute);
612         }
613     }
614 }
615 
GetState(DbGridControlNavigationBarState nWhich) const616 bool DbGridControl::NavigationBar::GetState(DbGridControlNavigationBarState nWhich) const
617 {
618     DbGridControl* pParent = static_cast<DbGridControl*>(GetParent());
619 
620     if (!pParent->IsOpen() || pParent->IsDesignMode() || !pParent->IsEnabled()
621         || pParent->IsFilterMode() )
622         return false;
623     else
624     {
625         // check if we have a master state provider
626         if (pParent->m_aMasterStateProvider.IsSet())
627         {
628             long nState = pParent->m_aMasterStateProvider.Call( nWhich );
629             if (nState>=0)
630                 return (nState>0);
631         }
632 
633         bool bAvailable = true;
634 
635         switch (nWhich)
636         {
637             case DbGridControlNavigationBarState::First:
638             case DbGridControlNavigationBarState::Prev:
639                 bAvailable = m_nCurrentPos > 0;
640                 break;
641             case DbGridControlNavigationBarState::Next:
642                 if(pParent->m_bRecordCountFinal)
643                 {
644                     bAvailable = m_nCurrentPos < pParent->GetRowCount() - 1;
645                     if (!bAvailable && pParent->GetOptions() & DbGridControlOptions::Insert)
646                         bAvailable = (m_nCurrentPos == pParent->GetRowCount() - 2) && pParent->IsModified();
647                 }
648                 break;
649             case DbGridControlNavigationBarState::Last:
650                 if(pParent->m_bRecordCountFinal)
651                 {
652                     if (pParent->GetOptions() & DbGridControlOptions::Insert)
653                         bAvailable = pParent->IsCurrentAppending() ? pParent->GetRowCount() > 1 :
654                                      m_nCurrentPos != pParent->GetRowCount() - 2;
655                     else
656                         bAvailable = m_nCurrentPos != pParent->GetRowCount() - 1;
657                 }
658                 break;
659             case DbGridControlNavigationBarState::New:
660                 bAvailable = (pParent->GetOptions() & DbGridControlOptions::Insert) && pParent->GetRowCount() && m_nCurrentPos < pParent->GetRowCount() - 1;
661                 break;
662             case DbGridControlNavigationBarState::Absolute:
663                 bAvailable = pParent->GetRowCount() > 0;
664                 break;
665             default: break;
666         }
667         return bAvailable;
668     }
669 }
670 
SetState(DbGridControlNavigationBarState nWhich)671 void DbGridControl::NavigationBar::SetState(DbGridControlNavigationBarState nWhich)
672 {
673     bool bAvailable = GetState(nWhich);
674     DbGridControl* pParent = static_cast<DbGridControl*>(GetParent());
675     vcl::Window* pWnd = nullptr;
676     switch (nWhich)
677     {
678         case DbGridControlNavigationBarState::First:
679             pWnd = m_aFirstBtn.get();
680             break;
681         case DbGridControlNavigationBarState::Prev:
682             pWnd = m_aPrevBtn.get();
683             break;
684         case DbGridControlNavigationBarState::Next:
685             pWnd = m_aNextBtn.get();
686             break;
687         case DbGridControlNavigationBarState::Last:
688             pWnd = m_aLastBtn.get();
689             break;
690         case DbGridControlNavigationBarState::New:
691             pWnd = m_aNewBtn.get();
692             break;
693         case DbGridControlNavigationBarState::Absolute:
694             pWnd = m_aAbsolute.get();
695             if (bAvailable)
696             {
697                 if (pParent->m_nTotalCount >= 0)
698                 {
699                     if (pParent->IsCurrentAppending())
700                         m_aAbsolute->SetMax(pParent->m_nTotalCount + 1);
701                     else
702                         m_aAbsolute->SetMax(pParent->m_nTotalCount);
703                 }
704                 else
705                     m_aAbsolute->SetMax(LONG_MAX);
706 
707                 m_aAbsolute->SetValue(m_nCurrentPos + 1);
708             }
709             else
710                 m_aAbsolute->SetText(OUString());
711             break;
712         case DbGridControlNavigationBarState::Text:
713             pWnd = m_aRecordText.get();
714             break;
715         case DbGridControlNavigationBarState::Of:
716             pWnd = m_aRecordOf.get();
717             break;
718         case DbGridControlNavigationBarState::Count:
719         {
720             pWnd = m_aRecordCount.get();
721             OUString aText;
722             if (bAvailable)
723             {
724                 if (pParent->GetOptions() & DbGridControlOptions::Insert)
725                 {
726                     if (pParent->IsCurrentAppending() && !pParent->IsModified())
727                         aText = m_aAbsolute->CreateFieldText(pParent->GetRowCount());
728                     else
729                         aText = m_aAbsolute->CreateFieldText(pParent->GetRowCount() - 1);
730                 }
731                 else
732                     aText = m_aAbsolute->CreateFieldText(pParent->GetRowCount());
733                 if(!pParent->m_bRecordCountFinal)
734                     aText += " *";
735             }
736             else
737                 aText.clear();
738 
739             // add the number of selected rows, if applicable
740             if (pParent->GetSelectRowCount())
741             {
742                 OUString aExtendedInfo = aText + " (" +
743                     m_aAbsolute->CreateFieldText(pParent->GetSelectRowCount()) + ")";
744                 pWnd->SetText(aExtendedInfo);
745             }
746             else
747                 pWnd->SetText(aText);
748 
749             pParent->SetRealRowCount(aText);
750         }   break;
751         default: break;
752     }
753     DBG_ASSERT(pWnd, "no window");
754     if (pWnd && (pWnd->IsEnabled() != bAvailable))
755         // this "pWnd->IsEnabled() != bAvailable" is a little hack : Window::Enable always generates a user
756         // event (ImplGenerateMouseMove) even if nothing happened. This may lead to some unwanted effects, so we
757         // do this check.
758         // For further explanation see Bug 69900.
759         pWnd->Enable(bAvailable);
760 }
761 
Resize()762 void DbGridControl::NavigationBar::Resize()
763 {
764     Control::Resize();
765     ArrangeControls();
766 }
767 
Paint(vcl::RenderContext & rRenderContext,const tools::Rectangle & rRect)768 void DbGridControl::NavigationBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
769 {
770     Control::Paint(rRenderContext, rRect);
771     Point aAbsolutePos = m_aAbsolute->GetPosPixel();
772     Size  aAbsoluteSize = m_aAbsolute->GetSizePixel();
773 
774     rRenderContext.DrawLine(Point(aAbsolutePos.X() - 1, 0 ),
775                             Point(aAbsolutePos.X() - 1, aAbsolutePos.Y() + aAbsoluteSize.Height()));
776 
777     rRenderContext.DrawLine(Point(aAbsolutePos.X() + aAbsoluteSize.Width() + 1, 0 ),
778                             Point(aAbsolutePos.X() + aAbsoluteSize.Width() + 1, aAbsolutePos.Y() + aAbsoluteSize.Height()));
779 }
780 
StateChanged(StateChangedType nType)781 void DbGridControl::NavigationBar::StateChanged(StateChangedType nType)
782 {
783     Control::StateChanged(nType);
784 
785     vcl::Window* pWindows[] =
786     {
787         m_aRecordText.get(),
788         m_aAbsolute.get(),
789         m_aRecordOf.get(),
790         m_aRecordCount.get(),
791         m_aFirstBtn.get(),
792         m_aPrevBtn.get(),
793         m_aNextBtn.get(),
794         m_aLastBtn.get(),
795         m_aNewBtn.get()
796     };
797 
798     switch ( nType )
799     {
800         case StateChangedType::Mirroring:
801         {
802             bool bIsRTLEnabled = IsRTLEnabled();
803             for (vcl::Window* pWindow : pWindows)
804                 pWindow->EnableRTL( bIsRTLEnabled );
805         }
806         break;
807 
808         case StateChangedType::Zoom:
809         {
810             Fraction aZoom = GetZoom();
811 
812             // not all of these controls need to know the new zoom, but to be sure ...
813             vcl::Font aFont(GetSettings().GetStyleSettings().GetToolFont());
814             if (IsControlFont())
815                 aFont.Merge(GetControlFont());
816 
817             for (vcl::Window* pWindow : pWindows)
818             {
819                 pWindow->SetZoom(aZoom);
820                 pWindow->SetZoomedPointFont(*pWindow, aFont);
821             }
822 
823             SetZoomedPointFont(*this, aFont);
824 
825             // rearrange the controls
826             ArrangeControls();
827         }
828         break;
829         default:;
830     }
831 }
832 
DbGridRow()833 DbGridRow::DbGridRow():m_eStatus(GridRowStatus::Clean), m_bIsNew(true)
834 {}
835 
DbGridRow(CursorWrapper * pCur,bool bPaintCursor)836 DbGridRow::DbGridRow(CursorWrapper* pCur, bool bPaintCursor)
837           :m_bIsNew(false)
838 {
839 
840     if (pCur && pCur->Is())
841     {
842         Reference< XIndexAccess >  xColumns(pCur->getColumns(), UNO_QUERY);
843         for (sal_Int32 i = 0; i < xColumns->getCount(); ++i)
844         {
845             Reference< XPropertySet > xColSet(
846                 xColumns->getByIndex(i), css::uno::UNO_QUERY);
847             m_aVariants.emplace_back( new DataColumn(xColSet) );
848         }
849 
850         if (pCur->rowDeleted())
851             m_eStatus = GridRowStatus::Deleted;
852         else
853         {
854             if (bPaintCursor)
855                 m_eStatus = (pCur->isAfterLast() || pCur->isBeforeFirst()) ? GridRowStatus::Invalid : GridRowStatus::Clean;
856             else
857             {
858                 const Reference< XPropertySet >& xSet = pCur->getPropertySet();
859                 if (xSet.is())
860                 {
861                     m_bIsNew = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISNEW));
862                     if (!m_bIsNew && (pCur->isAfterLast() || pCur->isBeforeFirst()))
863                         m_eStatus = GridRowStatus::Invalid;
864                     else if (::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISMODIFIED)))
865                         m_eStatus = GridRowStatus::Modified;
866                     else
867                         m_eStatus = GridRowStatus::Clean;
868                 }
869                 else
870                     m_eStatus = GridRowStatus::Invalid;
871             }
872         }
873         if (!m_bIsNew && IsValid())
874             m_aBookmark = pCur->getBookmark();
875         else
876             m_aBookmark = Any();
877     }
878     else
879         m_eStatus = GridRowStatus::Invalid;
880 }
881 
~DbGridRow()882 DbGridRow::~DbGridRow()
883 {
884 }
885 
SetState(CursorWrapper * pCur,bool bPaintCursor)886 void DbGridRow::SetState(CursorWrapper* pCur, bool bPaintCursor)
887 {
888     if (pCur && pCur->Is())
889     {
890         if (pCur->rowDeleted())
891         {
892             m_eStatus = GridRowStatus::Deleted;
893             m_bIsNew = false;
894         }
895         else
896         {
897             m_eStatus = GridRowStatus::Clean;
898             if (!bPaintCursor)
899             {
900                 const Reference< XPropertySet >& xSet = pCur->getPropertySet();
901                 DBG_ASSERT(xSet.is(), "DbGridRow::SetState : invalid cursor !");
902 
903                 if (::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISMODIFIED)))
904                     m_eStatus = GridRowStatus::Modified;
905                 m_bIsNew = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISNEW));
906             }
907             else
908                 m_bIsNew = false;
909         }
910 
911         try
912         {
913             if (!m_bIsNew && IsValid())
914                 m_aBookmark = pCur->getBookmark();
915             else
916                 m_aBookmark = Any();
917         }
918         catch(SQLException&)
919         {
920             DBG_UNHANDLED_EXCEPTION("svx");
921             m_aBookmark = Any();
922             m_eStatus = GridRowStatus::Invalid;
923             m_bIsNew = false;
924         }
925     }
926     else
927     {
928         m_aBookmark = Any();
929         m_eStatus = GridRowStatus::Invalid;
930         m_bIsNew = false;
931     }
932 }
933 
DbGridControl(Reference<XComponentContext> const & _rxContext,vcl::Window * pParent,WinBits nBits)934 DbGridControl::DbGridControl(
935                 Reference< XComponentContext > const & _rxContext,
936                 vcl::Window* pParent,
937                 WinBits nBits)
938             :EditBrowseBox(pParent, EditBrowseBoxFlags::NONE, nBits, DEFAULT_BROWSE_MODE )
939             ,m_xContext(_rxContext)
940             ,m_aBar(VclPtr<DbGridControl::NavigationBar>::Create(this))
941             ,m_nAsynAdjustEvent(nullptr)
942             ,m_pDataSourcePropListener(nullptr)
943             ,m_pFieldListeners(nullptr)
944             ,m_pGridListener(nullptr)
945             ,m_nSeekPos(-1)
946             ,m_nTotalCount(-1)
947             ,m_aNullDate(::dbtools::DBTypeConversion::getStandardDate())
948             ,m_nMode(DEFAULT_BROWSE_MODE)
949             ,m_nCurrentPos(-1)
950             ,m_nDeleteEvent(nullptr)
951             ,m_nOptions(DbGridControlOptions::Readonly)
952             ,m_nOptionMask(DbGridControlOptions::Insert | DbGridControlOptions::Update | DbGridControlOptions::Delete)
953             ,m_nLastColId(sal_uInt16(-1))
954             ,m_nLastRowId(-1)
955             ,m_bDesignMode(false)
956             ,m_bRecordCountFinal(false)
957             ,m_bNavigationBar(true)
958             ,m_bSynchDisplay(true)
959             ,m_bHandle(true)
960             ,m_bFilterMode(false)
961             ,m_bWantDestruction(false)
962             ,m_bPendingAdjustRows(false)
963             ,m_bHideScrollbars( false )
964             ,m_bUpdating(false)
965 {
966 
967     OUString sName(SvxResId(RID_STR_NAVIGATIONBAR));
968     m_aBar->SetAccessibleName(sName);
969     m_aBar->Show();
970     ImplInitWindow( InitWindowFacet::All );
971 }
972 
InsertHandleColumn()973 void DbGridControl::InsertHandleColumn()
974 {
975     // BrowseBox has problems when painting without a handleColumn (hide it here)
976     if (HasHandle())
977         BrowseBox::InsertHandleColumn(GetDefaultColumnWidth(OUString()));
978     else
979         BrowseBox::InsertHandleColumn(0);
980 }
981 
Init()982 void DbGridControl::Init()
983 {
984     VclPtr<BrowserHeader> pNewHeader = CreateHeaderBar(this);
985     pHeader->SetMouseTransparent(false);
986 
987     SetHeaderBar(pNewHeader);
988     SetMode(m_nMode);
989     SetCursorColor(Color(0xFF, 0, 0));
990 
991     InsertHandleColumn();
992 }
993 
~DbGridControl()994 DbGridControl::~DbGridControl()
995 {
996     disposeOnce();
997 }
998 
dispose()999 void DbGridControl::dispose()
1000 {
1001     if (!IsDisposed())
1002     {
1003         RemoveColumns();
1004 
1005         m_bWantDestruction = true;
1006         osl::MutexGuard aGuard(m_aDestructionSafety);
1007         if (m_pFieldListeners)
1008             DisconnectFromFields();
1009         m_pCursorDisposeListener.reset();
1010     }
1011 
1012     if (m_nDeleteEvent)
1013         Application::RemoveUserEvent(m_nDeleteEvent);
1014 
1015     if (m_pDataSourcePropMultiplexer.is())
1016     {
1017         m_pDataSourcePropMultiplexer->dispose();
1018         m_pDataSourcePropMultiplexer.clear();    // this should delete the multiplexer
1019         delete m_pDataSourcePropListener;
1020         m_pDataSourcePropListener = nullptr;
1021     }
1022     m_xRowSetListener.clear();
1023 
1024     m_pDataCursor.reset();
1025     m_pSeekCursor.reset();
1026 
1027     m_aBar.disposeAndClear();
1028 
1029     EditBrowseBox::dispose();
1030 }
1031 
StateChanged(StateChangedType nType)1032 void DbGridControl::StateChanged( StateChangedType nType )
1033 {
1034     EditBrowseBox::StateChanged( nType );
1035 
1036     switch (nType)
1037     {
1038         case StateChangedType::Mirroring:
1039             ImplInitWindow( InitWindowFacet::WritingMode );
1040             Invalidate();
1041             break;
1042 
1043         case StateChangedType::Zoom:
1044         {
1045             ImplInitWindow( InitWindowFacet::Font );
1046 
1047             // and give it a chance to rearrange
1048             Point aPoint = GetControlArea().TopLeft();
1049             sal_uInt16 nX = static_cast<sal_uInt16>(aPoint.X());
1050             ArrangeControls(nX, static_cast<sal_uInt16>(aPoint.Y()));
1051             ReserveControlArea(nX);
1052         }
1053         break;
1054         case StateChangedType::ControlFont:
1055             ImplInitWindow( InitWindowFacet::Font );
1056             Invalidate();
1057             break;
1058         case StateChangedType::ControlForeground:
1059             ImplInitWindow( InitWindowFacet::Foreground );
1060             Invalidate();
1061             break;
1062         case StateChangedType::ControlBackground:
1063             ImplInitWindow( InitWindowFacet::Background );
1064             Invalidate();
1065             break;
1066        default:;
1067     }
1068 }
1069 
DataChanged(const DataChangedEvent & rDCEvt)1070 void DbGridControl::DataChanged( const DataChangedEvent& rDCEvt )
1071 {
1072     EditBrowseBox::DataChanged( rDCEvt );
1073     if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS ) &&
1074          (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
1075     {
1076         ImplInitWindow( InitWindowFacet::All );
1077         Invalidate();
1078     }
1079 }
1080 
Select()1081 void DbGridControl::Select()
1082 {
1083     EditBrowseBox::Select();
1084 
1085     // as the selected rows may have changed, update the according display in our navigation bar
1086     m_aBar->InvalidateState(DbGridControlNavigationBarState::Count);
1087 
1088     if (m_pGridListener)
1089         m_pGridListener->selectionChanged();
1090 }
1091 
ImplInitWindow(const InitWindowFacet _eInitWhat)1092 void DbGridControl::ImplInitWindow( const InitWindowFacet _eInitWhat )
1093 {
1094     for (auto const & pCol : m_aColumns)
1095     {
1096         pCol->ImplInitWindow( GetDataWindow(), _eInitWhat );
1097     }
1098 
1099     if ( _eInitWhat & InitWindowFacet::WritingMode )
1100     {
1101         if ( m_bNavigationBar )
1102         {
1103             m_aBar->EnableRTL( IsRTLEnabled() );
1104         }
1105     }
1106 
1107     if ( _eInitWhat & InitWindowFacet::Font )
1108     {
1109         if ( m_bNavigationBar )
1110         {
1111             vcl::Font aFont = m_aBar->GetSettings().GetStyleSettings().GetToolFont();
1112             if ( IsControlFont() )
1113                 m_aBar->SetControlFont( GetControlFont() );
1114             else
1115                 m_aBar->SetControlFont();
1116 
1117             m_aBar->SetZoom( GetZoom() );
1118         }
1119     }
1120 
1121     if ( _eInitWhat & InitWindowFacet::Background )
1122     {
1123         if (IsControlBackground())
1124         {
1125             GetDataWindow().SetBackground(GetControlBackground());
1126             GetDataWindow().SetControlBackground(GetControlBackground());
1127             GetDataWindow().SetFillColor(GetControlBackground());
1128         }
1129         else
1130         {
1131             GetDataWindow().SetControlBackground();
1132             GetDataWindow().SetFillColor(GetFillColor());
1133         }
1134     }
1135 }
1136 
RemoveRows(bool bNewCursor)1137 void DbGridControl::RemoveRows(bool bNewCursor)
1138 {
1139     // Did the data cursor change?
1140     if (!bNewCursor)
1141     {
1142         m_pSeekCursor.reset();
1143         m_xPaintRow = m_xDataRow = m_xEmptyRow  = m_xCurrentRow = m_xSeekRow = nullptr;
1144         m_nCurrentPos = m_nSeekPos = -1;
1145         m_nOptions  = DbGridControlOptions::Readonly;
1146 
1147         RowRemoved(0, GetRowCount(), false);
1148         m_nTotalCount = -1;
1149     }
1150     else
1151     {
1152         RemoveRows();
1153     }
1154 }
1155 
RemoveRows()1156 void DbGridControl::RemoveRows()
1157 {
1158     // we're going to remove all columns and all row, so deactivate the current cell
1159     if (IsEditing())
1160         DeactivateCell();
1161 
1162     // de-initialize all columns
1163     // if there are columns, free all controllers
1164     for (auto const & pColumn : m_aColumns)
1165         pColumn->Clear();
1166 
1167     m_pSeekCursor.reset();
1168     m_pDataCursor.reset();
1169 
1170     m_xPaintRow = m_xDataRow = m_xEmptyRow  = m_xCurrentRow = m_xSeekRow = nullptr;
1171     m_nCurrentPos = m_nSeekPos = m_nTotalCount  = -1;
1172     m_nOptions  = DbGridControlOptions::Readonly;
1173 
1174     // reset number of sentences to zero in the browser
1175     EditBrowseBox::RemoveRows();
1176     m_aBar->InvalidateAll(m_nCurrentPos, true);
1177 }
1178 
ArrangeControls(sal_uInt16 & nX,sal_uInt16 nY)1179 void DbGridControl::ArrangeControls(sal_uInt16& nX, sal_uInt16 nY)
1180 {
1181     // positioning of the controls
1182     if (m_bNavigationBar)
1183     {
1184         tools::Rectangle aRect(GetControlArea());
1185         m_aBar->SetPosSizePixel(Point(0, nY + 1), Size(aRect.GetSize().Width(), aRect.GetSize().Height() - 1));
1186         nX = m_aBar->ArrangeControls();
1187     }
1188 }
1189 
EnableHandle(bool bEnable)1190 void DbGridControl::EnableHandle(bool bEnable)
1191 {
1192     if (m_bHandle == bEnable)
1193         return;
1194 
1195     // HandleColumn is only hidden because there are a lot of problems while painting otherwise
1196     RemoveColumn( HandleColumnId );
1197     m_bHandle = bEnable;
1198     InsertHandleColumn();
1199 }
1200 
1201 namespace
1202 {
adjustModeForScrollbars(BrowserMode & _rMode,bool _bNavigationBar,bool _bHideScrollbars)1203     bool adjustModeForScrollbars( BrowserMode& _rMode, bool _bNavigationBar, bool _bHideScrollbars )
1204     {
1205         BrowserMode nOldMode = _rMode;
1206 
1207         if ( !_bNavigationBar )
1208         {
1209             _rMode &= ~BrowserMode::AUTO_HSCROLL;
1210         }
1211 
1212         if ( _bHideScrollbars )
1213         {
1214             _rMode |= BrowserMode::NO_HSCROLL | BrowserMode::NO_VSCROLL;
1215             _rMode &= ~BrowserMode( BrowserMode::AUTO_HSCROLL | BrowserMode::AUTO_VSCROLL );
1216         }
1217         else
1218         {
1219             _rMode |= BrowserMode::AUTO_HSCROLL | BrowserMode::AUTO_VSCROLL;
1220             _rMode &= ~BrowserMode( BrowserMode::NO_HSCROLL | BrowserMode::NO_VSCROLL );
1221         }
1222 
1223         // note: if we have a navigation bar, we always have an AUTO_HSCROLL. In particular,
1224         // _bHideScrollbars is ignored then
1225         if ( _bNavigationBar )
1226         {
1227             _rMode |= BrowserMode::AUTO_HSCROLL;
1228             _rMode &= ~BrowserMode::NO_HSCROLL;
1229         }
1230 
1231         return nOldMode != _rMode;
1232     }
1233 }
1234 
EnableNavigationBar(bool bEnable)1235 void DbGridControl::EnableNavigationBar(bool bEnable)
1236 {
1237     if (m_bNavigationBar == bEnable)
1238         return;
1239 
1240     m_bNavigationBar = bEnable;
1241 
1242     if (bEnable)
1243     {
1244         m_aBar->Show();
1245         m_aBar->Enable();
1246         m_aBar->InvalidateAll(m_nCurrentPos, true);
1247 
1248         if ( adjustModeForScrollbars( m_nMode, m_bNavigationBar, m_bHideScrollbars ) )
1249             SetMode( m_nMode );
1250 
1251         // get size of the reserved ControlArea
1252         Point aPoint = GetControlArea().TopLeft();
1253         sal_uInt16 nX = static_cast<sal_uInt16>(aPoint.X());
1254 
1255         ArrangeControls(nX, static_cast<sal_uInt16>(aPoint.Y()));
1256         ReserveControlArea(nX);
1257     }
1258     else
1259     {
1260         m_aBar->Hide();
1261         m_aBar->Disable();
1262 
1263         if ( adjustModeForScrollbars( m_nMode, m_bNavigationBar, m_bHideScrollbars ) )
1264             SetMode( m_nMode );
1265 
1266         ReserveControlArea();
1267     }
1268 }
1269 
SetOptions(DbGridControlOptions nOpt)1270 DbGridControlOptions DbGridControl::SetOptions(DbGridControlOptions nOpt)
1271 {
1272     DBG_ASSERT(!m_xCurrentRow.is() || !m_xCurrentRow->IsModified(),
1273         "DbGridControl::SetOptions : please do not call when editing a record (things are much easier this way ;) !");
1274 
1275     // for the next setDataSource (which is triggered by a refresh, for instance)
1276     m_nOptionMask = nOpt;
1277 
1278     // normalize the new options
1279     Reference< XPropertySet > xDataSourceSet = m_pDataCursor->getPropertySet();
1280     if (xDataSourceSet.is())
1281     {
1282         // check what kind of options are available
1283         sal_Int32 nPrivileges = 0;
1284         xDataSourceSet->getPropertyValue(FM_PROP_PRIVILEGES) >>= nPrivileges;
1285         if ((nPrivileges & Privilege::INSERT) == 0)
1286             nOpt &= ~DbGridControlOptions::Insert;
1287         if ((nPrivileges & Privilege::UPDATE) == 0)
1288             nOpt &= ~DbGridControlOptions::Update;
1289         if ((nPrivileges & Privilege::DELETE) == 0)
1290             nOpt &= ~DbGridControlOptions::Delete;
1291     }
1292     else
1293         nOpt = DbGridControlOptions::Readonly;
1294 
1295     // need to do something after that ?
1296     if (nOpt == m_nOptions)
1297         return m_nOptions;
1298 
1299     // the 'update' option only affects our BrowserMode (with or w/o focus rect)
1300     BrowserMode nNewMode = m_nMode;
1301     if (!(m_nMode & BrowserMode::CURSOR_WO_FOCUS))
1302     {
1303         if (nOpt & DbGridControlOptions::Update)
1304             nNewMode |= BrowserMode::HIDECURSOR;
1305         else
1306             nNewMode &= ~BrowserMode::HIDECURSOR;
1307     }
1308     else
1309         nNewMode &= ~BrowserMode::HIDECURSOR;
1310         // should not be necessary if EnablePermanentCursor is used to change the cursor behaviour, but to be sure ...
1311 
1312     if (nNewMode != m_nMode)
1313     {
1314         SetMode(nNewMode);
1315         m_nMode = nNewMode;
1316     }
1317 
1318     // _after_ setting the mode because this results in an ActivateCell
1319     DeactivateCell();
1320 
1321     bool bInsertChanged = (nOpt & DbGridControlOptions::Insert) != (m_nOptions & DbGridControlOptions::Insert);
1322     m_nOptions = nOpt;
1323         // we need to set this before the code below because it indirectly uses m_nOptions
1324 
1325     // the 'insert' option affects our empty row
1326     if (bInsertChanged)
1327     {
1328         if (m_nOptions & DbGridControlOptions::Insert)
1329         {   // the insert option is to be set
1330             m_xEmptyRow = new DbGridRow();
1331             RowInserted(GetRowCount());
1332         }
1333         else
1334         {   // the insert option is to be reset
1335             m_xEmptyRow = nullptr;
1336             if ((GetCurRow() == GetRowCount() - 1) && (GetCurRow() > 0))
1337                 GoToRowColumnId(GetCurRow() - 1, GetCurColumnId());
1338             RowRemoved(GetRowCount());
1339         }
1340     }
1341 
1342     // the 'delete' options has no immediate consequences
1343 
1344     ActivateCell();
1345     Invalidate();
1346     return m_nOptions;
1347 }
1348 
ForceHideScrollbars()1349 void DbGridControl::ForceHideScrollbars()
1350 {
1351     if ( m_bHideScrollbars )
1352         return;
1353 
1354     m_bHideScrollbars = true;
1355 
1356     if ( adjustModeForScrollbars( m_nMode, m_bNavigationBar, m_bHideScrollbars ) )
1357         SetMode( m_nMode );
1358 }
1359 
EnablePermanentCursor(bool bEnable)1360 void DbGridControl::EnablePermanentCursor(bool bEnable)
1361 {
1362     if (IsPermanentCursorEnabled() == bEnable)
1363         return;
1364 
1365     if (bEnable)
1366     {
1367         m_nMode &= ~BrowserMode::HIDECURSOR;     // without this BrowserMode::CURSOR_WO_FOCUS won't have any affect
1368         m_nMode |= BrowserMode::CURSOR_WO_FOCUS;
1369     }
1370     else
1371     {
1372         if (m_nOptions & DbGridControlOptions::Update)
1373             m_nMode |= BrowserMode::HIDECURSOR;      // no cursor at all
1374         else
1375             m_nMode &= ~BrowserMode::HIDECURSOR;     // at least the "non-permanent" cursor
1376 
1377         m_nMode &= ~BrowserMode::CURSOR_WO_FOCUS;
1378     }
1379     SetMode(m_nMode);
1380 
1381     bool bWasEditing = IsEditing();
1382     DeactivateCell();
1383     if (bWasEditing)
1384         ActivateCell();
1385 }
1386 
IsPermanentCursorEnabled() const1387 bool DbGridControl::IsPermanentCursorEnabled() const
1388 {
1389     return (m_nMode & BrowserMode::CURSOR_WO_FOCUS) && !(m_nMode & BrowserMode::HIDECURSOR);
1390 }
1391 
refreshController(sal_uInt16 _nColId,GrantControlAccess)1392 void DbGridControl::refreshController(sal_uInt16 _nColId, GrantControlAccess /*_aAccess*/)
1393 {
1394     if ((GetCurColumnId() == _nColId) && IsEditing())
1395     {   // the controller which is currently active needs to be refreshed
1396         DeactivateCell();
1397         ActivateCell();
1398     }
1399 }
1400 
setDataSource(const Reference<XRowSet> & _xCursor,DbGridControlOptions nOpts)1401 void DbGridControl::setDataSource(const Reference< XRowSet >& _xCursor, DbGridControlOptions nOpts)
1402 {
1403     if (!_xCursor.is() && !m_pDataCursor)
1404         return;
1405 
1406     if (m_pDataSourcePropMultiplexer.is())
1407     {
1408         m_pDataSourcePropMultiplexer->dispose();
1409         m_pDataSourcePropMultiplexer.clear();    // this should delete the multiplexer
1410         delete m_pDataSourcePropListener;
1411         m_pDataSourcePropListener = nullptr;
1412     }
1413     m_xRowSetListener.clear();
1414 
1415     // is the new cursor valid ?
1416     // the cursor is only valid if it contains some columns
1417     // if there is no cursor or the cursor is not valid we have to clean up and leave
1418     if (!_xCursor.is() || !Reference< XColumnsSupplier > (_xCursor, UNO_QUERY_THROW)->getColumns()->hasElements())
1419     {
1420         RemoveRows();
1421         return;
1422     }
1423 
1424     // did the data cursor change?
1425     sal_uInt16 nCurPos = GetColumnPos(GetCurColumnId());
1426 
1427     SetUpdateMode(false);
1428     RemoveRows();
1429     DisconnectFromFields();
1430 
1431     m_pCursorDisposeListener.reset();
1432 
1433     {
1434         ::osl::MutexGuard aGuard(m_aAdjustSafety);
1435         if (m_nAsynAdjustEvent)
1436         {
1437             // the adjust was thought to work with the old cursor which we don't have anymore
1438             RemoveUserEvent(m_nAsynAdjustEvent);
1439             m_nAsynAdjustEvent = nullptr;
1440         }
1441     }
1442 
1443     // get a new formatter and data cursor
1444     m_xFormatter = nullptr;
1445     Reference< css::util::XNumberFormatsSupplier >  xSupplier = getNumberFormats(getConnection(_xCursor), true);
1446     if (xSupplier.is())
1447     {
1448         m_xFormatter = css::util::NumberFormatter::create(m_xContext);
1449         m_xFormatter->attachNumberFormatsSupplier(xSupplier);
1450 
1451         // retrieve the datebase of the Numberformatter
1452         try
1453         {
1454             xSupplier->getNumberFormatSettings()->getPropertyValue("NullDate") >>= m_aNullDate;
1455         }
1456         catch(Exception&)
1457         {
1458         }
1459     }
1460 
1461     m_pDataCursor.reset(new CursorWrapper(_xCursor));
1462 
1463     // now create a cursor for painting rows
1464     // we need that cursor only if we are not in insert only mode
1465     Reference< XResultSet > xClone;
1466     Reference< XResultSetAccess > xAccess( _xCursor, UNO_QUERY );
1467     try
1468     {
1469         xClone = xAccess.is() ? xAccess->createResultSet() : Reference< XResultSet > ();
1470     }
1471     catch(Exception&)
1472     {
1473     }
1474     if (xClone.is())
1475         m_pSeekCursor.reset(new CursorWrapper(xClone));
1476 
1477     // property listening on the data source
1478     // (Normally one class would be sufficient : the multiplexer which could forward the property change to us.
1479     // But for that we would have been derived from ::comphelper::OPropertyChangeListener, which isn't exported.
1480     // So we introduce a second class, which is a ::comphelper::OPropertyChangeListener (in the implementation file we know this class)
1481     // and forwards the property changes to our special method "DataSourcePropertyChanged".)
1482     if (m_pDataCursor)
1483     {
1484         m_pDataSourcePropListener = new FmXGridSourcePropListener(this);
1485         m_pDataSourcePropMultiplexer = new ::comphelper::OPropertyChangeMultiplexer(m_pDataSourcePropListener, m_pDataCursor->getPropertySet() );
1486         m_pDataSourcePropMultiplexer->addProperty(FM_PROP_ISMODIFIED);
1487         m_pDataSourcePropMultiplexer->addProperty(FM_PROP_ISNEW);
1488     }
1489 
1490     BrowserMode nOldMode = m_nMode;
1491     if (m_pSeekCursor)
1492     {
1493         try
1494         {
1495             Reference< XPropertySet >  xSet(_xCursor, UNO_QUERY);
1496             if (xSet.is())
1497             {
1498                 // check what kind of options are available
1499                 sal_Int32 nConcurrency = ResultSetConcurrency::READ_ONLY;
1500                 xSet->getPropertyValue(FM_PROP_RESULTSET_CONCURRENCY) >>= nConcurrency;
1501 
1502                 if ( ResultSetConcurrency::UPDATABLE == nConcurrency )
1503                 {
1504                     sal_Int32 nPrivileges = 0;
1505                     xSet->getPropertyValue(FM_PROP_PRIVILEGES) >>= nPrivileges;
1506 
1507                     // Insert Option should be set if insert only otherwise you won't see any rows
1508                     // and no insertion is possible
1509                     if ((m_nOptionMask & DbGridControlOptions::Insert)
1510                         && ((nPrivileges & Privilege::INSERT) == Privilege::INSERT) && (nOpts & DbGridControlOptions::Insert))
1511                         m_nOptions |= DbGridControlOptions::Insert;
1512                     if ((m_nOptionMask & DbGridControlOptions::Update)
1513                         && ((nPrivileges & Privilege::UPDATE) == Privilege::UPDATE) && (nOpts & DbGridControlOptions::Update))
1514                         m_nOptions |= DbGridControlOptions::Update;
1515                     if ((m_nOptionMask & DbGridControlOptions::Delete)
1516                         && ((nPrivileges & Privilege::DELETE) == Privilege::DELETE) && (nOpts & DbGridControlOptions::Delete))
1517                         m_nOptions |= DbGridControlOptions::Delete;
1518                 }
1519             }
1520         }
1521         catch( const Exception& )
1522         {
1523             DBG_UNHANDLED_EXCEPTION("svx");
1524         }
1525 
1526         bool bPermanentCursor = IsPermanentCursorEnabled();
1527         m_nMode = DEFAULT_BROWSE_MODE;
1528 
1529         if ( bPermanentCursor )
1530         {
1531             m_nMode |= BrowserMode::CURSOR_WO_FOCUS;
1532             m_nMode &= ~BrowserMode::HIDECURSOR;
1533         }
1534         else
1535         {
1536             // updates are allowed -> no focus rectangle
1537             if ( m_nOptions & DbGridControlOptions::Update )
1538                 m_nMode |= BrowserMode::HIDECURSOR;
1539         }
1540 
1541         m_nMode |= BrowserMode::MULTISELECTION;
1542 
1543         adjustModeForScrollbars( m_nMode, m_bNavigationBar, m_bHideScrollbars );
1544 
1545         Reference< XColumnsSupplier >  xSupplyColumns(_xCursor, UNO_QUERY);
1546         if (xSupplyColumns.is())
1547             InitColumnsByFields(Reference< XIndexAccess > (xSupplyColumns->getColumns(), UNO_QUERY));
1548 
1549         ConnectToFields();
1550     }
1551 
1552     sal_uInt32 nRecordCount(0);
1553 
1554     if (m_pSeekCursor)
1555     {
1556         Reference< XPropertySet > xSet = m_pDataCursor->getPropertySet();
1557         xSet->getPropertyValue(FM_PROP_ROWCOUNT) >>= nRecordCount;
1558         m_bRecordCountFinal = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ROWCOUNTFINAL));
1559 
1560         m_xRowSetListener = new RowSetEventListener(this);
1561         Reference< XRowsChangeBroadcaster> xChangeBroad(xSet,UNO_QUERY);
1562         if ( xChangeBroad.is( ) )
1563             xChangeBroad->addRowsChangeListener(m_xRowSetListener);
1564 
1565 
1566         // insert the currently known rows
1567         // and one row if we are able to insert rows
1568         if (m_nOptions & DbGridControlOptions::Insert)
1569         {
1570             // insert the empty row for insertion
1571             m_xEmptyRow = new DbGridRow();
1572             ++nRecordCount;
1573         }
1574         if (nRecordCount)
1575         {
1576             m_xPaintRow = m_xSeekRow = new DbGridRow(m_pSeekCursor.get(), true);
1577             m_xDataRow  = new DbGridRow(m_pDataCursor.get(), false);
1578             RowInserted(0, nRecordCount, false);
1579 
1580             if (m_xSeekRow->IsValid())
1581                 try
1582                 {
1583                     m_nSeekPos = m_pSeekCursor->getRow() - 1;
1584                 }
1585                 catch( const Exception& )
1586                 {
1587                     DBG_UNHANDLED_EXCEPTION("svx");
1588                     m_nSeekPos = -1;
1589                 }
1590         }
1591         else
1592         {
1593             // no rows so we don't need a seekcursor
1594             m_pSeekCursor.reset();
1595         }
1596     }
1597 
1598     // go to the old column
1599     if (nCurPos == BROWSER_INVALIDID || nCurPos >= ColCount())
1600         nCurPos = 0;
1601 
1602     // Column zero is a valid choice and guaranteed to exist,
1603     // but invisible to the user; if we have at least one
1604     // user-visible column, go to that one.
1605     if (nCurPos == 0 && ColCount() > 1)
1606         nCurPos = 1;
1607 
1608     // there are rows so go to the selected current column
1609     if (nRecordCount)
1610         GoToRowColumnId(0, GetColumnId(nCurPos));
1611     // else stop the editing if necessary
1612     else if (IsEditing())
1613         DeactivateCell();
1614 
1615     // now reset the mode
1616     if (m_nMode != nOldMode)
1617         SetMode(m_nMode);
1618 
1619     // RecalcRows was already called while resizing
1620     if (!IsResizing() && GetRowCount())
1621         RecalcRows(GetTopRow(), GetVisibleRows(), true);
1622 
1623     m_aBar->InvalidateAll(m_nCurrentPos, true);
1624     SetUpdateMode(true);
1625 
1626     // start listening on the seek cursor
1627     if (m_pSeekCursor)
1628         m_pCursorDisposeListener.reset(new DisposeListenerGridBridge(*this, Reference< XComponent > (Reference< XInterface >(*m_pSeekCursor), UNO_QUERY)));
1629 }
1630 
RemoveColumns()1631 void DbGridControl::RemoveColumns()
1632 {
1633     if ( IsEditing() )
1634         DeactivateCell();
1635 
1636     m_aColumns.clear();
1637 
1638     EditBrowseBox::RemoveColumns();
1639 }
1640 
CreateColumn(sal_uInt16 nId) const1641 std::unique_ptr<DbGridColumn> DbGridControl::CreateColumn(sal_uInt16 nId) const
1642 {
1643     return std::unique_ptr<DbGridColumn>(new DbGridColumn(nId, *const_cast<DbGridControl*>(this)));
1644 }
1645 
AppendColumn(const OUString & rName,sal_uInt16 nWidth,sal_uInt16 nModelPos,sal_uInt16 nId)1646 sal_uInt16 DbGridControl::AppendColumn(const OUString& rName, sal_uInt16 nWidth, sal_uInt16 nModelPos, sal_uInt16 nId)
1647 {
1648     DBG_ASSERT(nId == BROWSER_INVALIDID, "DbGridControl::AppendColumn : I want to set the ID myself ...");
1649     sal_uInt16 nRealPos = nModelPos;
1650     if (nModelPos != HEADERBAR_APPEND)
1651     {
1652         // calc the view pos. we can't use our converting functions because the new column
1653         // has no VCL-representation, yet.
1654         sal_Int16 nViewPos = nModelPos;
1655         while (nModelPos--)
1656         {
1657             if ( m_aColumns[ nModelPos ]->IsHidden() )
1658                 --nViewPos;
1659         }
1660         // restore nModelPos, we need it later
1661         nModelPos = nRealPos;
1662         // the position the base class gets is the view pos + 1 (because of the handle column)
1663         nRealPos = nViewPos + 1;
1664     }
1665 
1666     // calculate the new id
1667     for (nId=1; (GetModelColumnPos(nId) != GRID_COLUMN_NOT_FOUND) && size_t(nId) <= m_aColumns.size(); ++nId)
1668         ;
1669     DBG_ASSERT(GetViewColumnPos(nId) == GRID_COLUMN_NOT_FOUND, "DbGridControl::AppendColumn : inconsistent internal state !");
1670         // my column's models say "there is no column with id nId", but the view (the base class) says "there is a column ..."
1671 
1672     EditBrowseBox::AppendColumn(rName, nWidth, nRealPos, nId);
1673     if (nModelPos == HEADERBAR_APPEND)
1674         m_aColumns.push_back( CreateColumn(nId) );
1675     else
1676         m_aColumns.insert( m_aColumns.begin() + nModelPos, CreateColumn(nId) );
1677 
1678     return nId;
1679 }
1680 
RemoveColumn(sal_uInt16 nId)1681 void DbGridControl::RemoveColumn(sal_uInt16 nId)
1682 {
1683     EditBrowseBox::RemoveColumn(nId);
1684 
1685     const sal_uInt16 nIndex = GetModelColumnPos(nId);
1686     if(nIndex != GRID_COLUMN_NOT_FOUND)
1687     {
1688         m_aColumns.erase( m_aColumns.begin()+nIndex );
1689     }
1690 }
1691 
ColumnMoved(sal_uInt16 nId)1692 void DbGridControl::ColumnMoved(sal_uInt16 nId)
1693 {
1694     EditBrowseBox::ColumnMoved(nId);
1695 
1696     // remove the col from the model
1697     sal_uInt16 nOldModelPos = GetModelColumnPos(nId);
1698 #ifdef DBG_UTIL
1699     DbGridColumn* pCol = m_aColumns[ nOldModelPos ].get();
1700     DBG_ASSERT(!pCol->IsHidden(), "DbGridControl::ColumnMoved : moved a hidden col ? how this ?");
1701 #endif
1702 
1703     // for the new model pos we can't use GetModelColumnPos because we are altering the model at the moment
1704     // so the method won't work (in fact it would return the old model pos)
1705 
1706     // the new view pos is calculated easily
1707     sal_uInt16 nNewViewPos = GetViewColumnPos(nId);
1708 
1709     // from that we can compute the new model pos
1710     size_t nNewModelPos;
1711     for (nNewModelPos = 0; nNewModelPos < m_aColumns.size(); ++nNewModelPos)
1712     {
1713         if (!m_aColumns[ nNewModelPos ]->IsHidden())
1714         {
1715             if (!nNewViewPos)
1716                 break;
1717             else
1718                 --nNewViewPos;
1719         }
1720     }
1721     DBG_ASSERT( nNewModelPos < m_aColumns.size(), "DbGridControl::ColumnMoved : could not find the new model position !");
1722 
1723     // this will work. of course the model isn't fully consistent with our view right now, but let's
1724     // look at the situation : a column has been moved with in the VIEW from pos m to n, say m<n (in the
1725     // other case we can use analogue arguments).
1726     // All cols k with m<k<=n have been shifted left on pos, the former col m now has pos n.
1727     // In the model this affects a range of cols x to y, where x<=m and y<=n. And the number of hidden cols
1728     // within this range is constant, so we may calculate the view pos from the model pos in the above way.
1729 
1730     // for instance, let's look at a grid with six columns where the third one is hidden. this will
1731     // initially look like this :
1732 
1733     //              +---+---+---+---+---+---+
1734     // model pos    | 0 | 1 |*2*| 3 | 4 | 5 |
1735     //              +---+---+---+---+---+---+
1736     // ID           | 1 | 2 | 3 | 4 | 5 | 6 |
1737     //              +---+---+---+---+---+---+
1738     // view pos     | 0 | 1 | - | 2 | 3 | 4 |
1739     //              +---+---+---+---+---+---+
1740 
1741     // if we move the column at (view) pos 1 to (view) pos 3 we have :
1742 
1743     //              +---+---+---+---+---+---+
1744     // model pos    | 0 | 3 |*2*| 4 | 1 | 5 |   // not reflecting the changes, yet
1745     //              +---+---+---+---+---+---+
1746     // ID           | 1 | 4 | 3 | 5 | 2 | 6 |   // already reflecting the changes
1747     //              +---+---+---+---+---+---+
1748     // view pos     | 0 | 1 | - | 2 | 3 | 4 |
1749     //              +---+---+---+---+---+---+
1750 
1751     // or, sorted by the out-of-date model positions :
1752 
1753     //              +---+---+---+---+---+---+
1754     // model pos    | 0 | 1 |*2*| 3 | 4 | 5 |
1755     //              +---+---+---+---+---+---+
1756     // ID           | 1 | 2 | 3 | 4 | 5 | 6 |
1757     //              +---+---+---+---+---+---+
1758     // view pos     | 0 | 3 | - | 1 | 2 | 4 |
1759     //              +---+---+---+---+---+---+
1760 
1761     // We know the new view pos (3) of the moved column because our base class tells us. So we look at our
1762     // model for the 4th (the pos is zero-based) visible column, it is at (model) position 4. And this is
1763     // exactly the pos where we have to re-insert our column's model, so it looks ike this :
1764 
1765     //              +---+---+---+---+---+---+
1766     // model pos    | 0 |*1*| 2 | 3 | 4 | 5 |
1767     //              +---+---+---+---+---+---+
1768     // ID           | 1 | 3 | 4 | 5 | 2 | 6 |
1769     //              +---+---+---+---+---+---+
1770     // view pos     | 0 | - | 1 | 2 | 3 | 4 |
1771     //              +---+---+---+---+---+---+
1772 
1773     // Now, all is consistent again.
1774     // (except of the hidden column : The cycling of the cols occurred on the model, not on the view. maybe
1775     // the user expected the latter but there really is no good argument against our method ;) ...)
1776 
1777     // And no, this large explanation isn't just because I wanted to play a board game or something like
1778     // that. It's because it took me a while to see it myself, and the whole theme (hidden cols, model col
1779     // positions, view col positions)  is really painful (at least for me) so the above pictures helped me a lot ;)
1780 
1781     auto temp = std::move(m_aColumns[ nOldModelPos ]);
1782     m_aColumns.erase( m_aColumns.begin() + nOldModelPos );
1783     m_aColumns.insert( m_aColumns.begin() + nNewModelPos, std::move(temp) );
1784 }
1785 
SeekRow(long nRow)1786 bool DbGridControl::SeekRow(long nRow)
1787 {
1788     // in filter mode or in insert only mode we don't have any cursor!
1789     if ( !SeekCursor( nRow ) )
1790         return false;
1791 
1792     if ( IsFilterMode() )
1793     {
1794         DBG_ASSERT( IsFilterRow( nRow ), "DbGridControl::SeekRow(): No filter row, wrong mode" );
1795         m_xPaintRow = m_xEmptyRow;
1796     }
1797     else
1798     {
1799         // on the current position we have to take the current row for display as we want
1800         // to have the most recent values for display
1801         if ( ( nRow == m_nCurrentPos ) && getDisplaySynchron() )
1802             m_xPaintRow = m_xCurrentRow;
1803         // seek to the empty insert row
1804         else if ( IsInsertionRow( nRow ) )
1805             m_xPaintRow = m_xEmptyRow;
1806         else
1807         {
1808             m_xSeekRow->SetState( m_pSeekCursor.get(), true );
1809             m_xPaintRow = m_xSeekRow;
1810         }
1811     }
1812 
1813     EditBrowseBox::SeekRow(nRow);
1814 
1815     return m_nSeekPos >= 0;
1816 }
1817 
1818 // Is called whenever the visible amount of data changes
VisibleRowsChanged(long nNewTopRow,sal_uInt16 nLinesOnScreen)1819 void DbGridControl::VisibleRowsChanged( long nNewTopRow, sal_uInt16 nLinesOnScreen )
1820 {
1821     RecalcRows(nNewTopRow, nLinesOnScreen, false);
1822 }
1823 
RecalcRows(long nNewTopRow,sal_uInt16 nLinesOnScreen,bool bUpdateCursor)1824 void DbGridControl::RecalcRows(long nNewTopRow, sal_uInt16 nLinesOnScreen, bool bUpdateCursor)
1825 {
1826     // If no cursor -> no rows in the browser.
1827     if (!m_pSeekCursor)
1828     {
1829         DBG_ASSERT(GetRowCount() == 0,"DbGridControl: without cursor no rows are allowed to be there");
1830         return;
1831     }
1832 
1833     // ignore any implicitly made updates
1834     bool bDisablePaint = !bUpdateCursor && IsPaintEnabled();
1835     if (bDisablePaint)
1836         EnablePaint(false);
1837 
1838     // adjust cache to the visible area
1839     Reference< XPropertySet > xSet = m_pSeekCursor->getPropertySet();
1840     sal_Int32 nCacheSize = 0;
1841     xSet->getPropertyValue(FM_PROP_FETCHSIZE) >>= nCacheSize;
1842     bool bCacheAligned   = false;
1843     // no further cursor movements after initializing (m_nSeekPos < 0) because it is already
1844     // positioned on the first sentence
1845     long nDelta = nNewTopRow - GetTopRow();
1846     // limit for relative positioning
1847     long nLimit = nCacheSize ? nCacheSize / 2 : 0;
1848 
1849     // more lines on screen than in cache
1850     if (nLimit < nLinesOnScreen)
1851     {
1852         Any aCacheSize;
1853         aCacheSize <<= sal_Int32(nLinesOnScreen*2);
1854         xSet->setPropertyValue(FM_PROP_FETCHSIZE, aCacheSize);
1855         // here we need to update the cursor for sure
1856         bUpdateCursor = true;
1857         bCacheAligned = true;
1858         nLimit = nLinesOnScreen;
1859     }
1860 
1861     // In the following, all positionings are done as it is
1862     // ensured that there are enough lines in the data cache
1863 
1864     // window goes downwards with less than two windows difference or
1865     // the cache was updated and no rowcount yet
1866     if (nDelta < nLimit && (nDelta > 0
1867         || (bCacheAligned && m_nTotalCount < 0)) )
1868         SeekCursor(nNewTopRow + nLinesOnScreen - 1);
1869     else if (nDelta < 0 && std::abs(nDelta) < nLimit)
1870         SeekCursor(nNewTopRow);
1871     else if (nDelta != 0 || bUpdateCursor)
1872         SeekCursor(nNewTopRow, true);
1873 
1874     AdjustRows();
1875 
1876     // ignore any updates implicit made
1877     EnablePaint(true);
1878 }
1879 
RowInserted(long nRow,long nNumRows,bool bDoPaint)1880 void DbGridControl::RowInserted(long nRow, long nNumRows, bool bDoPaint)
1881 {
1882     if (nNumRows)
1883     {
1884         if (m_bRecordCountFinal && m_nTotalCount < 0)
1885         {
1886             // if we have an insert row we have to reduce to count by 1
1887             // as the total count reflects only the existing rows in database
1888             m_nTotalCount = GetRowCount() + nNumRows;
1889             if (m_xEmptyRow.is())
1890                 --m_nTotalCount;
1891         }
1892         else if (m_nTotalCount >= 0)
1893             m_nTotalCount += nNumRows;
1894 
1895         EditBrowseBox::RowInserted(nRow, nNumRows, bDoPaint);
1896         m_aBar->InvalidateState(DbGridControlNavigationBarState::Count);
1897     }
1898 }
1899 
RowRemoved(long nRow,long nNumRows,bool bDoPaint)1900 void DbGridControl::RowRemoved(long nRow, long nNumRows, bool bDoPaint)
1901 {
1902     if (nNumRows)
1903     {
1904         if (m_bRecordCountFinal && m_nTotalCount < 0)
1905         {
1906             m_nTotalCount = GetRowCount() - nNumRows;
1907             // if we have an insert row reduce by 1
1908             if (m_xEmptyRow.is())
1909                 --m_nTotalCount;
1910         }
1911         else if (m_nTotalCount >= 0)
1912             m_nTotalCount -= nNumRows;
1913 
1914         EditBrowseBox::RowRemoved(nRow, nNumRows, bDoPaint);
1915         m_aBar->InvalidateState(DbGridControlNavigationBarState::Count);
1916     }
1917 }
1918 
AdjustRows()1919 void DbGridControl::AdjustRows()
1920 {
1921     if (!m_pSeekCursor)
1922         return;
1923 
1924     Reference< XPropertySet > xSet = m_pDataCursor->getPropertySet();
1925 
1926     // refresh RecordCount
1927     sal_Int32 nRecordCount = 0;
1928     xSet->getPropertyValue(FM_PROP_ROWCOUNT) >>= nRecordCount;
1929     if (!m_bRecordCountFinal)
1930         m_bRecordCountFinal = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ROWCOUNTFINAL));
1931 
1932     // Did the number of rows change?
1933     // Here we need to consider that there might be an additional row for adding new data sets
1934 
1935     // add additional AppendRow for insertion
1936     if (m_nOptions & DbGridControlOptions::Insert)
1937         ++nRecordCount;
1938 
1939     // If there is currently an insertion, so do not consider this added row in RecordCount or Appendrow
1940     if (!IsUpdating() && m_bRecordCountFinal && IsModified() && m_xCurrentRow != m_xEmptyRow &&
1941         m_xCurrentRow->IsNew())
1942         ++nRecordCount;
1943     // ensured with !m_bUpdating: otherwise the edited data set (that SaveRow added and why this
1944     // method was called) would be called twice (if m_bUpdating == sal_True): once in RecordCount
1945     // and a second time here (60787 - FS)
1946 
1947     if (nRecordCount != GetRowCount())
1948     {
1949         long nDelta = GetRowCount() - static_cast<long>(nRecordCount);
1950         if (nDelta > 0) // too many
1951         {
1952             RowRemoved(GetRowCount() - nDelta, nDelta, false);
1953             // some rows are gone, thus, repaint starting at the current position
1954             Invalidate();
1955 
1956             sal_Int32 nNewPos = AlignSeekCursor();
1957             if (m_bSynchDisplay)
1958                 EditBrowseBox::GoToRow(nNewPos);
1959 
1960             SetCurrent(nNewPos);
1961             // there are rows so go to the selected current column
1962             if (nRecordCount)
1963                 GoToRowColumnId(nNewPos, GetColumnId(GetCurColumnId()));
1964             if (!IsResizing() && GetRowCount())
1965                 RecalcRows(GetTopRow(), GetVisibleRows(), true);
1966             m_aBar->InvalidateAll(m_nCurrentPos, true);
1967         }
1968         else  // too few
1969             RowInserted(GetRowCount(), -nDelta);
1970     }
1971 
1972     if (m_bRecordCountFinal && m_nTotalCount < 0)
1973     {
1974         if (m_nOptions & DbGridControlOptions::Insert)
1975             m_nTotalCount = GetRowCount() - 1;
1976         else
1977             m_nTotalCount = GetRowCount();
1978     }
1979     m_aBar->InvalidateState(DbGridControlNavigationBarState::Count);
1980 }
1981 
GetRowStatus(long nRow) const1982 svt::EditBrowseBox::RowStatus DbGridControl::GetRowStatus(long nRow) const
1983 {
1984     if (IsFilterRow(nRow))
1985         return EditBrowseBox::FILTER;
1986     else if (m_nCurrentPos >= 0 && nRow == m_nCurrentPos)
1987     {
1988         // new row
1989         if (!IsValid(m_xCurrentRow))
1990             return EditBrowseBox::DELETED;
1991         else if (IsModified())
1992             return EditBrowseBox::MODIFIED;
1993         else if (m_xCurrentRow->IsNew())
1994             return EditBrowseBox::CURRENTNEW;
1995         else
1996             return EditBrowseBox::CURRENT;
1997     }
1998     else if (IsInsertionRow(nRow))
1999         return EditBrowseBox::NEW;
2000     else if (!IsValid(m_xSeekRow))
2001         return EditBrowseBox::DELETED;
2002     else
2003         return EditBrowseBox::CLEAN;
2004 }
2005 
PaintCell(OutputDevice & rDev,const tools::Rectangle & rRect,sal_uInt16 nColumnId) const2006 void DbGridControl::PaintCell(OutputDevice& rDev, const tools::Rectangle& rRect, sal_uInt16 nColumnId) const
2007 {
2008     if (!IsValid(m_xPaintRow))
2009         return;
2010 
2011     size_t Location = GetModelColumnPos(nColumnId);
2012     DbGridColumn* pColumn = (Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
2013     if (pColumn)
2014     {
2015         tools::Rectangle aArea(rRect);
2016         if ((GetMode() & BrowserMode::CURSOR_WO_FOCUS) == BrowserMode::CURSOR_WO_FOCUS)
2017         {
2018             aArea.AdjustTop(1 );
2019             aArea.AdjustBottom( -1 );
2020         }
2021         pColumn->Paint(rDev, aArea, m_xPaintRow.get(), getNumberFormatter());
2022     }
2023 }
2024 
CursorMoving(long nNewRow,sal_uInt16 nNewCol)2025 bool DbGridControl::CursorMoving(long nNewRow, sal_uInt16 nNewCol)
2026 {
2027 
2028     DeactivateCell( false );
2029 
2030     if  (   m_pDataCursor
2031         &&  ( m_nCurrentPos != nNewRow )
2032         && !SetCurrent( nNewRow )
2033         )
2034     {
2035         ActivateCell();
2036         return false;
2037     }
2038 
2039     return EditBrowseBox::CursorMoving( nNewRow, nNewCol );
2040 }
2041 
SetCurrent(long nNewRow)2042 bool DbGridControl::SetCurrent(long nNewRow)
2043 {
2044     // Each movement of the datacursor must start with BeginCursorAction and end with
2045     // EndCursorAction to block all notifications during the movement
2046     BeginCursorAction();
2047 
2048     try
2049     {
2050         // compare positions
2051         if (SeekCursor(nNewRow))
2052         {
2053             if (IsFilterRow(nNewRow))   // special mode for filtering
2054             {
2055                 m_xCurrentRow = m_xDataRow = m_xPaintRow = m_xEmptyRow;
2056                 m_nCurrentPos = nNewRow;
2057             }
2058             else
2059             {
2060                 bool bNewRowInserted = false;
2061                 // Should we go to the insertrow ?
2062                 if (IsInsertionRow(nNewRow))
2063                 {
2064                     // to we need to move the cursor to the insert row?
2065                     // we need to insert the if the current row isn't the insert row or if the
2066                     // cursor triggered the move by itself and we need a reinitialization of the row
2067                     Reference< XPropertySet > xCursorProps = m_pDataCursor->getPropertySet();
2068                     if ( !::comphelper::getBOOL(xCursorProps->getPropertyValue(FM_PROP_ISNEW)) )
2069                     {
2070                         Reference< XResultSetUpdate > xUpdateCursor(Reference< XInterface >(*m_pDataCursor), UNO_QUERY);
2071                         xUpdateCursor->moveToInsertRow();
2072                     }
2073                     bNewRowInserted = true;
2074                 }
2075                 else
2076                 {
2077 
2078                     if ( !m_pSeekCursor->isBeforeFirst() && !m_pSeekCursor->isAfterLast() )
2079                     {
2080                         Any aBookmark = m_pSeekCursor->getBookmark();
2081                         if (!m_xCurrentRow.is() || m_xCurrentRow->IsNew() || !CompareBookmark(aBookmark, m_pDataCursor->getBookmark()))
2082                         {
2083                             // adjust the cursor to the new desired row
2084                             if (!m_pDataCursor->moveToBookmark(aBookmark))
2085                             {
2086                                 EndCursorAction();
2087                                 return false;
2088                             }
2089                         }
2090                     }
2091                 }
2092                 m_xDataRow->SetState(m_pDataCursor.get(), false);
2093                 m_xCurrentRow = m_xDataRow;
2094 
2095                 long nPaintPos = -1;
2096                 // do we have to repaint the last regular row in case of setting defaults or autovalues
2097                 if (m_nCurrentPos >= 0 && m_nCurrentPos >= (GetRowCount() - 2))
2098                     nPaintPos = m_nCurrentPos;
2099 
2100                 m_nCurrentPos = nNewRow;
2101 
2102                 // repaint the new row to display all defaults
2103                 if (bNewRowInserted)
2104                     RowModified(m_nCurrentPos);
2105                 if (nPaintPos >= 0)
2106                     RowModified(nPaintPos);
2107             }
2108         }
2109         else
2110         {
2111             OSL_FAIL("DbGridControl::SetCurrent : SeekRow failed !");
2112             EndCursorAction();
2113             return false;
2114         }
2115     }
2116     catch ( const Exception& )
2117     {
2118         DBG_UNHANDLED_EXCEPTION("svx");
2119         EndCursorAction();
2120         return false;
2121     }
2122 
2123     EndCursorAction();
2124     return true;
2125 }
2126 
CursorMoved()2127 void DbGridControl::CursorMoved()
2128 {
2129 
2130     // cursor movement due to deletion or insertion of rows
2131     if (m_pDataCursor && m_nCurrentPos != GetCurRow())
2132     {
2133         DeactivateCell();
2134         SetCurrent(GetCurRow());
2135     }
2136 
2137     EditBrowseBox::CursorMoved();
2138     m_aBar->InvalidateAll(m_nCurrentPos);
2139 
2140     // select the new column when they moved
2141     if ( IsDesignMode() && GetSelectedColumnCount() > 0 && GetCurColumnId() )
2142     {
2143         SelectColumnId( GetCurColumnId() );
2144     }
2145 
2146     if ( m_nLastColId != GetCurColumnId() )
2147         onColumnChange();
2148     m_nLastColId = GetCurColumnId();
2149 
2150     if ( m_nLastRowId != GetCurRow() )
2151         onRowChange();
2152     m_nLastRowId = GetCurRow();
2153 }
2154 
onRowChange()2155 void DbGridControl::onRowChange()
2156 {
2157     // not interested in
2158 }
2159 
onColumnChange()2160 void DbGridControl::onColumnChange()
2161 {
2162     if ( m_pGridListener )
2163         m_pGridListener->columnChanged();
2164 }
2165 
setDisplaySynchron(bool bSync)2166 void DbGridControl::setDisplaySynchron(bool bSync)
2167 {
2168     if (bSync != m_bSynchDisplay)
2169     {
2170         m_bSynchDisplay = bSync;
2171         if (m_bSynchDisplay)
2172             AdjustDataSource();
2173     }
2174 }
2175 
AdjustDataSource(bool bFull)2176 void DbGridControl::AdjustDataSource(bool bFull)
2177 {
2178     SAL_INFO("svx.fmcomp", "DbGridControl::AdjustDataSource");
2179     SolarMutexGuard aGuard;
2180     // If the current row is recalculated at the moment, do not adjust
2181 
2182     if (bFull)
2183         m_xCurrentRow = nullptr;
2184     // if we are on the same row only repaint
2185     // but this is only possible for rows which are not inserted, in that case the comparison result
2186     // may not be correct
2187     else
2188         if  (   m_xCurrentRow.is()
2189             &&  !m_xCurrentRow->IsNew()
2190             &&  !m_pDataCursor->isBeforeFirst()
2191             &&  !m_pDataCursor->isAfterLast()
2192             &&  !m_pDataCursor->rowDeleted()
2193             )
2194         {
2195             bool bEqualBookmarks = CompareBookmark( m_xCurrentRow->GetBookmark(), m_pDataCursor->getBookmark() );
2196 
2197             bool bDataCursorIsOnNew = false;
2198             m_pDataCursor->getPropertySet()->getPropertyValue( FM_PROP_ISNEW ) >>= bDataCursorIsOnNew;
2199 
2200             if ( bEqualBookmarks && !bDataCursorIsOnNew )
2201             {
2202                 // position of my data cursor is the same as the position our current row points tpo
2203                 // sync the status, repaint, done
2204                 DBG_ASSERT(m_xDataRow == m_xCurrentRow, "Errors in the data row");
2205                 SAL_INFO("svx.fmcomp", "same position, new state: " << ROWSTATUS(m_xCurrentRow));
2206                 RowModified(m_nCurrentPos);
2207                 return;
2208             }
2209         }
2210 
2211     // away from the data cursor's row
2212     if (m_xPaintRow == m_xCurrentRow)
2213         m_xPaintRow = m_xSeekRow;
2214 
2215     // not up-to-date row, thus, adjust completely
2216     if (!m_xCurrentRow.is())
2217         AdjustRows();
2218 
2219     sal_Int32 nNewPos = AlignSeekCursor();
2220     if (nNewPos < 0)// could not find any position
2221         return;
2222 
2223     if (nNewPos != m_nCurrentPos)
2224     {
2225         if (m_bSynchDisplay)
2226             EditBrowseBox::GoToRow(nNewPos);
2227 
2228         if (!m_xCurrentRow.is())
2229             // Happens e.g. when deleting the n last datasets (n>1) while the cursor was positioned
2230             // on the last one. In this case, AdjustRows deletes two rows from BrowseBox, by what
2231             // CurrentRow is corrected to point two rows down, so that GoToRow will point into
2232             // emptiness (since we are - purportedly - at the correct position)
2233             SetCurrent(nNewPos);
2234     }
2235     else
2236     {
2237         SetCurrent(nNewPos);
2238         RowModified(nNewPos);
2239     }
2240 
2241     // if the data cursor was moved from outside, this section is voided
2242     SetNoSelection();
2243     m_aBar->InvalidateAll(m_nCurrentPos, m_xCurrentRow.is());
2244 }
2245 
AlignSeekCursor()2246 sal_Int32 DbGridControl::AlignSeekCursor()
2247 {
2248     // position SeekCursor onto the data cursor, no data transmission
2249 
2250     if (!m_pSeekCursor)
2251         return -1;
2252 
2253     Reference< XPropertySet > xSet = m_pDataCursor->getPropertySet();
2254 
2255     // now align the seek cursor and the data cursor
2256     if (::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISNEW)))
2257         m_nSeekPos = GetRowCount() - 1;
2258     else
2259     {
2260         try
2261         {
2262             if ( m_pDataCursor->isBeforeFirst() )
2263             {
2264                 // this is somewhat strange, but can nevertheless happen
2265                 SAL_INFO( "svx.fmcomp", "DbGridControl::AlignSeekCursor: nobody should tamper with my cursor this way (before first)!" );
2266                 m_pSeekCursor->first();
2267                 m_pSeekCursor->previous();
2268                 m_nSeekPos = -1;
2269             }
2270             else if ( m_pDataCursor->isAfterLast() )
2271             {
2272                 SAL_INFO( "svx.fmcomp", "DbGridControl::AlignSeekCursor: nobody should tamper with my cursor this way (after last)!" );
2273                 m_pSeekCursor->last();
2274                 m_pSeekCursor->next();
2275                 m_nSeekPos = -1;
2276             }
2277             else
2278             {
2279                 m_pSeekCursor->moveToBookmark(m_pDataCursor->getBookmark());
2280                 if (!CompareBookmark(m_pDataCursor->getBookmark(), m_pSeekCursor->getBookmark()))
2281                     // unfortunately, moveToBookmark might lead to a re-positioning of the seek
2282                     // cursor (if the complex moveToBookmark with all its events fires an update
2283                     // somewhere) -> retry
2284                     m_pSeekCursor->moveToBookmark(m_pDataCursor->getBookmark());
2285                     // Now there is still the chance of a failure but it is less likely.
2286                     // The alternative would be a loop until everything is fine - no good solution...
2287                 m_nSeekPos = m_pSeekCursor->getRow() - 1;
2288             }
2289         }
2290         catch(Exception&)
2291         {
2292         }
2293     }
2294     return m_nSeekPos;
2295 }
2296 
SeekCursor(long nRow,bool bAbsolute)2297 bool DbGridControl::SeekCursor(long nRow, bool bAbsolute)
2298 {
2299     // position SeekCursor onto the data cursor, no data transmission
2300 
2301     // additions for the filtermode
2302     if (IsFilterRow(nRow))
2303     {
2304         m_nSeekPos = 0;
2305         return true;
2306     }
2307 
2308     if (!m_pSeekCursor)
2309         return false;
2310 
2311     // is this an insertion?
2312     if (IsValid(m_xCurrentRow) && m_xCurrentRow->IsNew() &&
2313         nRow >= m_nCurrentPos)
2314     {
2315         // if so, scrolling down must be prevented as this is already the last data set!
2316         if (nRow == m_nCurrentPos)
2317         {
2318             // no adjustment necessary
2319             m_nSeekPos = nRow;
2320         }
2321         else if (IsInsertionRow(nRow)) // blank row for data insertion
2322             m_nSeekPos = nRow;
2323     }
2324     else if (IsInsertionRow(nRow)) // blank row for data insertion
2325         m_nSeekPos = nRow;
2326     else if ((-1 == nRow) && (GetRowCount() == ((m_nOptions & DbGridControlOptions::Insert) ? 1 : 0)) && m_pSeekCursor->isAfterLast())
2327         m_nSeekPos = nRow;
2328     else
2329     {
2330         bool bSuccess = false;
2331         long nSteps = 0;
2332         try
2333         {
2334             if ( m_pSeekCursor->rowDeleted() )
2335             {
2336                 // somebody deleted the current row of the seek cursor. Move it away from this row.
2337                 m_pSeekCursor->next();
2338                 if ( m_pSeekCursor->isAfterLast() || m_pSeekCursor->isBeforeFirst() )
2339                     bAbsolute = true;
2340             }
2341 
2342             if ( !bAbsolute )
2343             {
2344                 DBG_ASSERT( !m_pSeekCursor->isAfterLast() && !m_pSeekCursor->isBeforeFirst(),
2345                     "DbGridControl::SeekCursor: how did the seek cursor get to this position?!" );
2346                 nSteps = nRow - (m_pSeekCursor->getRow() - 1);
2347                 bAbsolute = std::abs(nSteps) > 100;
2348             }
2349 
2350             if ( bAbsolute )
2351             {
2352                 bSuccess = m_pSeekCursor->absolute(nRow + 1);
2353                 if (bSuccess)
2354                     m_nSeekPos = nRow;
2355             }
2356             else
2357             {
2358                 if (nSteps > 0) // position onto the last needed data set
2359                 {
2360                     if (m_pSeekCursor->isAfterLast())
2361                         bSuccess = false;
2362                     else if (m_pSeekCursor->isBeforeFirst())
2363                         bSuccess = m_pSeekCursor->absolute(nSteps);
2364                     else
2365                         bSuccess = m_pSeekCursor->relative(nSteps);
2366                 }
2367                 else if (nSteps < 0)
2368                 {
2369                     if (m_pSeekCursor->isBeforeFirst())
2370                         bSuccess = false;
2371                     else if (m_pSeekCursor->isAfterLast())
2372                         bSuccess = m_pSeekCursor->absolute(nSteps);
2373                     else
2374                         bSuccess = m_pSeekCursor->relative(nSteps);
2375                 }
2376                 else
2377                 {
2378                     m_nSeekPos = nRow;
2379                     return true;
2380                 }
2381             }
2382         }
2383         catch(Exception&)
2384         {
2385             OSL_FAIL("DbGridControl::SeekCursor : failed ...");
2386         }
2387 
2388         try
2389         {
2390             if (!bSuccess)
2391             {
2392                 if (bAbsolute || nSteps > 0)
2393                 {
2394                     if (m_pSeekCursor->isLast())
2395                         bSuccess = true;
2396                     else
2397                         bSuccess = m_pSeekCursor->last();
2398                 }
2399                 else
2400                 {
2401                     if (m_pSeekCursor->isFirst())
2402                         bSuccess = true;
2403                     else
2404                         bSuccess = m_pSeekCursor->first();
2405                 }
2406             }
2407 
2408             if (bSuccess)
2409                 m_nSeekPos = m_pSeekCursor->getRow() - 1;
2410             else
2411                 m_nSeekPos = -1;
2412         }
2413         catch(Exception&)
2414         {
2415             DBG_UNHANDLED_EXCEPTION("svx");
2416             OSL_FAIL("DbGridControl::SeekCursor : failed ...");
2417             m_nSeekPos = -1; // no further data set available
2418         }
2419     }
2420     return m_nSeekPos == nRow;
2421 }
2422 
MoveToFirst()2423 void DbGridControl::MoveToFirst()
2424 {
2425     if (m_pSeekCursor && (GetCurRow() != 0))
2426         MoveToPosition(0);
2427 }
2428 
MoveToLast()2429 void DbGridControl::MoveToLast()
2430 {
2431     if (!m_pSeekCursor)
2432         return;
2433 
2434     if (m_nTotalCount < 0) // no RecordCount, yet
2435     {
2436         try
2437         {
2438             bool bRes = m_pSeekCursor->last();
2439 
2440             if (bRes)
2441             {
2442                 m_nSeekPos = m_pSeekCursor->getRow() - 1;
2443                 AdjustRows();
2444             }
2445         }
2446         catch(Exception&)
2447         {
2448         }
2449     }
2450 
2451     // position onto the last data set not on a blank row
2452     if (m_nOptions & DbGridControlOptions::Insert)
2453     {
2454         if ((GetRowCount() - 1) > 0)
2455             MoveToPosition(GetRowCount() - 2);
2456     }
2457     else if (GetRowCount())
2458         MoveToPosition(GetRowCount() - 1);
2459 }
2460 
MoveToPrev()2461 void DbGridControl::MoveToPrev()
2462 {
2463     long nNewRow = std::max(GetCurRow() - 1, 0L);
2464     if (GetCurRow() != nNewRow)
2465         MoveToPosition(nNewRow);
2466 }
2467 
MoveToNext()2468 void DbGridControl::MoveToNext()
2469 {
2470     if (!m_pSeekCursor)
2471         return;
2472 
2473     if (m_nTotalCount > 0)
2474     {
2475         // move the data cursor to the right position
2476         long nNewRow = std::min(GetRowCount() - 1, GetCurRow() + 1);
2477         if (GetCurRow() != nNewRow)
2478             MoveToPosition(nNewRow);
2479     }
2480     else
2481     {
2482         bool bOk = false;
2483         try
2484         {
2485             // try to move to next row
2486             // when not possible our paint cursor is already on the last row
2487             // then we must be sure that the data cursor is on the position
2488             // we call ourself again
2489             bOk = m_pSeekCursor->next();
2490             if (bOk)
2491             {
2492                 m_nSeekPos = m_pSeekCursor->getRow() - 1;
2493                 MoveToPosition(GetCurRow() + 1);
2494             }
2495         }
2496         catch(SQLException &)
2497         {
2498             DBG_UNHANDLED_EXCEPTION("svx");
2499         }
2500 
2501         if(!bOk)
2502         {
2503             AdjustRows();
2504             if (m_nTotalCount > 0) // only to avoid infinite recursion
2505                 MoveToNext();
2506         }
2507     }
2508 }
2509 
MoveToPosition(sal_uInt32 nPos)2510 void DbGridControl::MoveToPosition(sal_uInt32 nPos)
2511 {
2512     if (!m_pSeekCursor)
2513         return;
2514 
2515     if (m_nTotalCount < 0 && static_cast<long>(nPos) >= GetRowCount())
2516     {
2517         try
2518         {
2519             if (!m_pSeekCursor->absolute(nPos + 1))
2520             {
2521                 AdjustRows();
2522                 return;
2523             }
2524             else
2525             {
2526                 m_nSeekPos = m_pSeekCursor->getRow() - 1;
2527                 AdjustRows();
2528             }
2529         }
2530         catch(Exception&)
2531         {
2532             return;
2533         }
2534     }
2535     EditBrowseBox::GoToRow(nPos);
2536     m_aBar->InvalidateAll(m_nCurrentPos);
2537 }
2538 
AppendNew()2539 void DbGridControl::AppendNew()
2540 {
2541     if (!m_pSeekCursor || !(m_nOptions & DbGridControlOptions::Insert))
2542         return;
2543 
2544     if (m_nTotalCount < 0) // no RecordCount, yet
2545     {
2546         try
2547         {
2548             bool bRes = m_pSeekCursor->last();
2549 
2550             if (bRes)
2551             {
2552                 m_nSeekPos = m_pSeekCursor->getRow() - 1;
2553                 AdjustRows();
2554             }
2555         }
2556         catch(Exception&)
2557         {
2558             return;
2559         }
2560     }
2561 
2562     long nNewRow = m_nTotalCount + 1;
2563     if (nNewRow > 0 && GetCurRow() != nNewRow)
2564         MoveToPosition(nNewRow - 1);
2565 }
2566 
SetDesignMode(bool bMode)2567 void DbGridControl::SetDesignMode(bool bMode)
2568 {
2569     if (IsDesignMode() != bMode)
2570     {
2571         // adjust Enable/Disable for design mode so that the headerbar remains configurable
2572         if (bMode)
2573         {
2574             if (!IsEnabled())
2575             {
2576                 Enable();
2577                 GetDataWindow().Disable();
2578             }
2579         }
2580         else
2581         {
2582             // disable completely
2583             if (!GetDataWindow().IsEnabled())
2584                 Disable();
2585         }
2586 
2587         m_bDesignMode = bMode;
2588         GetDataWindow().SetMouseTransparent(bMode);
2589         SetMouseTransparent(bMode);
2590 
2591         m_aBar->InvalidateAll(m_nCurrentPos, true);
2592     }
2593 }
2594 
SetFilterMode(bool bMode)2595 void DbGridControl::SetFilterMode(bool bMode)
2596 {
2597     if (IsFilterMode() != bMode)
2598     {
2599         m_bFilterMode = bMode;
2600 
2601         if (bMode)
2602         {
2603             SetUpdateMode(false);
2604 
2605             // there is no cursor anymore
2606             if (IsEditing())
2607                 DeactivateCell();
2608             RemoveRows(false);
2609 
2610             m_xEmptyRow = new DbGridRow();
2611 
2612             // setting the new filter controls
2613             for (auto const & pCurCol : m_aColumns)
2614             {
2615                 if (!pCurCol->IsHidden())
2616                     pCurCol->UpdateControl();
2617             }
2618 
2619             // one row for filtering
2620             RowInserted(0);
2621             SetUpdateMode(true);
2622         }
2623         else
2624             setDataSource(Reference< XRowSet > ());
2625     }
2626 }
2627 
GetCellText(long _nRow,sal_uInt16 _nColId) const2628 OUString DbGridControl::GetCellText(long _nRow, sal_uInt16 _nColId) const
2629 {
2630     size_t Location = GetModelColumnPos( _nColId );
2631     DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
2632     OUString sRet;
2633     if ( const_cast<DbGridControl*>(this)->SeekRow(_nRow) )
2634         sRet = GetCurrentRowCellText(pColumn, m_xPaintRow);
2635     return sRet;
2636 }
2637 
GetCurrentRowCellText(DbGridColumn const * pColumn,const DbGridRowRef & _rRow) const2638 OUString DbGridControl::GetCurrentRowCellText(DbGridColumn const * pColumn,const DbGridRowRef& _rRow) const
2639 {
2640     // text output for a single row
2641     OUString aText;
2642     if ( pColumn && IsValid(_rRow) )
2643         aText = pColumn->GetCellText(_rRow.get(), m_xFormatter);
2644     return aText;
2645 }
2646 
GetTotalCellWidth(long nRow,sal_uInt16 nColId)2647 sal_uInt32 DbGridControl::GetTotalCellWidth(long nRow, sal_uInt16 nColId)
2648 {
2649     if (SeekRow(nRow))
2650     {
2651         size_t Location = GetModelColumnPos( nColId );
2652         DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
2653         return GetDataWindow().GetTextWidth(GetCurrentRowCellText(pColumn,m_xPaintRow));
2654     }
2655     else
2656         return 30;  // FIXME magic number for default cell width
2657 }
2658 
PreExecuteRowContextMenu(sal_uInt16,PopupMenu & rMenu)2659 void DbGridControl::PreExecuteRowContextMenu(sal_uInt16 /*nRow*/, PopupMenu& rMenu)
2660 {
2661     bool bDelete = (m_nOptions & DbGridControlOptions::Delete) && GetSelectRowCount() && !IsCurrentAppending();
2662     // if only a blank row is selected then do not delete
2663     bDelete = bDelete && !((m_nOptions & DbGridControlOptions::Insert) && GetSelectRowCount() == 1 && IsRowSelected(GetRowCount() - 1));
2664 
2665     rMenu.EnableItem(rMenu.GetItemId("delete"), bDelete);
2666     rMenu.EnableItem(rMenu.GetItemId("save"), IsModified());
2667 
2668     // the undo is more difficult
2669     bool bCanUndo = IsModified();
2670     int nState = -1;
2671     if (m_aMasterStateProvider.IsSet())
2672         nState = m_aMasterStateProvider.Call(DbGridControlNavigationBarState::Undo);
2673     bCanUndo &= ( 0 != nState );
2674 
2675     rMenu.EnableItem(rMenu.GetItemId("undo"), bCanUndo);
2676 }
2677 
PostExecuteRowContextMenu(sal_uInt16,const PopupMenu & rMenu,sal_uInt16 nExecutionResult)2678 void DbGridControl::PostExecuteRowContextMenu(sal_uInt16 /*nRow*/, const PopupMenu& rMenu, sal_uInt16 nExecutionResult)
2679 {
2680     if (nExecutionResult == rMenu.GetItemId("delete"))
2681     {
2682         // delete asynchronously
2683         if (m_nDeleteEvent)
2684             Application::RemoveUserEvent(m_nDeleteEvent);
2685         m_nDeleteEvent = Application::PostUserEvent(LINK(this,DbGridControl,OnDelete), nullptr, true);
2686     }
2687     else if (nExecutionResult == rMenu.GetItemId("undo"))
2688         Undo();
2689     else if (nExecutionResult == rMenu.GetItemId("save"))
2690         SaveRow();
2691 }
2692 
DataSourcePropertyChanged(const PropertyChangeEvent & evt)2693 void DbGridControl::DataSourcePropertyChanged(const PropertyChangeEvent& evt)
2694 {
2695     SAL_INFO("svx.fmcomp", "DbGridControl::DataSourcePropertyChanged");
2696     SolarMutexGuard aGuard;
2697     // prop "IsModified" changed ?
2698     // during update don't care about the modified state
2699     if (!IsUpdating() && evt.PropertyName == FM_PROP_ISMODIFIED )
2700     {
2701         Reference< XPropertySet > xSource(evt.Source, UNO_QUERY);
2702         DBG_ASSERT( xSource.is(), "DbGridControl::DataSourcePropertyChanged: invalid event source!" );
2703         bool bIsNew = false;
2704         if (xSource.is())
2705             bIsNew = ::comphelper::getBOOL(xSource->getPropertyValue(FM_PROP_ISNEW));
2706 
2707         if (bIsNew && m_xCurrentRow.is())
2708         {
2709             DBG_ASSERT(::comphelper::getBOOL(xSource->getPropertyValue(FM_PROP_ROWCOUNTFINAL)), "DbGridControl::DataSourcePropertyChanged : somebody moved the form to a new record before the row count was final !");
2710             sal_Int32 nRecordCount = 0;
2711             xSource->getPropertyValue(FM_PROP_ROWCOUNT) >>= nRecordCount;
2712             if (::comphelper::getBOOL(evt.NewValue))
2713             {   // modified state changed from sal_False to sal_True and we're on an insert row
2714                 // -> we've to add a new grid row
2715                 if ((nRecordCount == GetRowCount() - 1)  && m_xCurrentRow->IsNew())
2716                 {
2717                     RowInserted(GetRowCount());
2718                     InvalidateStatusCell(m_nCurrentPos);
2719                     m_aBar->InvalidateAll(m_nCurrentPos);
2720                 }
2721             }
2722             else
2723             {   // modified state changed from sal_True to sal_False and we're on an insert row
2724                 // we have two "new row"s at the moment : the one we're editing currently (where the current
2725                 // column is the only dirty element) and a "new new" row which is completely clean. As the first
2726                 // one is about to be cleaned, too, the second one is obsolete now.
2727                 if (m_xCurrentRow->IsNew() && nRecordCount == (GetRowCount() - 2))
2728                 {
2729                     RowRemoved(GetRowCount() - 1);
2730                     InvalidateStatusCell(m_nCurrentPos);
2731                     m_aBar->InvalidateAll(m_nCurrentPos);
2732                 }
2733             }
2734         }
2735         if (m_xCurrentRow.is())
2736         {
2737             m_xCurrentRow->SetStatus(::comphelper::getBOOL(evt.NewValue) ? GridRowStatus::Modified : GridRowStatus::Clean);
2738             m_xCurrentRow->SetNew( bIsNew );
2739             InvalidateStatusCell(m_nCurrentPos);
2740             SAL_INFO("svx.fmcomp", "modified flag changed, new state: " << ROWSTATUS(m_xCurrentRow));
2741         }
2742     }
2743 }
2744 
StartDrag(sal_Int8,const Point & rPosPixel)2745 void DbGridControl::StartDrag( sal_Int8 /*nAction*/, const Point& rPosPixel )
2746 {
2747     if (!m_pSeekCursor || IsResizing())
2748         return;
2749 
2750     sal_uInt16 nColId = GetColumnId(GetColumnAtXPosPixel(rPosPixel.X()));
2751     long   nRow = GetRowAtYPosPixel(rPosPixel.Y());
2752     if (nColId != HandleColumnId && nRow >= 0)
2753     {
2754         if (GetDataWindow().IsMouseCaptured())
2755             GetDataWindow().ReleaseMouse();
2756 
2757         size_t Location = GetModelColumnPos( nColId );
2758         DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
2759         rtl::Reference<OStringTransferable> pTransferable = new OStringTransferable(GetCurrentRowCellText(pColumn,m_xPaintRow));
2760         pTransferable->StartDrag(this, DND_ACTION_COPY);
2761     }
2762 }
2763 
canCopyCellText(sal_Int32 _nRow,sal_uInt16 _nColId)2764 bool DbGridControl::canCopyCellText(sal_Int32 _nRow, sal_uInt16 _nColId)
2765 {
2766     return  (_nRow >= 0)
2767         &&  (_nRow < GetRowCount())
2768         &&  (_nColId != HandleColumnId)
2769         &&  (GetModelColumnPos(_nColId) != GRID_COLUMN_NOT_FOUND);
2770 }
2771 
copyCellText(sal_Int32 _nRow,sal_uInt16 _nColId)2772 void DbGridControl::copyCellText(sal_Int32 _nRow, sal_uInt16 _nColId)
2773 {
2774     DBG_ASSERT(canCopyCellText(_nRow, _nColId), "DbGridControl::copyCellText: invalid call!");
2775     DbGridColumn* pColumn = m_aColumns[ GetModelColumnPos(_nColId) ].get();
2776     SeekRow(_nRow);
2777     OStringTransfer::CopyString( GetCurrentRowCellText( pColumn,m_xPaintRow ), this );
2778 }
2779 
executeRowContextMenu(long _nRow,const Point & _rPreferredPos)2780 void DbGridControl::executeRowContextMenu( long _nRow, const Point& _rPreferredPos )
2781 {
2782     VclBuilder aBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "svx/ui/rowsmenu.ui", "");
2783     VclPtr<PopupMenu> aContextMenu(aBuilder.get_menu("menu"));
2784 
2785     PreExecuteRowContextMenu( static_cast<sal_uInt16>(_nRow), *aContextMenu );
2786     aContextMenu->RemoveDisabledEntries( true, true );
2787     PostExecuteRowContextMenu( static_cast<sal_uInt16>(_nRow), *aContextMenu, aContextMenu->Execute( this, _rPreferredPos ) );
2788 }
2789 
Command(const CommandEvent & rEvt)2790 void DbGridControl::Command(const CommandEvent& rEvt)
2791 {
2792     switch (rEvt.GetCommand())
2793     {
2794         case CommandEventId::ContextMenu:
2795         {
2796             if ( !m_pSeekCursor )
2797             {
2798                 EditBrowseBox::Command(rEvt);
2799                 return;
2800             }
2801 
2802             if ( !rEvt.IsMouseEvent() )
2803             {   // context menu requested by keyboard
2804                 if ( GetSelectRowCount() )
2805                 {
2806                     long nRow = FirstSelectedRow( );
2807 
2808                     ::tools::Rectangle aRowRect( GetRowRectPixel( nRow ) );
2809                     executeRowContextMenu( nRow, aRowRect.LeftCenter() );
2810 
2811                     // handled
2812                     return;
2813                 }
2814             }
2815 
2816             sal_uInt16 nColId = GetColumnId(GetColumnAtXPosPixel(rEvt.GetMousePosPixel().X()));
2817             long   nRow = GetRowAtYPosPixel(rEvt.GetMousePosPixel().Y());
2818 
2819             if (nColId == HandleColumnId)
2820             {
2821                 executeRowContextMenu( nRow, rEvt.GetMousePosPixel() );
2822             }
2823             else if (canCopyCellText(nRow, nColId))
2824             {
2825                 VclBuilder aBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "svx/ui/cellmenu.ui", "");
2826                 VclPtr<PopupMenu> aContextMenu(aBuilder.get_menu("menu"));
2827                 if (aContextMenu->Execute(this, rEvt.GetMousePosPixel()))
2828                     copyCellText(nRow, nColId);
2829             }
2830             else
2831             {
2832                 EditBrowseBox::Command(rEvt);
2833                 return;
2834             }
2835 
2836             [[fallthrough]];
2837         }
2838         default:
2839             EditBrowseBox::Command(rEvt);
2840     }
2841 }
2842 
IMPL_LINK_NOARG(DbGridControl,OnDelete,void *,void)2843 IMPL_LINK_NOARG(DbGridControl, OnDelete, void*, void)
2844 {
2845     m_nDeleteEvent = nullptr;
2846     DeleteSelectedRows();
2847 }
2848 
DeleteSelectedRows()2849 void DbGridControl::DeleteSelectedRows()
2850 {
2851     DBG_ASSERT(GetSelection(), "no selection!!!");
2852 
2853     if (!m_pSeekCursor)
2854         return;
2855 }
2856 
GetController(long,sal_uInt16 nColumnId)2857 CellController* DbGridControl::GetController(long /*nRow*/, sal_uInt16 nColumnId)
2858 {
2859     if (!IsValid(m_xCurrentRow) || !IsEnabled())
2860         return nullptr;
2861 
2862     size_t Location = GetModelColumnPos(nColumnId);
2863     DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
2864     if (!pColumn)
2865         return nullptr;
2866 
2867     CellController* pReturn = nullptr;
2868     if (IsFilterMode())
2869         pReturn = pColumn->GetController().get();
2870     else
2871     {
2872         if (::comphelper::hasProperty(FM_PROP_ENABLED, pColumn->getModel()))
2873         {
2874             if (!::comphelper::getBOOL(pColumn->getModel()->getPropertyValue(FM_PROP_ENABLED)))
2875                 return nullptr;
2876         }
2877 
2878         bool bInsert = (m_xCurrentRow->IsNew() && (m_nOptions & DbGridControlOptions::Insert));
2879         bool bUpdate = (!m_xCurrentRow->IsNew() && (m_nOptions & DbGridControlOptions::Update));
2880 
2881         if ((bInsert && !pColumn->IsAutoValue()) || bUpdate)
2882         {
2883             pReturn = pColumn->GetController().get();
2884         }
2885     }
2886     return pReturn;
2887 }
2888 
CellModified()2889 void DbGridControl::CellModified()
2890 {
2891     SAL_INFO("svx.fmcomp", "DbGridControl::CellModified");
2892 
2893     {
2894         ::osl::MutexGuard aGuard(m_aAdjustSafety);
2895         if (m_nAsynAdjustEvent)
2896         {
2897             SAL_INFO("svx.fmcomp", "forcing a synchron call to " << (m_bPendingAdjustRows ? "AdjustRows" : "AdustDataSource"));
2898             RemoveUserEvent(m_nAsynAdjustEvent);
2899             m_nAsynAdjustEvent = nullptr;
2900 
2901             // force the call : this should be no problem as we're probably running in the solar thread here
2902             // (cell modified is triggered by user actions)
2903             if (m_bPendingAdjustRows)
2904                 AdjustRows();
2905             else
2906                 AdjustDataSource();
2907         }
2908     }
2909 
2910     if (!IsFilterMode() && IsValid(m_xCurrentRow) && !m_xCurrentRow->IsModified())
2911     {
2912         // enable edit mode
2913         // a data set should be inserted
2914         if (m_xCurrentRow->IsNew())
2915         {
2916             m_xCurrentRow->SetStatus(GridRowStatus::Modified);
2917             SAL_INFO("svx.fmcomp", "current row is new, new state: MODIFIED");
2918             // if no row was added yet, do it now
2919             if (m_nCurrentPos == GetRowCount() - 1)
2920             {
2921                 // increment RowCount
2922                 RowInserted(GetRowCount());
2923                 InvalidateStatusCell(m_nCurrentPos);
2924                 m_aBar->InvalidateAll(m_nCurrentPos);
2925             }
2926         }
2927         else if (m_xCurrentRow->GetStatus() != GridRowStatus::Modified)
2928         {
2929             m_xCurrentRow->SetState(m_pDataCursor.get(), false);
2930             SAL_INFO("svx.fmcomp", "current row is not new, after SetState, new state: " << ROWSTATUS(m_xCurrentRow));
2931             m_xCurrentRow->SetStatus(GridRowStatus::Modified);
2932             SAL_INFO("svx.fmcomp", "current row is not new, new state: MODIFIED");
2933             InvalidateStatusCell(m_nCurrentPos);
2934         }
2935     }
2936 }
2937 
Dispatch(sal_uInt16 nId)2938 void DbGridControl::Dispatch(sal_uInt16 nId)
2939 {
2940     if (nId == BROWSER_CURSORENDOFFILE)
2941     {
2942         if (m_nOptions & DbGridControlOptions::Insert)
2943             AppendNew();
2944         else
2945             MoveToLast();
2946     }
2947     else
2948         EditBrowseBox::Dispatch(nId);
2949 }
2950 
Undo()2951 void DbGridControl::Undo()
2952 {
2953     if (IsFilterMode() || !IsValid(m_xCurrentRow) || !IsModified())
2954         return;
2955 
2956     // check if we have somebody doin' the UNDO for us
2957     int nState = -1;
2958     if (m_aMasterStateProvider.IsSet())
2959         nState = m_aMasterStateProvider.Call(DbGridControlNavigationBarState::Undo);
2960     if (nState>0)
2961     {   // yes, we have, and the slot is enabled
2962         DBG_ASSERT(m_aMasterSlotExecutor.IsSet(), "DbGridControl::Undo : a state, but no execute link ?");
2963         bool lResult = m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::Undo);
2964         if (lResult)
2965             // handled
2966             return;
2967     }
2968     else if (nState == 0)
2969         // yes, we have, and the slot is disabled
2970         return;
2971 
2972     BeginCursorAction();
2973 
2974     bool bAppending = m_xCurrentRow->IsNew();
2975     bool bDirty     = m_xCurrentRow->IsModified();
2976 
2977     try
2978     {
2979         // cancel editing
2980         Reference< XResultSetUpdate >  xUpdateCursor(Reference< XInterface >(*m_pDataCursor), UNO_QUERY);
2981         // no effects if we're not updating currently
2982         if (bAppending)
2983             // just refresh the row
2984             xUpdateCursor->moveToInsertRow();
2985         else
2986             xUpdateCursor->cancelRowUpdates();
2987 
2988     }
2989     catch(Exception&)
2990     {
2991         DBG_UNHANDLED_EXCEPTION("svx");
2992     }
2993 
2994     EndCursorAction();
2995 
2996     m_xDataRow->SetState(m_pDataCursor.get(), false);
2997     if (m_xPaintRow == m_xCurrentRow)
2998         m_xPaintRow = m_xCurrentRow = m_xDataRow;
2999     else
3000         m_xCurrentRow = m_xDataRow;
3001 
3002     if (bAppending && (EditBrowseBox::IsModified() || bDirty))
3003         // remove the row
3004         if (m_nCurrentPos == GetRowCount() - 2)
3005         {   // maybe we already removed it (in resetCurrentRow, called if the above moveToInsertRow
3006             // caused our data source form to be reset - which should be the usual case...)
3007             RowRemoved(GetRowCount() - 1);
3008             m_aBar->InvalidateAll(m_nCurrentPos);
3009         }
3010 
3011     RowModified(m_nCurrentPos);
3012 }
3013 
resetCurrentRow()3014 void DbGridControl::resetCurrentRow()
3015 {
3016     if (IsModified())
3017     {
3018         // scenario : we're on the insert row, the row is dirty, and thus there exists a "second" insert row (which
3019         // is clean). Normally in DataSourcePropertyChanged we would remove this second row if the modified state of
3020         // the insert row changes from sal_True to sal_False. But if our current cell is the only modified element (means the
3021         // data source isn't modified) and we're reset this DataSourcePropertyChanged would never be called, so we
3022         // would never delete the obsolete "second insert row". Thus in this special case this method here
3023         // is the only possibility to determine the redundance of the row (resetCurrentRow is called when the
3024         // "first insert row" is about to be cleaned, so of course the "second insert row" is redundant now)
3025         Reference< XPropertySet > xDataSource = getDataSource()->getPropertySet();
3026         if (xDataSource.is() && !::comphelper::getBOOL(xDataSource->getPropertyValue(FM_PROP_ISMODIFIED)))
3027         {
3028             // are we on a new row currently ?
3029             if (m_xCurrentRow->IsNew())
3030             {
3031                 if (m_nCurrentPos == GetRowCount() - 2)
3032                 {
3033                     RowRemoved(GetRowCount() - 1);
3034                     m_aBar->InvalidateAll(m_nCurrentPos);
3035                 }
3036             }
3037         }
3038 
3039         // update the rows
3040         m_xDataRow->SetState(m_pDataCursor.get(), false);
3041         if (m_xPaintRow == m_xCurrentRow)
3042             m_xPaintRow = m_xCurrentRow = m_xDataRow;
3043         else
3044             m_xCurrentRow = m_xDataRow;
3045     }
3046 
3047     RowModified(GetCurRow()); // will update the current controller if affected
3048 }
3049 
RowModified(long nRow)3050 void DbGridControl::RowModified( long nRow )
3051 {
3052     if (nRow == m_nCurrentPos && IsEditing())
3053     {
3054         CellControllerRef aTmpRef = Controller();
3055         aTmpRef->ClearModified();
3056         InitController(aTmpRef, m_nCurrentPos, GetCurColumnId());
3057     }
3058     EditBrowseBox::RowModified(nRow);
3059 }
3060 
IsModified() const3061 bool DbGridControl::IsModified() const
3062 {
3063     return !IsFilterMode() && IsValid(m_xCurrentRow) && (m_xCurrentRow->IsModified() || EditBrowseBox::IsModified());
3064 }
3065 
IsCurrentAppending() const3066 bool DbGridControl::IsCurrentAppending() const
3067 {
3068     return m_xCurrentRow.is() && m_xCurrentRow->IsNew();
3069 }
3070 
IsInsertionRow(long nRow) const3071 bool DbGridControl::IsInsertionRow(long nRow) const
3072 {
3073     return (m_nOptions & DbGridControlOptions::Insert) && m_nTotalCount >= 0 && (nRow == GetRowCount() - 1);
3074 }
3075 
SaveModified()3076 bool DbGridControl::SaveModified()
3077 {
3078     SAL_INFO("svx.fmcomp", "DbGridControl::SaveModified");
3079     DBG_ASSERT(IsValid(m_xCurrentRow), "GridControl:: Invalid row");
3080     if (!IsValid(m_xCurrentRow))
3081         return true;
3082 
3083     // accept input for this field
3084     // Where there changes at the current input field?
3085     if (!EditBrowseBox::IsModified())
3086         return true;
3087 
3088     size_t Location = GetModelColumnPos( GetCurColumnId() );
3089     DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
3090     bool bOK = pColumn && pColumn->Commit();
3091     DBG_ASSERT( Controller().is(), "DbGridControl::SaveModified: was modified, by have no controller?!" );
3092     if ( !Controller().is() )
3093         // this might happen if the callbacks implicitly triggered by Commit
3094         // fiddled with the form or the control ...
3095         // (Note that this here is a workaround, at most. We need a general concept how
3096         // to treat this, you can imagine an arbitrary number of scenarios where a callback
3097         // triggers something which leaves us in an expected state.)
3098         // #i67147# / 2006-07-17 / frank.schoenheit@sun.com
3099         return bOK;
3100 
3101     if (bOK)
3102     {
3103         Controller()->ClearModified();
3104 
3105         if ( IsValid(m_xCurrentRow) )
3106         {
3107             m_xCurrentRow->SetState(m_pDataCursor.get(), false);
3108             SAL_INFO("svx.fmcomp", "explicit SetState, new state: " << ROWSTATUS(m_xCurrentRow));
3109             InvalidateStatusCell( m_nCurrentPos );
3110         }
3111         else
3112         {
3113             SAL_INFO("svx.fmcomp", "no SetState, new state: " << ROWSTATUS(m_xCurrentRow));
3114         }
3115     }
3116     else
3117     {
3118         // reset the modified flag...
3119         Controller()->SetModified();
3120     }
3121 
3122     return bOK;
3123 }
3124 
SaveRow()3125 bool DbGridControl::SaveRow()
3126 {
3127     SAL_INFO("svx.fmcomp", "DbGridControl::SaveRow");
3128     // valid row
3129     if (!IsValid(m_xCurrentRow) || !IsModified())
3130         return true;
3131     // value of the controller was not saved, yet
3132     else if (Controller().is() && Controller()->IsModified())
3133     {
3134         if (!SaveModified())
3135             return false;
3136     }
3137     m_bUpdating = true;
3138 
3139     BeginCursorAction();
3140     bool bAppending = m_xCurrentRow->IsNew();
3141     bool bSuccess = false;
3142     try
3143     {
3144         Reference< XResultSetUpdate >  xUpdateCursor(Reference< XInterface >(*m_pDataCursor), UNO_QUERY);
3145         if (bAppending)
3146             xUpdateCursor->insertRow();
3147         else
3148             xUpdateCursor->updateRow();
3149         bSuccess = true;
3150     }
3151     catch(SQLException&)
3152     {
3153         EndCursorAction();
3154         m_bUpdating = false;
3155         return false;
3156     }
3157 
3158     try
3159     {
3160         if (bSuccess)
3161         {
3162             // if we are appending we still sit on the insert row
3163             // we don't move just clear the flags not to move on the current row
3164             m_xCurrentRow->SetState(m_pDataCursor.get(), false);
3165             SAL_INFO("svx.fmcomp", "explicit SetState after a successful update, new state: " << ROWSTATUS(m_xCurrentRow));
3166             m_xCurrentRow->SetNew(false);
3167 
3168             // adjust the seekcursor if it is on the same position as the datacursor
3169             if (m_nSeekPos == m_nCurrentPos || bAppending)
3170             {
3171                 // get the bookmark to refetch the data
3172                 // in insert mode we take the new bookmark of the data cursor
3173                 Any aBookmark = bAppending ? m_pDataCursor->getBookmark() : m_pSeekCursor->getBookmark();
3174                 m_pSeekCursor->moveToBookmark(aBookmark);
3175                 // get the data
3176                 m_xSeekRow->SetState(m_pSeekCursor.get(), true);
3177                 m_nSeekPos = m_pSeekCursor->getRow() - 1;
3178             }
3179         }
3180         // and repaint the row
3181         RowModified(m_nCurrentPos);
3182     }
3183     catch(Exception&)
3184     {
3185     }
3186 
3187     m_bUpdating = false;
3188     EndCursorAction();
3189 
3190     // The old code returned (nRecords != 0) here.
3191     // Me thinks this is wrong : If something goes wrong while update the record, an exception will be thrown,
3192     // which results in a "return sal_False" (see above). If no exception is thrown, everything is fine. If nRecords
3193     // is zero, this simply means all fields had their original values.
3194     // FS - 06.12.99 - 70502
3195     return true;
3196 }
3197 
PreNotify(NotifyEvent & rEvt)3198 bool DbGridControl::PreNotify(NotifyEvent& rEvt)
3199 {
3200     // do not handle events of the Navbar
3201     if (m_aBar->IsWindowOrChild(rEvt.GetWindow()))
3202         return BrowseBox::PreNotify(rEvt);
3203 
3204     switch (rEvt.GetType())
3205     {
3206         case MouseNotifyEvent::KEYINPUT:
3207         {
3208             const KeyEvent* pKeyEvent = rEvt.GetKeyEvent();
3209 
3210             sal_uInt16 nCode = pKeyEvent->GetKeyCode().GetCode();
3211             bool   bShift = pKeyEvent->GetKeyCode().IsShift();
3212             bool   bCtrl = pKeyEvent->GetKeyCode().IsMod1();
3213             bool   bAlt = pKeyEvent->GetKeyCode().IsMod2();
3214             if ( ( KEY_TAB == nCode ) && bCtrl && !bAlt )
3215             {
3216                 // Ctrl-Tab is used to step out of the control, without traveling to the
3217                 // remaining cells first
3218                 // -> build a new key event without the Ctrl-key, and let the very base class handle it
3219                 vcl::KeyCode aNewCode( KEY_TAB, bShift, false, false, false );
3220                 KeyEvent aNewEvent( pKeyEvent->GetCharCode(), aNewCode );
3221 
3222                 // call the Control - our direct base class will interpret this in a way we do not want (and do
3223                 // a cell traveling)
3224                 Control::KeyInput( aNewEvent );
3225                 return true;
3226             }
3227 
3228             if ( !bShift && !bCtrl && ( KEY_ESCAPE == nCode ) )
3229             {
3230                 if (IsModified())
3231                 {
3232                     Undo();
3233                     return true;
3234                 }
3235             }
3236             else if ( ( KEY_DELETE == nCode ) && !bShift && !bCtrl )    // delete rows
3237             {
3238                 if ((m_nOptions & DbGridControlOptions::Delete) && GetSelectRowCount())
3239                 {
3240                     // delete asynchronously
3241                     if (m_nDeleteEvent)
3242                         Application::RemoveUserEvent(m_nDeleteEvent);
3243                     m_nDeleteEvent = Application::PostUserEvent(LINK(this,DbGridControl,OnDelete), nullptr, true);
3244                     return true;
3245                 }
3246             }
3247 
3248             [[fallthrough]];
3249         }
3250         default:
3251             return EditBrowseBox::PreNotify(rEvt);
3252     }
3253 }
3254 
IsTabAllowed(bool bRight) const3255 bool DbGridControl::IsTabAllowed(bool bRight) const
3256 {
3257     if (bRight)
3258         // Tab only if not on the _last_ row
3259         return GetCurRow() < (GetRowCount() - 1) || !m_bRecordCountFinal ||
3260                GetViewColumnPos(GetCurColumnId()) < (GetViewColCount() - 1);
3261     else
3262     {
3263         // Tab only if not on the _first_ row
3264         return GetCurRow() > 0 || (GetCurColumnId() && GetViewColumnPos(GetCurColumnId()) > 0);
3265     }
3266 }
3267 
KeyInput(const KeyEvent & rEvt)3268 void DbGridControl::KeyInput( const KeyEvent& rEvt )
3269 {
3270     if (rEvt.GetKeyCode().GetFunction() == KeyFuncType::COPY)
3271     {
3272         long nRow = GetCurRow();
3273         sal_uInt16 nColId = GetCurColumnId();
3274         if (nRow >= 0 && nRow < GetRowCount() && nColId < ColCount())
3275         {
3276             size_t Location = GetModelColumnPos( nColId );
3277             DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
3278             OStringTransfer::CopyString( GetCurrentRowCellText( pColumn, m_xCurrentRow ), this );
3279             return;
3280         }
3281     }
3282     EditBrowseBox::KeyInput(rEvt);
3283 }
3284 
HideColumn(sal_uInt16 nId)3285 void DbGridControl::HideColumn(sal_uInt16 nId)
3286 {
3287     DeactivateCell();
3288 
3289     // determine the col for the focus to set to after removal
3290     sal_uInt16 nPos = GetViewColumnPos(nId);
3291     sal_uInt16 nNewColId = nPos == (ColCount()-1)
3292         ? GetColumnIdFromViewPos(nPos-1)    // last col is to be removed -> take the previous
3293         : GetColumnIdFromViewPos(nPos+1);   // take the next
3294 
3295     long lCurrentWidth = GetColumnWidth(nId);
3296     EditBrowseBox::RemoveColumn(nId);
3297         // don't use my own RemoveColumn, this would remove it from m_aColumns, too
3298 
3299     // update my model
3300     size_t Location = GetModelColumnPos( nId );
3301     DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
3302     DBG_ASSERT(pColumn, "DbGridControl::HideColumn : somebody did hide a nonexistent column !");
3303     if (pColumn)
3304     {
3305         pColumn->m_bHidden = true;
3306         pColumn->m_nLastVisibleWidth = CalcReverseZoom(lCurrentWidth);
3307     }
3308 
3309     // and reset the focus
3310     if ( nId == GetCurColumnId() )
3311         GoToColumnId( nNewColId );
3312 }
3313 
ShowColumn(sal_uInt16 nId)3314 void DbGridControl::ShowColumn(sal_uInt16 nId)
3315 {
3316     sal_uInt16 nPos = GetModelColumnPos(nId);
3317     DBG_ASSERT(nPos != GRID_COLUMN_NOT_FOUND, "DbGridControl::ShowColumn : invalid argument !");
3318     if (nPos == GRID_COLUMN_NOT_FOUND)
3319         return;
3320 
3321     DbGridColumn* pColumn = m_aColumns[ nPos ].get();
3322     if (!pColumn->IsHidden())
3323     {
3324         DBG_ASSERT(GetViewColumnPos(nId) != GRID_COLUMN_NOT_FOUND, "DbGridControl::ShowColumn : inconsistent internal state !");
3325             // if the column isn't marked as hidden, it should be visible, shouldn't it ?
3326         return;
3327     }
3328     DBG_ASSERT(GetViewColumnPos(nId) == GRID_COLUMN_NOT_FOUND, "DbGridControl::ShowColumn : inconsistent internal state !");
3329         // the opposite situation ...
3330 
3331     // to determine the new view position we need an adjacent non-hidden column
3332     sal_uInt16 nNextNonHidden = BROWSER_INVALIDID;
3333     // first search the cols to the right
3334     for ( size_t i = nPos + 1; i < m_aColumns.size(); ++i )
3335     {
3336         DbGridColumn* pCurCol = m_aColumns[ i ].get();
3337         if (!pCurCol->IsHidden())
3338         {
3339             nNextNonHidden = i;
3340             break;
3341         }
3342     }
3343     if ((nNextNonHidden == BROWSER_INVALIDID) && (nPos > 0))
3344     {
3345         // then to the left
3346         for ( size_t i = nPos; i > 0; --i )
3347         {
3348             DbGridColumn* pCurCol = m_aColumns[ i-1 ].get();
3349             if (!pCurCol->IsHidden())
3350             {
3351                 nNextNonHidden = i-1;
3352                 break;
3353             }
3354         }
3355     }
3356     sal_uInt16 nNewViewPos = (nNextNonHidden == BROWSER_INVALIDID)
3357         ? 1 // there is no visible column -> insert behind the handle col
3358         : GetViewColumnPos( m_aColumns[ nNextNonHidden ]->GetId() ) + 1;
3359             // the first non-handle col has "view pos" 0, but the pos arg for InsertDataColumn expects
3360             // a position 1 for the first non-handle col -> +1
3361     DBG_ASSERT(nNewViewPos != GRID_COLUMN_NOT_FOUND, "DbGridControl::ShowColumn : inconsistent internal state !");
3362         // we found a col marked as visible but got no view pos for it ...
3363 
3364     if ((nNextNonHidden<nPos) && (nNextNonHidden != BROWSER_INVALIDID))
3365         // nNextNonHidden is a column to the left, so we want to insert the new col _right_ beside it's pos
3366         ++nNewViewPos;
3367 
3368     DeactivateCell();
3369 
3370     OUString aName;
3371     pColumn->getModel()->getPropertyValue(FM_PROP_LABEL) >>= aName;
3372     InsertDataColumn(nId, aName, CalcZoom(pColumn->m_nLastVisibleWidth), HeaderBarItemBits::CENTER | HeaderBarItemBits::CLICKABLE, nNewViewPos);
3373     pColumn->m_bHidden = false;
3374 
3375     ActivateCell();
3376     Invalidate();
3377 }
3378 
GetColumnIdFromModelPos(sal_uInt16 nPos) const3379 sal_uInt16 DbGridControl::GetColumnIdFromModelPos( sal_uInt16 nPos ) const
3380 {
3381     if (nPos >= m_aColumns.size())
3382     {
3383         OSL_FAIL("DbGridControl::GetColumnIdFromModelPos : invalid argument !");
3384         return GRID_COLUMN_NOT_FOUND;
3385     }
3386 
3387     DbGridColumn* pCol = m_aColumns[ nPos ].get();
3388 #if (OSL_DEBUG_LEVEL > 0) || defined DBG_UTIL
3389     // in the debug version, we convert the ModelPos into a ViewPos and compare this with the
3390     // value we will return (nId at the corresponding Col in m_aColumns)
3391 
3392     if (!pCol->IsHidden())
3393     {   // makes sense only if the column is visible
3394         sal_uInt16 nViewPos = nPos;
3395         for ( size_t i = 0; i < m_aColumns.size() && i < nPos; ++i)
3396             if ( m_aColumns[ i ]->IsHidden())
3397                 --nViewPos;
3398 
3399         DBG_ASSERT(pCol && GetColumnIdFromViewPos(nViewPos) == pCol->GetId(),
3400             "DbGridControl::GetColumnIdFromModelPos : this isn't consistent... did I misunderstand something ?");
3401     }
3402 #endif
3403     return pCol->GetId();
3404 }
3405 
GetModelColumnPos(sal_uInt16 nId) const3406 sal_uInt16 DbGridControl::GetModelColumnPos( sal_uInt16 nId ) const
3407 {
3408     for ( size_t i = 0; i < m_aColumns.size(); ++i )
3409         if ( m_aColumns[ i ]->GetId() == nId )
3410             return i;
3411 
3412     return GRID_COLUMN_NOT_FOUND;
3413 }
3414 
implAdjustInSolarThread(bool _bRows)3415 void DbGridControl::implAdjustInSolarThread(bool _bRows)
3416 {
3417     SAL_INFO("svx.fmcomp", "DbGridControl::implAdjustInSolarThread");
3418     ::osl::MutexGuard aGuard(m_aAdjustSafety);
3419     if (!Application::IsMainThread())
3420     {
3421         m_nAsynAdjustEvent = PostUserEvent(LINK(this, DbGridControl, OnAsyncAdjust), reinterpret_cast< void* >( _bRows ), true);
3422         m_bPendingAdjustRows = _bRows;
3423         if (_bRows)
3424             SAL_INFO("svx.fmcomp", "posting an AdjustRows");
3425         else
3426             SAL_INFO("svx.fmcomp", "posting an AdjustDataSource");
3427     }
3428     else
3429     {
3430         if (_bRows)
3431             SAL_INFO("svx.fmcomp", "doing an AdjustRows");
3432         else
3433             SAL_INFO("svx.fmcomp", "doing an AdjustDataSource");
3434         // always adjust the rows before adjusting the data source
3435         // If this is not necessary (because the row count did not change), nothing is done
3436         // The problem is that we can't rely on the order of which the calls come in: If the cursor was moved
3437         // to a position behind row count know 'til now, the cursorMoved notification may come before the
3438         // RowCountChanged notification
3439         // 94093 - 02.11.2001 - frank.schoenheit@sun.com
3440         AdjustRows();
3441 
3442         if ( !_bRows )
3443             AdjustDataSource();
3444     }
3445 }
3446 
IMPL_LINK(DbGridControl,OnAsyncAdjust,void *,pAdjustWhat,void)3447 IMPL_LINK(DbGridControl, OnAsyncAdjust, void*, pAdjustWhat, void)
3448 {
3449     m_nAsynAdjustEvent = nullptr;
3450 
3451     AdjustRows();
3452         // see implAdjustInSolarThread for a comment why we do this every time
3453 
3454     if ( !pAdjustWhat )
3455         AdjustDataSource();
3456 }
3457 
BeginCursorAction()3458 void DbGridControl::BeginCursorAction()
3459 {
3460     if (m_pFieldListeners)
3461     {
3462         ColumnFieldValueListeners* pListeners = static_cast<ColumnFieldValueListeners*>(m_pFieldListeners);
3463         for (const auto& rListener : *pListeners)
3464         {
3465             GridFieldValueListener* pCurrent = rListener.second;
3466             if (pCurrent)
3467                 pCurrent->suspend();
3468         }
3469     }
3470 
3471     if (m_pDataSourcePropListener)
3472         m_pDataSourcePropListener->suspend();
3473 }
3474 
EndCursorAction()3475 void DbGridControl::EndCursorAction()
3476 {
3477     if (m_pFieldListeners)
3478     {
3479         ColumnFieldValueListeners* pListeners = static_cast<ColumnFieldValueListeners*>(m_pFieldListeners);
3480         for (const auto& rListener : *pListeners)
3481         {
3482             GridFieldValueListener* pCurrent = rListener.second;
3483             if (pCurrent)
3484                 pCurrent->resume();
3485         }
3486     }
3487 
3488     if (m_pDataSourcePropListener)
3489         m_pDataSourcePropListener->resume();
3490 }
3491 
ConnectToFields()3492 void DbGridControl::ConnectToFields()
3493 {
3494     ColumnFieldValueListeners* pListeners = static_cast<ColumnFieldValueListeners*>(m_pFieldListeners);
3495     DBG_ASSERT(!pListeners || pListeners->empty(), "DbGridControl::ConnectToFields : please call DisconnectFromFields first !");
3496 
3497     if (!pListeners)
3498     {
3499         pListeners = new ColumnFieldValueListeners;
3500         m_pFieldListeners = pListeners;
3501     }
3502 
3503     for (auto const & pCurrent : m_aColumns)
3504     {
3505         sal_uInt16 nViewPos = pCurrent ? GetViewColumnPos(pCurrent->GetId()) : GRID_COLUMN_NOT_FOUND;
3506         if (GRID_COLUMN_NOT_FOUND == nViewPos)
3507             continue;
3508 
3509         Reference< XPropertySet >  xField = pCurrent->GetField();
3510         if (!xField.is())
3511             continue;
3512 
3513         // column is visible and bound here
3514         GridFieldValueListener*& rpListener = (*pListeners)[pCurrent->GetId()];
3515         DBG_ASSERT(!rpListener, "DbGridControl::ConnectToFields : already a listener for this column ?!");
3516         rpListener = new GridFieldValueListener(*this, xField, pCurrent->GetId());
3517     }
3518 }
3519 
DisconnectFromFields()3520 void DbGridControl::DisconnectFromFields()
3521 {
3522     if (!m_pFieldListeners)
3523         return;
3524 
3525     ColumnFieldValueListeners* pListeners = static_cast<ColumnFieldValueListeners*>(m_pFieldListeners);
3526     while (!pListeners->empty())
3527     {
3528         sal_Int32 nOldSize = pListeners->size();
3529         pListeners->begin()->second->dispose();
3530         DBG_ASSERT(nOldSize > static_cast<sal_Int32>(pListeners->size()), "DbGridControl::DisconnectFromFields : dispose on a listener should result in a removal from my list !");
3531     }
3532 
3533     delete pListeners;
3534     m_pFieldListeners = nullptr;
3535 }
3536 
FieldValueChanged(sal_uInt16 _nId)3537 void DbGridControl::FieldValueChanged(sal_uInt16 _nId)
3538 {
3539     osl::MutexGuard aPreventDestruction(m_aDestructionSafety);
3540     // needed as this may run in a thread other than the main one
3541     if (GetRowStatus(GetCurRow()) != EditBrowseBox::MODIFIED)
3542         // all other cases are handled elsewhere
3543         return;
3544 
3545     size_t Location = GetModelColumnPos( _nId );
3546     DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
3547     if (pColumn)
3548     {
3549         std::unique_ptr<vcl::SolarMutexTryAndBuyGuard> pGuard;
3550         while (!m_bWantDestruction && (!pGuard || !pGuard->isAcquired()))
3551             pGuard.reset(new vcl::SolarMutexTryAndBuyGuard);
3552 
3553         if (m_bWantDestruction)
3554         {   // at this moment, within another thread, our destructor tries to destroy the listener which called this method
3555             // => don't do anything
3556             // 73365 - 23.02.00 - FS
3557             return;
3558         }
3559 
3560         // and finally do the update ...
3561         pColumn->UpdateFromField(m_xCurrentRow.get(), m_xFormatter);
3562         RowModified(GetCurRow());
3563     }
3564 }
3565 
FieldListenerDisposing(sal_uInt16 _nId)3566 void DbGridControl::FieldListenerDisposing(sal_uInt16 _nId)
3567 {
3568     ColumnFieldValueListeners* pListeners = static_cast<ColumnFieldValueListeners*>(m_pFieldListeners);
3569     if (!pListeners)
3570     {
3571         OSL_FAIL("DbGridControl::FieldListenerDisposing : invalid call (have no listener array) !");
3572         return;
3573     }
3574 
3575     ColumnFieldValueListeners::const_iterator aPos = pListeners->find(_nId);
3576     if (aPos == pListeners->end())
3577     {
3578         OSL_FAIL("DbGridControl::FieldListenerDisposing : invalid call (did not find the listener) !");
3579         return;
3580     }
3581 
3582     delete aPos->second;
3583 
3584     pListeners->erase(aPos);
3585 }
3586 
disposing(sal_uInt16 _nId)3587 void DbGridControl::disposing(sal_uInt16 _nId)
3588 {
3589     if (_nId == 0)
3590     {   // the seek cursor is being disposed
3591         ::osl::MutexGuard aGuard(m_aAdjustSafety);
3592         setDataSource(nullptr, DbGridControlOptions::Readonly); // our clone was disposed so we set our datasource to null to avoid later access to it
3593         if (m_nAsynAdjustEvent)
3594         {
3595             RemoveUserEvent(m_nAsynAdjustEvent);
3596             m_nAsynAdjustEvent = nullptr;
3597         }
3598     }
3599 }
3600 
GetAccessibleControlCount() const3601 sal_Int32 DbGridControl::GetAccessibleControlCount() const
3602 {
3603     return EditBrowseBox::GetAccessibleControlCount() + 1; // the navigation control
3604 }
3605 
CreateAccessibleControl(sal_Int32 _nIndex)3606 Reference<XAccessible > DbGridControl::CreateAccessibleControl( sal_Int32 _nIndex )
3607 {
3608     Reference<XAccessible > xRet;
3609     if ( _nIndex == EditBrowseBox::GetAccessibleControlCount() )
3610     {
3611         xRet = m_aBar->GetAccessible();
3612     }
3613     else
3614         xRet = EditBrowseBox::CreateAccessibleControl( _nIndex );
3615     return xRet;
3616 }
3617 
CreateAccessibleCell(sal_Int32 _nRow,sal_uInt16 _nColumnPos)3618 Reference< XAccessible > DbGridControl::CreateAccessibleCell( sal_Int32 _nRow, sal_uInt16 _nColumnPos )
3619 {
3620     sal_uInt16 nColumnId = GetColumnId( _nColumnPos );
3621     size_t Location = GetModelColumnPos(nColumnId);
3622     DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
3623     if ( pColumn )
3624     {
3625         Reference< css::awt::XControl> xInt(pColumn->GetCell());
3626         Reference< css::awt::XCheckBox> xBox(xInt,UNO_QUERY);
3627         if ( xBox.is() )
3628         {
3629             TriState eValue = TRISTATE_FALSE;
3630             switch( xBox->getState() )
3631             {
3632                 case 0:
3633                     eValue = TRISTATE_FALSE;
3634                     break;
3635                 case 1:
3636                     eValue = TRISTATE_TRUE;
3637                     break;
3638                 case 2:
3639                     eValue = TRISTATE_INDET;
3640                     break;
3641             }
3642             return EditBrowseBox::CreateAccessibleCheckBoxCell( _nRow, _nColumnPos,eValue );
3643         }
3644     }
3645     return EditBrowseBox::CreateAccessibleCell( _nRow, _nColumnPos );
3646 }
3647 
3648 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
3649