1
0
Fork 0
libreoffice/dbaccess/source/ui/control/RelationControl.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

691 lines
24 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <RelationControl.hxx>
#include <svtools/editbrowsebox.hxx>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <comphelper/diagnose_ex.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <TableConnectionData.hxx>
#include <TableConnection.hxx>
#include <TableWindow.hxx>
#include <com/sun/star/awt/XWindow.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
#include <RelControliFace.hxx>
#include <helpids.h>
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <vector>
#include <utility>
using std::pair;
using std::make_pair;
#define SOURCE_COLUMN 1
#define DEST_COLUMN 2
namespace dbaui
{
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::sdbcx;
using namespace ::com::sun::star::container;
using namespace svt;
typedef ::svt::EditBrowseBox ORelationControl_Base;
class ORelationControl : public ORelationControl_Base
{
friend class OTableListBoxControl;
VclPtr< ::svt::ListBoxControl> m_pListCell;
TTableConnectionData::value_type m_pConnData;
OTableListBoxControl* m_pBoxControl;
tools::Long m_nDataPos;
Reference< XPropertySet> m_xSourceDef;
Reference< XPropertySet> m_xDestDef;
enum opcode { DELETE, INSERT, MODIFY };
typedef std::vector< pair < opcode, pair < OConnectionLineDataVec::size_type, OConnectionLineDataVec::size_type> > > ops_type;
ops_type m_ops;
void fillListBox(const Reference< XPropertySet>& _xDest);
/** returns the column id for the editbrowsebox
@param _nColId
the column id SOURCE_COLUMN or DEST_COLUMN
@return the current column id either SOURCE_COLUMN or DEST_COLUMN depends on the connection data
*/
sal_uInt16 getColumnIdent( sal_uInt16 _nColId ) const;
public:
explicit ORelationControl(const css::uno::Reference<css::awt::XWindow>& rParent);
void SetController(OTableListBoxControl* pController)
{
m_pBoxControl = pController;
}
/** searches for a connection between these two tables
@param _pSource
the left table
@param _pDest
the right window
*/
void setWindowTables(const OTableWindow* _pSource,const OTableWindow* _pDest);
/** allows to access the connection data from outside
@return the connection data
*/
const TTableConnectionData::value_type& getData() const { return m_pConnData; }
void lateInit();
protected:
virtual ~ORelationControl() override { disposeOnce(); }
virtual void dispose() override { m_pListCell.disposeAndClear(); ORelationControl_Base::dispose(); }
virtual void Resize() override;
virtual Size GetOptimalSize() const override;
virtual bool PreNotify(NotifyEvent& rNEvt ) override;
virtual bool IsTabAllowed(bool bForward) const override;
void Init(const TTableConnectionData::value_type& _pConnData);
using ORelationControl_Base::Init;
virtual void InitController( ::svt::CellControllerRef& rController, sal_Int32 nRow, sal_uInt16 nCol ) override;
virtual ::svt::CellController* GetController( sal_Int32 nRow, sal_uInt16 nCol ) override;
virtual void PaintCell( OutputDevice& rDev, const tools::Rectangle& rRect, sal_uInt16 nColId ) const override;
virtual bool SeekRow( sal_Int32 nRow ) override;
virtual bool SaveModified() override;
virtual OUString GetCellText( sal_Int32 nRow, sal_uInt16 nColId ) const override;
virtual void CellModified() override;
DECL_LINK( AsynchDeactivate, void*, void );
private:
DECL_LINK( AsynchActivate, void*, void );
};
ORelationControl::ORelationControl(const css::uno::Reference<css::awt::XWindow>& rParent)
: EditBrowseBox(VCLUnoHelper::GetWindow(rParent),
EditBrowseBoxFlags::SMART_TAB_TRAVEL | EditBrowseBoxFlags::NO_HANDLE_COLUMN_CONTENT,
WB_TABSTOP | WB_BORDER,
BrowserMode::AUTOSIZE_LASTCOL)
, m_pBoxControl(nullptr)
, m_nDataPos(0)
{
}
void ORelationControl::Init(const TTableConnectionData::value_type& _pConnData)
{
m_pConnData = _pConnData;
OSL_ENSURE(m_pConnData, "No data supplied!");
m_pConnData->normalizeLines();
}
void ORelationControl::lateInit()
{
if ( !m_pConnData )
return;
m_xSourceDef = m_pConnData->getReferencingTable()->getTable();
m_xDestDef = m_pConnData->getReferencedTable()->getTable();
if ( ColCount() == 0 )
{
InsertDataColumn( SOURCE_COLUMN, m_pConnData->getReferencingTable()->GetWinName(), 100);
InsertDataColumn( DEST_COLUMN, m_pConnData->getReferencedTable()->GetWinName(), 100);
// If the Defs do not yet exits, we need to set them with SetSource-/-DestDef
m_pListCell.reset( VclPtr<ListBoxControl>::Create( &GetDataWindow() ) );
// set browse mode
SetMode( BrowserMode::COLUMNSELECTION |
BrowserMode::HLINES |
BrowserMode::VLINES |
BrowserMode::HIDECURSOR |
BrowserMode::HIDESELECT |
BrowserMode::AUTO_HSCROLL |
BrowserMode::AUTO_VSCROLL);
}
else
// not the first call
RowRemoved(0, GetRowCount());
RowInserted(0, m_pConnData->GetConnLineDataList().size() + 1); // add one extra row
}
void ORelationControl::Resize()
{
EditBrowseBox::Resize();
tools::Long nOutputWidth = GetOutputSizePixel().Width() - 1;
SetColumnWidth(1, (nOutputWidth / 2));
SetColumnWidth(2, (nOutputWidth / 2));
}
bool ORelationControl::PreNotify(NotifyEvent& rNEvt)
{
if (rNEvt.GetType() == NotifyEventType::LOSEFOCUS && !HasChildPathFocus() && !ControlHasFocus())
PostUserEvent(LINK(this, ORelationControl, AsynchDeactivate), nullptr, true);
else if (rNEvt.GetType() == NotifyEventType::GETFOCUS)
PostUserEvent(LINK(this, ORelationControl, AsynchActivate), nullptr, true);
return EditBrowseBox::PreNotify(rNEvt);
}
IMPL_LINK_NOARG(ORelationControl, AsynchActivate, void*, void)
{
ActivateCell();
}
IMPL_LINK_NOARG(ORelationControl, AsynchDeactivate, void*, void)
{
DeactivateCell();
}
bool ORelationControl::IsTabAllowed(bool bForward) const
{
sal_Int32 nRow = GetCurRow();
sal_uInt16 nCol = GetCurColumnId();
bool bRet = !( ( bForward && (nCol == DEST_COLUMN) && (nRow == GetRowCount() - 1))
|| (!bForward && (nCol == SOURCE_COLUMN) && (nRow == 0)));
return bRet && EditBrowseBox::IsTabAllowed(bForward);
}
bool ORelationControl::SaveModified()
{
sal_Int32 nRow = GetCurRow();
if ( nRow != BROWSER_ENDOFSELECTION )
{
weld::ComboBox& rListBox = m_pListCell->get_widget();
OUString sFieldName(rListBox.get_active_text());
OConnectionLineDataVec& rLines = m_pConnData->GetConnLineDataList();
if ( rLines.size() <= o3tl::make_unsigned(nRow) )
{
rLines.emplace_back(new OConnectionLineData());
nRow = rLines.size() - 1;
// add new past-rLines row
m_ops.emplace_back(INSERT, make_pair(nRow+1, nRow+2));
}
OConnectionLineDataRef pConnLineData = rLines[nRow];
switch( getColumnIdent( GetCurColumnId() ) )
{
case SOURCE_COLUMN:
pConnLineData->SetSourceFieldName( sFieldName );
break;
case DEST_COLUMN:
pConnLineData->SetDestFieldName( sFieldName );
break;
}
// the modification we just did does *not* need to be registered in m_ops;
// it is already taken into account (by the codepath that called us)
//m_ops.push_back(make_pair(MODIFY, make_pair(nRow, nRow+1)));
}
const OConnectionLineDataVec::size_type oldSize = m_pConnData->GetConnLineDataList().size();
OConnectionLineDataVec::size_type line = m_pConnData->normalizeLines();
const OConnectionLineDataVec::size_type newSize = m_pConnData->GetConnLineDataList().size();
assert(newSize <= oldSize);
m_ops.emplace_back(MODIFY, make_pair(line, newSize));
m_ops.emplace_back(DELETE, make_pair(newSize, oldSize));
return true;
}
sal_uInt16 ORelationControl::getColumnIdent( sal_uInt16 _nColId ) const
{
sal_uInt16 nId = _nColId;
if ( m_pConnData->getReferencingTable() != m_pBoxControl->getReferencingTable() )
nId = ( _nColId == SOURCE_COLUMN) ? DEST_COLUMN : SOURCE_COLUMN;
return nId;
}
OUString ORelationControl::GetCellText( sal_Int32 nRow, sal_uInt16 nColId ) const
{
OUString sText;
if ( m_pConnData->GetConnLineDataList().size() > o3tl::make_unsigned(nRow) )
{
OConnectionLineDataRef pConnLineData = m_pConnData->GetConnLineDataList()[nRow];
switch( getColumnIdent( nColId ) )
{
case SOURCE_COLUMN:
sText = pConnLineData->GetSourceFieldName();
break;
case DEST_COLUMN:
sText = pConnLineData->GetDestFieldName();
break;
}
}
return sText;
}
void ORelationControl::InitController( CellControllerRef& /*rController*/, sal_Int32 nRow, sal_uInt16 nColumnId )
{
OUString sHelpId( HID_RELATIONDIALOG_LEFTFIELDCELL );
Reference< XPropertySet> xDef;
switch ( getColumnIdent(nColumnId) )
{
case SOURCE_COLUMN:
xDef = m_xSourceDef;
sHelpId = HID_RELATIONDIALOG_LEFTFIELDCELL;
break;
case DEST_COLUMN:
xDef = m_xDestDef;
sHelpId = HID_RELATIONDIALOG_RIGHTFIELDCELL;
break;
default:
// ?????????
break;
}
if ( !xDef.is() )
return;
fillListBox(xDef);
OUString sName = GetCellText( nRow, nColumnId );
weld::ComboBox& rList = m_pListCell->get_widget();
rList.set_active_text(sName);
if (rList.get_active_text() != sName)
{
rList.append_text(sName);
rList.set_active_text(sName);
}
rList.set_help_id(sHelpId);
}
CellController* ORelationControl::GetController( sal_Int32 /*nRow*/, sal_uInt16 /*nColumnId*/ )
{
return new ListBoxCellController( m_pListCell.get() );
}
bool ORelationControl::SeekRow( sal_Int32 nRow )
{
m_nDataPos = nRow;
return true;
}
void ORelationControl::PaintCell( OutputDevice& rDev, const tools::Rectangle& rRect, sal_uInt16 nColumnId ) const
{
OUString aText = GetCellText( m_nDataPos, nColumnId );
Point aPos( rRect.TopLeft() );
Size aTextSize( GetDataWindow().GetTextWidth( aText ), GetDataWindow().GetTextHeight() );
if( aPos.X() < rRect.Left() || aPos.X() + aTextSize.Width() > rRect.Right() ||
aPos.Y() < rRect.Top() || aPos.Y() + aTextSize.Height() > rRect.Bottom() )
{
rDev.SetClipRegion(vcl::Region(rRect));
}
rDev.DrawText( aPos, aText );
if( rDev.IsClipRegion() )
rDev.SetClipRegion();
}
void ORelationControl::fillListBox(const Reference< XPropertySet>& _xDest)
{
weld::ComboBox& rList = m_pListCell->get_widget();
rList.clear();
try
{
if ( _xDest.is() )
{
//sal_Int32 nRows = GetRowCount();
Reference<XColumnsSupplier> xSup(_xDest,UNO_QUERY);
Reference<XNameAccess> xColumns = xSup->getColumns();
for (auto& text : xColumns->getElementNames())
{
rList.append_text(text);
}
rList.insert_text(0, OUString());
}
}
catch( const Exception& )
{
DBG_UNHANDLED_EXCEPTION("dbaccess");
}
}
void ORelationControl::setWindowTables(const OTableWindow* _pSource,const OTableWindow* _pDest)
{
// If I edit here, hide
bool bWasEditing = IsEditing();
if ( bWasEditing )
DeactivateCell();
if ( _pSource && _pDest )
{
m_xSourceDef = _pSource->GetTable();
SetColumnTitle(1, _pSource->GetName());
m_xDestDef = _pDest->GetTable();
SetColumnTitle(2, _pDest->GetName());
const OJoinTableView* pView = _pSource->getTableView();
OTableConnection* pConn = pView->GetTabConn(_pSource,_pDest);
if ( pConn && !m_pConnData->GetConnLineDataList().empty() )
{
m_pConnData->CopyFrom(*pConn->GetData());
m_pBoxControl->getContainer()->notifyConnectionChange();
}
else
{
// no connection found so we clear our data
OConnectionLineDataVec& rLines = m_pConnData->GetConnLineDataList();
for( const auto& rLine : rLines )
{
rLine->Reset();
}
m_pConnData->setReferencingTable(_pSource->GetData());
m_pConnData->setReferencedTable(_pDest->GetData());
}
m_pConnData->normalizeLines();
}
// Repaint
Invalidate();
if ( bWasEditing )
{
GoToRow(0);
ActivateCell();
}
}
void ORelationControl::CellModified()
{
EditBrowseBox::CellModified();
SaveModified();
assert(m_pBoxControl);
m_pBoxControl->NotifyCellChange();
}
Size ORelationControl::GetOptimalSize() const
{
return LogicToPixel(Size(140, 80), MapMode(MapUnit::MapAppFont));
}
OTableListBoxControl::OTableListBoxControl(weld::Builder* _pParent,
const OJoinTableView::OTableWindowMap* _pTableMap,
IRelationControlInterface* _pParentDialog)
: m_xLeftTable(_pParent->weld_combo_box(u"table1"_ustr))
, m_xRightTable(_pParent->weld_combo_box(u"table2"_ustr))
, m_xTable(_pParent->weld_container(u"relations"_ustr))
, m_xTableCtrlParent(m_xTable->CreateChildFrame())
, m_xRC_Tables(VclPtr<ORelationControl>::Create(m_xTableCtrlParent))
, m_pTableMap(_pTableMap)
, m_pParentDialog(_pParentDialog)
{
Size aPrefSize = m_xRC_Tables->GetOptimalSize();
m_xTable->set_size_request(aPrefSize.Width(), aPrefSize.Height());
m_xRC_Tables->SetController(this);
m_xRC_Tables->Init();
lateUIInit();
Link<weld::ComboBox&,void> aLink(LINK(this, OTableListBoxControl, OnTableChanged));
m_xLeftTable->connect_changed(aLink);
m_xRightTable->connect_changed(aLink);
}
OTableListBoxControl::~OTableListBoxControl()
{
m_xRC_Tables.disposeAndClear();
m_xTableCtrlParent->dispose();
m_xTableCtrlParent.clear();
}
void OTableListBoxControl::fillListBoxes()
{
OSL_ENSURE( !m_pTableMap->empty(), "OTableListBoxControl::fillListBoxes: no table window!");
OTableWindow* pInitialLeft = nullptr;
OTableWindow* pInitialRight = nullptr;
// Collect the names of all TabWins
for (auto const& elem : *m_pTableMap)
{
m_xLeftTable->append_text(elem.first);
m_xRightTable->append_text(elem.first);
if (!pInitialLeft)
{
pInitialLeft = elem.second;
m_strCurrentLeft = elem.first;
}
else if (!pInitialRight)
{
pInitialRight = elem.second;
m_strCurrentRight = elem.first;
}
}
if ( !pInitialRight )
{
pInitialRight = pInitialLeft;
m_strCurrentRight = m_strCurrentLeft;
}
// The corresponding Defs for my Controls
m_xRC_Tables->setWindowTables(pInitialLeft,pInitialRight);
// The table selected in a ComboBox must not be available in the other
if ( m_pTableMap->size() > 2 )
{
m_xLeftTable->remove_text(m_strCurrentRight);
m_xRightTable->remove_text(m_strCurrentLeft);
}
// Select the first one on the left side and on the right side,
// select the second one
m_xLeftTable->set_active_text(m_strCurrentLeft);
m_xRightTable->set_active_text(m_strCurrentRight);
m_xLeftTable->grab_focus();
}
IMPL_LINK(OTableListBoxControl, OnTableChanged, weld::ComboBox&, rListBox, void)
{
OUString strSelected(rListBox.get_active_text());
OTableWindow* pLeft = nullptr;
OTableWindow* pRight = nullptr;
// Special treatment: If there are only two tables, we need to switch the other one too when changing in a LB
if ( m_pTableMap->size() == 2 )
{
weld::ComboBox* pOther;
if (&rListBox == m_xLeftTable.get())
pOther = m_xRightTable.get();
else
pOther = m_xLeftTable.get();
pOther->set_active(1 - pOther->get_active());
OJoinTableView::OTableWindowMap::const_iterator aIter = m_pTableMap->begin();
OTableWindow* pFirst = aIter->second;
++aIter;
OTableWindow* pSecond = aIter->second;
if (m_xLeftTable->get_active_text() == pFirst->GetName())
{
pLeft = pFirst;
pRight = pSecond;
}
else
{
pLeft = pSecond;
pRight = pFirst;
}
}
else
{
// First we need the TableDef to the Table and with it the TabWin
OJoinTableView::OTableWindowMap::const_iterator aFind = m_pTableMap->find(strSelected);
OTableWindow* pLoop = nullptr;
if( aFind != m_pTableMap->end() )
pLoop = aFind->second;
OSL_ENSURE(pLoop != nullptr, "ORelationDialog::OnTableChanged: invalid ListBox entry!");
// We need to find strSelect, because we filled the ListBoxes with the table names with which we compare now
if (&rListBox == m_xLeftTable.get())
{
// Insert the previously selected Entry on the left side on the right side
m_xRightTable->append_text(m_strCurrentLeft);
// Remove the currently selected Entry
m_xRightTable->remove_text(strSelected);
m_strCurrentLeft = strSelected;
pLeft = pLoop;
OJoinTableView::OTableWindowMap::const_iterator aIter = m_pTableMap->find(m_xRightTable->get_active_text());
OSL_ENSURE( aIter != m_pTableMap->end(), "Invalid name");
if ( aIter != m_pTableMap->end() )
pRight = aIter->second;
m_xLeftTable->grab_focus();
}
else
{
// Insert the previously selected Entry on the right side on the left side
m_xLeftTable->append_text(m_strCurrentRight);
// Remove the currently selected Entry
m_xLeftTable->remove_text(strSelected);
m_strCurrentRight = strSelected;
pRight = pLoop;
OJoinTableView::OTableWindowMap::const_iterator aIter = m_pTableMap->find(m_xLeftTable->get_active_text());
OSL_ENSURE( aIter != m_pTableMap->end(), "Invalid name");
if ( aIter != m_pTableMap->end() )
pLeft = aIter->second;
}
}
rListBox.grab_focus();
m_xRC_Tables->setWindowTables(pLeft,pRight);
NotifyCellChange();
}
void OTableListBoxControl::NotifyCellChange()
{
// Enable/disable the OK button, depending on having a valid situation
TTableConnectionData::value_type pConnData = m_xRC_Tables->getData();
const OConnectionLineDataVec& rLines = pConnData->GetConnLineDataList();
bool bValid = !rLines.empty();
if (bValid)
{
for (auto const& line : rLines)
{
bValid = ! (line->GetSourceFieldName().isEmpty() || line->GetDestFieldName().isEmpty());
if (!bValid)
break;
}
}
m_pParentDialog->setValid(bValid);
m_xRC_Tables->DeactivateCell();
for (auto const& elem : m_xRC_Tables->m_ops)
{
switch(elem.first)
{
case ORelationControl::DELETE:
m_xRC_Tables->RowRemoved(elem.second.first, elem.second.second - elem.second.first);
break;
case ORelationControl::INSERT:
m_xRC_Tables->RowInserted(elem.second.first, elem.second.second - elem.second.first);
break;
case ORelationControl::MODIFY:
for(OConnectionLineDataVec::size_type j = elem.second.first; j < elem.second.second; ++j)
m_xRC_Tables->RowModified(j);
break;
}
}
m_xRC_Tables->ActivateCell();
m_xRC_Tables->m_ops.clear();
}
static void fillEntryAndDisable(weld::ComboBox& _rListBox,const OUString& _sEntry)
{
_rListBox.append_text(_sEntry);
_rListBox.set_active(0);
_rListBox.set_sensitive(false);
}
void OTableListBoxControl::fillAndDisable(const TTableConnectionData::value_type& _pConnectionData)
{
fillEntryAndDisable(*m_xLeftTable, _pConnectionData->getReferencingTable()->GetWinName());
fillEntryAndDisable(*m_xRightTable, _pConnectionData->getReferencedTable()->GetWinName());
}
void OTableListBoxControl::Init(const TTableConnectionData::value_type& _pConnData)
{
m_xRC_Tables->Init(_pConnData);
}
void OTableListBoxControl::lateUIInit()
{
m_xRC_Tables->Show();
lateInit();
}
void OTableListBoxControl::lateInit()
{
m_xRC_Tables->lateInit();
}
void OTableListBoxControl::Disable()
{
m_xLeftTable->set_sensitive(false);
m_xRightTable->set_sensitive(false);
m_xRC_Tables->Disable();
}
void OTableListBoxControl::Invalidate()
{
m_xRC_Tables->Invalidate();
}
void OTableListBoxControl::SaveModified()
{
m_xRC_Tables->SaveModified();
}
TTableWindowData::value_type const & OTableListBoxControl::getReferencingTable() const
{
return m_xRC_Tables->getData()->getReferencingTable();
}
void OTableListBoxControl::enableRelation(bool _bEnable)
{
if ( !_bEnable )
m_xRC_Tables->PostUserEvent(LINK(m_xRC_Tables, ORelationControl, AsynchDeactivate));
m_xRC_Tables->Enable(_bEnable);
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */