diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /cui/source/dialogs | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
51 files changed, 21015 insertions, 0 deletions
diff --git a/cui/source/dialogs/AdditionsDialog.cxx b/cui/source/dialogs/AdditionsDialog.cxx new file mode 100644 index 000000000..c26a0d974 --- /dev/null +++ b/cui/source/dialogs/AdditionsDialog.cxx @@ -0,0 +1,871 @@ +/* -*- 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/. + * + */ + +#include <sal/config.h> + +#include <algorithm> +#include <cmath> + +#include <config_folders.h> + +#include <AdditionsDialog.hxx> +#include <dialmgr.hxx> +#include <strings.hrc> + +#include <sal/log.hxx> + +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/graphic/XGraphicProvider.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <osl/file.hxx> +#include <rtl/bootstrap.hxx> +#include <tools/urlobj.hxx> +#include <tools/stream.hxx> +#include <tools/diagnose_ex.h> +#include <comphelper/processfactory.hxx> +#include <vcl/virdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/graphicfilter.hxx> +#include <cppuhelper/exc_hlp.hxx> + +#include <com/sun/star/util/SearchFlags.hpp> +#include <com/sun/star/util/SearchAlgorithms2.hpp> +#include <unotools/textsearch.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <ucbhelper/content.hxx> + +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> + +#include <com/sun/star/task/XInteractionApprove.hpp> + +#include <orcus/json_document_tree.hpp> +#include <orcus/json_parser.hpp> +#include <orcus/config.hpp> + +#define PAGE_SIZE 30 + +using namespace css; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::XComponentContext; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::graphic::GraphicProvider; +using ::com::sun::star::graphic::XGraphicProvider; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::beans::PropertyValue; +using ::com::sun::star::graphic::XGraphic; + +using namespace com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::beans; + +namespace +{ +// Gets the content of the given URL and returns as a standard string +std::string ucbGet(const OUString& rURL) +{ + try + { + auto const s = utl::UcbStreamHelper::CreateStream(rURL, StreamMode::STD_READ); + if (!s) + { + SAL_WARN("cui.dialogs", "CreateStream <" << rURL << "> failed"); + return {}; + } + std::string response_body; + do + { + char buf[4096]; + auto const n = s->ReadBytes(buf, sizeof buf); + response_body.append(buf, n); + } while (s->good()); + if (s->bad()) + { + SAL_WARN("cui.dialogs", "Reading <" << rURL << "> failed with " << s->GetError()); + return {}; + } + return response_body; + } + catch (css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", "Download failed"); + return {}; + } +} + +// Downloads and saves the file at the given rURL to a local path (sFolderURL/fileName) +void ucbDownload(const OUString& rURL, const OUString& sFolderURL, const OUString& fileName) +{ + try + { + ucbhelper::Content(sFolderURL, {}, comphelper::getProcessComponentContext()) + .transferContent(ucbhelper::Content(rURL, {}, comphelper::getProcessComponentContext()), + ucbhelper::InsertOperation::Copy, fileName, + css::ucb::NameClash::OVERWRITE); + } + catch (css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", "Download failed"); + } +} + +void parseResponse(const std::string& rResponse, std::vector<AdditionInfo>& aAdditions) +{ + orcus::json::document_tree aJsonDoc; + orcus::json_config aConfig; + + if (rResponse.empty()) + return; + + try + { + aJsonDoc.load(rResponse, aConfig); + } + catch (const orcus::json::parse_error&) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", "Invalid JSON file from the extensions API"); + return; + } + + auto aDocumentRoot = aJsonDoc.get_document_root(); + if (aDocumentRoot.type() != orcus::json::node_t::object) + { + SAL_WARN("cui.dialogs", "invalid root entries: " << rResponse); + return; + } + + auto resultsArray = aDocumentRoot.child("extension"); + + for (size_t i = 0; i < resultsArray.child_count(); ++i) + { + auto arrayElement = resultsArray.child(i); + + try + { + AdditionInfo aNewAddition = { + OStringToOUString(arrayElement.child("id").string_value(), RTL_TEXTENCODING_UTF8), + OStringToOUString(arrayElement.child("name").string_value(), RTL_TEXTENCODING_UTF8), + OStringToOUString(arrayElement.child("author").string_value(), + RTL_TEXTENCODING_UTF8), + OStringToOUString(arrayElement.child("url").string_value(), RTL_TEXTENCODING_UTF8), + OStringToOUString(arrayElement.child("screenshotURL").string_value(), + RTL_TEXTENCODING_UTF8), + OStringToOUString(arrayElement.child("extensionIntroduction").string_value(), + RTL_TEXTENCODING_UTF8), + OStringToOUString(arrayElement.child("extensionDescription").string_value(), + RTL_TEXTENCODING_UTF8), + OStringToOUString( + arrayElement.child("releases").child(0).child("compatibility").string_value(), + RTL_TEXTENCODING_UTF8), + OStringToOUString( + arrayElement.child("releases").child(0).child("releaseName").string_value(), + RTL_TEXTENCODING_UTF8), + OStringToOUString( + arrayElement.child("releases").child(0).child("license").string_value(), + RTL_TEXTENCODING_UTF8), + OStringToOUString(arrayElement.child("commentNumber").string_value(), + RTL_TEXTENCODING_UTF8), + OStringToOUString(arrayElement.child("commentURL").string_value(), + RTL_TEXTENCODING_UTF8), + OStringToOUString(arrayElement.child("rating").string_value(), + RTL_TEXTENCODING_UTF8), + OStringToOUString(arrayElement.child("downloadNumber").string_value(), + RTL_TEXTENCODING_UTF8), + OStringToOUString( + arrayElement.child("releases").child(0).child("downloadURL").string_value(), + RTL_TEXTENCODING_UTF8) + }; + + aAdditions.push_back(aNewAddition); + } + catch (orcus::json::document_error& e) + { + // This usually happens when one of the values is null (type() == orcus::json::node_t::null) + // TODO: Allow null values in additions. + SAL_WARN("cui.dialogs", "Additions JSON parse error: " << e.what()); + } + } +} + +bool getPreviewFile(const AdditionInfo& aAdditionInfo, OUString& sPreviewFile) +{ + uno::Reference<ucb::XSimpleFileAccess3> xFileAccess + = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext()); + + // copy the images to the user's additions folder + OUString userFolder = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER + "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}"; + rtl::Bootstrap::expandMacros(userFolder); + userFolder += "/user/additions/" + aAdditionInfo.sExtensionID + "/"; + + OUString aPreviewFile(INetURLObject(aAdditionInfo.sScreenshotURL).getName()); + OUString aPreviewURL = aAdditionInfo.sScreenshotURL; + + try + { + osl::Directory::createPath(userFolder); + + if (!xFileAccess->exists(userFolder + aPreviewFile)) + ucbDownload(aPreviewURL, userFolder, aPreviewFile); + } + catch (const uno::Exception&) + { + return false; + } + sPreviewFile = userFolder + aPreviewFile; + return true; +} + +void LoadImage(std::u16string_view rPreviewFile, std::shared_ptr<AdditionsItem> pCurrentItem) +{ + const sal_Int8 Margin = 6; + + SolarMutexGuard aGuard; + + GraphicFilter aFilter; + Graphic aGraphic; + + INetURLObject aURLObj(rPreviewFile); + + // for VCL to be able to create bitmaps / do visual changes in the thread + aFilter.ImportGraphic(aGraphic, aURLObj); + BitmapEx aBmp = aGraphic.GetBitmapEx(); + Size aBmpSize = aBmp.GetSizePixel(); + Size aThumbSize(pCurrentItem->m_xImageScreenshot->get_size_request()); + if (!aBmp.IsEmpty()) + { + double aScale; + if (aBmpSize.Width() > aThumbSize.Width() - 2 * Margin) + { + aScale = static_cast<double>(aBmpSize.Width()) / (aThumbSize.Width() - 2 * Margin); + aBmp.Scale(Size(aBmpSize.Width() / aScale, aBmpSize.Height() / aScale)); + } + else if (aBmpSize.Height() > aThumbSize.Height() - 2 * Margin) + { + aScale = static_cast<double>(aBmpSize.Height()) / (aThumbSize.Height() - 2 * Margin); + aBmp.Scale(Size(aBmpSize.Width() / aScale, aBmpSize.Height() / aScale)); + }; + aBmpSize = aBmp.GetSizePixel(); + } + + ScopedVclPtr<VirtualDevice> xVirDev = pCurrentItem->m_xImageScreenshot->create_virtual_device(); + xVirDev->SetOutputSizePixel(aThumbSize); + //white background since images come with a white border + xVirDev->SetBackground(Wallpaper(COL_WHITE)); + xVirDev->Erase(); + xVirDev->DrawBitmapEx(Point(aThumbSize.Width() / 2 - aBmpSize.Width() / 2, Margin), aBmp); + pCurrentItem->m_xImageScreenshot->set_image(xVirDev.get()); + xVirDev.disposeAndClear(); +} + +} // End of the anonymous namespace + +SearchAndParseThread::SearchAndParseThread(AdditionsDialog* pDialog, const bool isFirstLoading) + : Thread("cuiAdditionsSearchThread") + , m_pAdditionsDialog(pDialog) + , m_bExecute(true) + , m_bIsFirstLoading(isFirstLoading) +{ +} + +SearchAndParseThread::~SearchAndParseThread() {} + +void SearchAndParseThread::Append(AdditionInfo& additionInfo) +{ + if (!m_bExecute) + return; + OUString aPreviewFile; + bool bResult = getPreviewFile(additionInfo, aPreviewFile); // info vector json data + + if (!bResult) + { + SAL_INFO("cui.dialogs", "Couldn't get the preview file. Skipping: " << aPreviewFile); + return; + } + + SolarMutexGuard aGuard; + + auto newItem = std::make_shared<AdditionsItem>(m_pAdditionsDialog->m_xContentGrid.get(), + m_pAdditionsDialog, additionInfo); + m_pAdditionsDialog->m_aAdditionsItems.push_back(newItem); + std::shared_ptr<AdditionsItem> aCurrentItem = m_pAdditionsDialog->m_aAdditionsItems.back(); + + LoadImage(aPreviewFile, aCurrentItem); + m_pAdditionsDialog->m_nCurrentListItemCount++; + + if (m_pAdditionsDialog->m_nCurrentListItemCount == m_pAdditionsDialog->m_nMaxItemCount) + { + if (m_pAdditionsDialog->m_nCurrentListItemCount + != m_pAdditionsDialog->m_aAllExtensionsVector.size()) + aCurrentItem->m_xButtonShowMore->set_visible(true); + } +} + +void SearchAndParseThread::Search() +{ + m_pAdditionsDialog->m_searchOptions.searchString + = m_pAdditionsDialog->m_xEntrySearch->get_text(); + utl::TextSearch textSearch(m_pAdditionsDialog->m_searchOptions); + + size_t nIteration = 0; + for (auto& rInfo : m_pAdditionsDialog->m_aAllExtensionsVector) + { + if (m_pAdditionsDialog->m_nCurrentListItemCount == m_pAdditionsDialog->m_nMaxItemCount) + break; + + OUString sExtensionName = rInfo.sName; + OUString sExtensionDescription = rInfo.sDescription; + + if (!m_pAdditionsDialog->m_xEntrySearch->get_text().isEmpty() + && !textSearch.searchForward(sExtensionName) + && !textSearch.searchForward(sExtensionDescription)) + { + continue; + } + else + { + if (nIteration >= m_pAdditionsDialog->m_nCurrentListItemCount) + Append(rInfo); + nIteration++; + } + } + CheckInstalledExtensions(); +} + +void SearchAndParseThread::CheckInstalledExtensions() +{ + const uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>> xAllPackages + = m_pAdditionsDialog->getInstalledExtensions(); + + if (!xAllPackages.hasElements()) + return; + + OUString currentExtensionName; + + for (auto& package : xAllPackages) + { + for (auto& extensionVersion : package) + { + if (extensionVersion.is()) + { + currentExtensionName = extensionVersion->getName(); + if (currentExtensionName.isEmpty()) + continue; + + m_pAdditionsDialog->m_searchOptions.searchString = currentExtensionName; + utl::TextSearch textSearch(m_pAdditionsDialog->m_searchOptions); + + for (auto& rInfo : m_pAdditionsDialog->m_aAdditionsItems) + { + OUString sExtensionDownloadURL = rInfo->m_sDownloadURL; + + if (!textSearch.searchForward(sExtensionDownloadURL)) + { + continue; + } + else + { + SolarMutexGuard aGuard; + rInfo->m_xButtonInstall->set_sensitive(false); + rInfo->m_xButtonInstall->set_label( + CuiResId(RID_CUISTR_ADDITIONS_INSTALLEDBUTTON)); + } + } + } + } + } +} + +void SearchAndParseThread::execute() +{ + OUString sProgress; + if (m_bIsFirstLoading) + sProgress = CuiResId(RID_CUISTR_ADDITIONS_LOADING); + else + sProgress = CuiResId(RID_CUISTR_ADDITIONS_SEARCHING); + + m_pAdditionsDialog->SetProgress( + sProgress); // Loading or searching according to being first call or not + + if (m_bIsFirstLoading) + { + std::string sResponse = ucbGet(m_pAdditionsDialog->m_sURL); + parseResponse(sResponse, m_pAdditionsDialog->m_aAllExtensionsVector); + std::sort(m_pAdditionsDialog->m_aAllExtensionsVector.begin(), + m_pAdditionsDialog->m_aAllExtensionsVector.end(), + AdditionsDialog::sortByDownload); + Search(); + } + else // Searching + { + Search(); + } + + if (!m_bExecute) + return; + + SolarMutexGuard aGuard; + sProgress.clear(); + m_pAdditionsDialog->SetProgress(sProgress); +} + +AdditionsDialog::AdditionsDialog(weld::Window* pParent, const OUString& sAdditionsTag) + : GenericDialogController(pParent, "cui/ui/additionsdialog.ui", "AdditionsDialog") + , m_aSearchDataTimer("AdditionsDialog SearchDataTimer") + , m_xEntrySearch(m_xBuilder->weld_entry("entrySearch")) + , m_xButtonClose(m_xBuilder->weld_button("buttonClose")) + , m_xMenuButtonSettings(m_xBuilder->weld_menu_button("buttonGear")) + , m_xContentWindow(m_xBuilder->weld_scrolled_window("contentWindow")) + , m_xContentGrid(m_xBuilder->weld_container("contentGrid")) + , m_xLabelProgress(m_xBuilder->weld_label("labelProgress")) + , m_xGearBtn(m_xBuilder->weld_menu_button("buttonGear")) +{ + m_xGearBtn->connect_selected(LINK(this, AdditionsDialog, GearHdl)); + m_xGearBtn->set_item_active("gear_sort_voting", true); + + m_aSearchDataTimer.SetInvokeHandler(LINK(this, AdditionsDialog, ImplUpdateDataHdl)); + m_aSearchDataTimer.SetTimeout(EDIT_UPDATEDATA_TIMEOUT); + + m_xEntrySearch->connect_changed(LINK(this, AdditionsDialog, SearchUpdateHdl)); + m_xEntrySearch->connect_focus_out(LINK(this, AdditionsDialog, FocusOut_Impl)); + m_xButtonClose->connect_clicked(LINK(this, AdditionsDialog, CloseButtonHdl)); + + m_sTag = sAdditionsTag; + m_nMaxItemCount = PAGE_SIZE; // Dialog initialization item count + m_nCurrentListItemCount = 0; // First, there is no item on the list. + + OUString titlePrefix = CuiResId(RID_CUISTR_ADDITIONS_DIALOG_TITLE_PREFIX); + if (!m_sTag.isEmpty()) + { // tdf#142564 localize extension category names + OUString sDialogTitle = ""; + if (sAdditionsTag == "Templates") + { + sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_TEMPLATES); + } + else if (sAdditionsTag == "Dictionary") + { + sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_DICTIONARY); + } + else if (sAdditionsTag == "Gallery") + { + sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_GALLERY); + } + else if (sAdditionsTag == "Icons") + { + sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_ICONS); + } + else if (sAdditionsTag == "Color Palette") + { + sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_PALETTES); + } + this->set_title(sDialogTitle); + } + else + { + this->set_title(titlePrefix); + m_sTag = "allextensions"; // Means empty parameter + } + + OUString sEncodedURLPart = INetURLObject::encode(m_sTag, INetURLObject::PART_PCHAR, + INetURLObject::EncodeMechanism::All); + + //FIXME: Temporary URL - v0 is not using actual api + OUString rURL = "https://extensions.libreoffice.org/api/v0/" + sEncodedURLPart + ".json"; + m_sURL = rURL; + + m_xExtensionManager + = deployment::ExtensionManager::get(::comphelper::getProcessComponentContext()); + + //Initialize search util + m_searchOptions.AlgorithmType2 = css::util::SearchAlgorithms2::ABSOLUTE; + m_searchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE; + m_searchOptions.searchFlag |= (css::util::SearchFlags::REG_NOT_BEGINOFLINE + | css::util::SearchFlags::REG_NOT_ENDOFLINE); + m_pSearchThread = new SearchAndParseThread(this, true); + m_pSearchThread->launch(); +} + +AdditionsDialog::~AdditionsDialog() +{ + if (m_pSearchThread.is()) + { + m_pSearchThread->StopExecution(); + // Release the solar mutex, so the thread is not affected by the race + // when it's after the m_bExecute check but before taking the solar + // mutex. + SolarMutexReleaser aReleaser; + m_pSearchThread->join(); + } +} + +uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>> +AdditionsDialog::getInstalledExtensions() +{ + uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>> xAllPackages; + + try + { + xAllPackages = m_xExtensionManager->getAllExtensions( + uno::Reference<task::XAbortChannel>(), uno::Reference<ucb::XCommandEnvironment>()); + } + catch (const deployment::DeploymentException&) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", ""); + } + catch (const ucb::CommandFailedException&) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", ""); + } + catch (const ucb::CommandAbortedException&) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", ""); + } + catch (const lang::IllegalArgumentException& e) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException(e.Message, e.Context, anyEx); + } + return xAllPackages; +} + +void AdditionsDialog::SetProgress(const OUString& rProgress) +{ + if (rProgress.isEmpty()) + { + m_xLabelProgress->hide(); + m_xButtonClose->set_sensitive(true); + } + else + { + SolarMutexGuard aGuard; + m_xLabelProgress->show(); + m_xLabelProgress->set_label(rProgress); + m_xDialog->resize_to_request(); //TODO + } +} + +void AdditionsDialog::ClearList() +{ + // for VCL to be able to destroy bitmaps + SolarMutexGuard aGuard; + + for (auto& item : this->m_aAdditionsItems) + { + item->m_xContainer->hide(); + } + this->m_aAdditionsItems.clear(); +} + +void AdditionsDialog::RefreshUI() +{ + if (m_pSearchThread.is()) + m_pSearchThread->StopExecution(); + ClearList(); + m_nCurrentListItemCount = 0; + m_nMaxItemCount = PAGE_SIZE; + m_pSearchThread = new SearchAndParseThread(this, false); + m_pSearchThread->launch(); +} + +bool AdditionsDialog::sortByRating(const AdditionInfo& a, const AdditionInfo& b) +{ + return a.sRating.toDouble() > b.sRating.toDouble(); +} + +bool AdditionsDialog::sortByComment(const AdditionInfo& a, const AdditionInfo& b) +{ + return a.sCommentNumber.toUInt32() > b.sCommentNumber.toUInt32(); +} + +bool AdditionsDialog::sortByDownload(const AdditionInfo& a, const AdditionInfo& b) +{ + return a.sDownloadNumber.toUInt32() > b.sDownloadNumber.toUInt32(); +} + +AdditionsItem::AdditionsItem(weld::Widget* pParent, AdditionsDialog* pParentDialog, + const AdditionInfo& additionInfo) + : m_xBuilder(Application::CreateBuilder(pParent, "cui/ui/additionsfragment.ui")) + , m_xContainer(m_xBuilder->weld_widget("additionsEntry")) + , m_xImageScreenshot(m_xBuilder->weld_image("imageScreenshot")) + , m_xButtonInstall(m_xBuilder->weld_button("buttonInstall")) + , m_xLinkButtonWebsite(m_xBuilder->weld_link_button("btnWebsite")) + , m_xLabelName(m_xBuilder->weld_label("lbName")) + , m_xLabelAuthor(m_xBuilder->weld_label("labelAuthor")) + , m_xLabelDesc(m_xBuilder->weld_label("labelDesc")) // no change (print description) + , m_xLabelDescription(m_xBuilder->weld_label("labelDescription")) + , m_xLabelLicense(m_xBuilder->weld_label("lbLicenseText")) + , m_xLabelVersion(m_xBuilder->weld_label("lbVersionText")) + , m_xLabelComments(m_xBuilder->weld_label("labelComments")) // no change + , m_xLinkButtonComments(m_xBuilder->weld_link_button("linkButtonComments")) + , m_xImageVoting1(m_xBuilder->weld_image("imageVoting1")) + , m_xImageVoting2(m_xBuilder->weld_image("imageVoting2")) + , m_xImageVoting3(m_xBuilder->weld_image("imageVoting3")) + , m_xImageVoting4(m_xBuilder->weld_image("imageVoting4")) + , m_xImageVoting5(m_xBuilder->weld_image("imageVoting5")) + , m_xLabelNoVoting(m_xBuilder->weld_label("votingLabel")) + , m_xImageDownloadNumber(m_xBuilder->weld_image("imageDownloadNumber")) + , m_xLabelDownloadNumber(m_xBuilder->weld_label("labelDownloadNumber")) + , m_xButtonShowMore(m_xBuilder->weld_button("buttonShowMore")) + , m_pParentDialog(pParentDialog) + , m_sDownloadURL("") + , m_sExtensionID("") +{ + SolarMutexGuard aGuard; + + // AdditionsItem set location + m_xContainer->set_grid_left_attach(0); + m_xContainer->set_grid_top_attach(pParentDialog->m_aAdditionsItems.size()); + + // Set maximum length of the extension title + OUString sExtensionName; + const sal_Int32 maxExtensionNameLength = 30; + + if (additionInfo.sName.getLength() > maxExtensionNameLength) + { + std::u16string_view sShortName = additionInfo.sName.subView(0, maxExtensionNameLength - 3); + sExtensionName = OUString::Concat(sShortName) + "..."; + } + else + { + sExtensionName = additionInfo.sName; + } + + m_xLabelName->set_label(sExtensionName); + + double aExtensionRating = additionInfo.sRating.toDouble(); + switch (std::isnan(aExtensionRating) ? 0 : int(std::clamp(aExtensionRating, 0.0, 5.0))) + { + case 5: + m_xImageVoting5->set_from_icon_name("cmd/sc_stars-full.png"); + [[fallthrough]]; + case 4: + m_xImageVoting4->set_from_icon_name("cmd/sc_stars-full.png"); + [[fallthrough]]; + case 3: + m_xImageVoting3->set_from_icon_name("cmd/sc_stars-full.png"); + [[fallthrough]]; + case 2: + m_xImageVoting2->set_from_icon_name("cmd/sc_stars-full.png"); + [[fallthrough]]; + case 1: + m_xImageVoting1->set_from_icon_name("cmd/sc_stars-full.png"); + break; + } + + m_xLinkButtonWebsite->set_uri(additionInfo.sExtensionURL); + m_xLabelDescription->set_label(additionInfo.sIntroduction); + + if (!additionInfo.sAuthorName.equalsIgnoreAsciiCase("null")) + m_xLabelAuthor->set_label(additionInfo.sAuthorName); + + m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON)); + m_xLabelLicense->set_label(additionInfo.sLicense); + m_xLabelVersion->set_label(">=" + additionInfo.sCompatibleVersion); + m_xLinkButtonComments->set_label(additionInfo.sCommentNumber); + m_xLinkButtonComments->set_uri(additionInfo.sCommentURL); + m_xLabelDownloadNumber->set_label(additionInfo.sDownloadNumber); + m_pParentDialog = pParentDialog; + m_sDownloadURL = additionInfo.sDownloadURL; + m_sExtensionID = additionInfo.sExtensionID; + + m_xButtonShowMore->connect_clicked(LINK(this, AdditionsItem, ShowMoreHdl)); + m_xButtonInstall->connect_clicked(LINK(this, AdditionsItem, InstallHdl)); +} + +bool AdditionsItem::getExtensionFile(OUString& sExtensionFile) +{ + uno::Reference<ucb::XSimpleFileAccess3> xFileAccess + = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext()); + + // copy the extensions' files to the user's additions folder + OUString userFolder = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER + "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}"; + rtl::Bootstrap::expandMacros(userFolder); + userFolder += "/user/additions/" + m_sExtensionID + "/"; + + OUString aExtensionsFile(INetURLObject(m_sDownloadURL).getName()); + OUString aExtensionsURL = m_sDownloadURL; + + try + { + osl::Directory::createPath(userFolder); + + if (!xFileAccess->exists(userFolder + aExtensionsFile)) + ucbDownload(aExtensionsURL, userFolder, aExtensionsFile); + } + catch (const uno::Exception&) + { + return false; + } + sExtensionFile = userFolder + aExtensionsFile; + return true; +} + +IMPL_LINK_NOARG(AdditionsDialog, ImplUpdateDataHdl, Timer*, void) { RefreshUI(); } + +IMPL_LINK_NOARG(AdditionsDialog, SearchUpdateHdl, weld::Entry&, void) +{ + m_aSearchDataTimer.Start(); +} + +IMPL_LINK_NOARG(AdditionsDialog, FocusOut_Impl, weld::Widget&, void) +{ + if (m_aSearchDataTimer.IsActive()) + { + m_aSearchDataTimer.Stop(); + m_aSearchDataTimer.Invoke(); + } +} + +IMPL_LINK_NOARG(AdditionsDialog, CloseButtonHdl, weld::Button&, void) +{ + if (m_pSearchThread.is()) + m_pSearchThread->StopExecution(); + this->response(RET_CLOSE); +} + +IMPL_LINK_NOARG(AdditionsItem, ShowMoreHdl, weld::Button&, void) +{ + this->m_xButtonShowMore->set_visible(false); + m_pParentDialog->m_nMaxItemCount += PAGE_SIZE; + if (m_pParentDialog->m_pSearchThread.is()) + m_pParentDialog->m_pSearchThread->StopExecution(); + m_pParentDialog->m_pSearchThread = new SearchAndParseThread(m_pParentDialog, false); + m_pParentDialog->m_pSearchThread->launch(); +} + +IMPL_LINK_NOARG(AdditionsItem, InstallHdl, weld::Button&, void) +{ + m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLING)); + m_xButtonInstall->set_sensitive(false); + OUString aExtensionFile; + bool bResult = getExtensionFile(aExtensionFile); // info vector json data + + if (!bResult) + { + m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON)); + m_xButtonInstall->set_sensitive(true); + + SAL_INFO("cui.dialogs", "Couldn't get the extension file."); + return; + } + + rtl::Reference<TmpRepositoryCommandEnv> pCmdEnv = new TmpRepositoryCommandEnv(); + uno::Reference<task::XAbortChannel> xAbortChannel; + try + { + m_pParentDialog->m_xExtensionManager->addExtension( + aExtensionFile, uno::Sequence<beans::NamedValue>(), "user", xAbortChannel, pCmdEnv); + m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLEDBUTTON)); + } + catch (const ucb::CommandFailedException) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", ""); + m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON)); + m_xButtonInstall->set_sensitive(true); + } + catch (const ucb::CommandAbortedException) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", ""); + m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON)); + m_xButtonInstall->set_sensitive(true); + } + catch (const deployment::DeploymentException) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", ""); + m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON)); + m_xButtonInstall->set_sensitive(true); + } + catch (const lang::IllegalArgumentException) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", ""); + m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON)); + m_xButtonInstall->set_sensitive(true); + } + catch (const css::uno::Exception) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", ""); + m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON)); + m_xButtonInstall->set_sensitive(true); + } +} + +// TmpRepositoryCommandEnv + +TmpRepositoryCommandEnv::TmpRepositoryCommandEnv() {} + +TmpRepositoryCommandEnv::~TmpRepositoryCommandEnv() {} +// XCommandEnvironment + +uno::Reference<task::XInteractionHandler> TmpRepositoryCommandEnv::getInteractionHandler() +{ + return this; +} + +uno::Reference<ucb::XProgressHandler> TmpRepositoryCommandEnv::getProgressHandler() { return this; } + +// XInteractionHandler +void TmpRepositoryCommandEnv::handle(uno::Reference<task::XInteractionRequest> const& xRequest) +{ + OSL_ASSERT(xRequest->getRequest().getValueTypeClass() == uno::TypeClass_EXCEPTION); + + bool approve = true; + + // select: + uno::Sequence<Reference<task::XInteractionContinuation>> conts(xRequest->getContinuations()); + Reference<task::XInteractionContinuation> const* pConts = conts.getConstArray(); + sal_Int32 len = conts.getLength(); + for (sal_Int32 pos = 0; pos < len; ++pos) + { + if (approve) + { + uno::Reference<task::XInteractionApprove> xInteractionApprove(pConts[pos], + uno::UNO_QUERY); + if (xInteractionApprove.is()) + { + xInteractionApprove->select(); + // don't query again for ongoing continuations: + approve = false; + } + } + } +} + +// XProgressHandler +void TmpRepositoryCommandEnv::push(uno::Any const& /*Status*/) {} + +void TmpRepositoryCommandEnv::update(uno::Any const& /*Status */) {} + +void TmpRepositoryCommandEnv::pop() {} + +IMPL_LINK(AdditionsDialog, GearHdl, const OString&, rIdent, void) +{ + if (rIdent == "gear_sort_voting") + { + std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByRating); + } + else if (rIdent == "gear_sort_comments") + { + std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByComment); + } + else if (rIdent == "gear_sort_downloads") + { + std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByDownload); + } + // After the sorting, UI will be refreshed to update extension list. + RefreshUI(); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/DiagramDialog.cxx b/cui/source/dialogs/DiagramDialog.cxx new file mode 100644 index 000000000..c53bafe94 --- /dev/null +++ b/cui/source/dialogs/DiagramDialog.cxx @@ -0,0 +1,156 @@ +/* -*- 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/. +*/ + +#include <DiagramDialog.hxx> + +#include <comphelper/dispatchcommand.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdundo.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <svx/diagram/datamodel.hxx> +#include <svx/diagram/IDiagramHelper.hxx> + +DiagramDialog::DiagramDialog(weld::Window* pWindow, SdrObjGroup& rDiagram) + : GenericDialogController(pWindow, "cui/ui/diagramdialog.ui", "DiagramDialog") + , m_rDiagram(rDiagram) + , m_nUndos(0) + , mpBtnOk(m_xBuilder->weld_button("btnOk")) + , mpBtnCancel(m_xBuilder->weld_button("btnCancel")) + , mpBtnAdd(m_xBuilder->weld_button("btnAdd")) + , mpBtnRemove(m_xBuilder->weld_button("btnRemove")) + , mpTreeDiagram(m_xBuilder->weld_tree_view("treeDiagram")) + , mpTextAdd(m_xBuilder->weld_text_view("textAdd")) +{ + mpBtnCancel->connect_clicked(LINK(this, DiagramDialog, OnAddCancel)); + mpBtnAdd->connect_clicked(LINK(this, DiagramDialog, OnAddClick)); + mpBtnRemove->connect_clicked(LINK(this, DiagramDialog, OnRemoveClick)); + + populateTree(nullptr, OUString()); + + // expand all items + weld::TreeView* pTreeDiagram = mpTreeDiagram.get(); + pTreeDiagram->all_foreach([pTreeDiagram](weld::TreeIter& rEntry) { + pTreeDiagram->expand_row(rEntry); + return false; + }); +} + +IMPL_LINK_NOARG(DiagramDialog, OnAddCancel, weld::Button&, void) +{ + // If the user cancels the dialog, undo all changes done so far. It may + // even be feasible to then delete the redo-stack, since it stays + // available (?) - but it does no harm either... + while (0 != m_nUndos) + { + comphelper::dispatchCommand(".uno:Undo", {}); + m_nUndos--; + } + + m_xDialog->response(RET_CANCEL); +} + +IMPL_LINK_NOARG(DiagramDialog, OnAddClick, weld::Button&, void) +{ + if (!m_rDiagram.isDiagram()) + return; + + OUString sText = mpTextAdd->get_text(); + const std::shared_ptr< svx::diagram::IDiagramHelper >& pDiagramHelper(m_rDiagram.getDiagramHelper()); + + if (pDiagramHelper && !sText.isEmpty()) + { + SdrModel& rDrawModel(m_rDiagram.getSdrModelFromSdrObject()); + const bool bUndo(rDrawModel.IsUndoEnabled()); + svx::diagram::DiagramDataStatePtr aStartState; + + if (bUndo) + { + // rescue all start state Diagram-defining data + aStartState = pDiagramHelper->extractDiagramDataState(); + } + + OUString sNodeId = pDiagramHelper->addNode(sText); + + if (bUndo) + { + // create undo action. That will internally secure the + // current Diagram-defining data as end state + rDrawModel.AddUndo( + rDrawModel.GetSdrUndoFactory().CreateUndoDiagramModelData(m_rDiagram, aStartState)); + m_nUndos++; + } + + std::unique_ptr<weld::TreeIter> pEntry(mpTreeDiagram->make_iterator()); + mpTreeDiagram->insert(nullptr, -1, &sText, &sNodeId, nullptr, nullptr, false, pEntry.get()); + mpTreeDiagram->select(*pEntry); + comphelper::dispatchCommand(".uno:RegenerateDiagram", {}); + } +} + +IMPL_LINK_NOARG(DiagramDialog, OnRemoveClick, weld::Button&, void) +{ + if (!m_rDiagram.isDiagram()) + return; + + std::unique_ptr<weld::TreeIter> pEntry(mpTreeDiagram->make_iterator()); + const std::shared_ptr< svx::diagram::IDiagramHelper >& pDiagramHelper(m_rDiagram.getDiagramHelper()); + + if (pDiagramHelper && mpTreeDiagram->get_selected(pEntry.get())) + { + SdrModel& rDrawModel(m_rDiagram.getSdrModelFromSdrObject()); + const bool bUndo(rDrawModel.IsUndoEnabled()); + svx::diagram::DiagramDataStatePtr aStartState; + + if (bUndo) + { + // rescue all start state Diagram-defining data + aStartState = pDiagramHelper->extractDiagramDataState(); + } + + if (pDiagramHelper->removeNode(mpTreeDiagram->get_id(*pEntry))) + { + if (bUndo) + { + // create undo action. That will internally secure the + // current Diagram-defining data as end state + rDrawModel.AddUndo(rDrawModel.GetSdrUndoFactory().CreateUndoDiagramModelData( + m_rDiagram, aStartState)); + m_nUndos++; + } + + mpTreeDiagram->remove(*pEntry); + comphelper::dispatchCommand(".uno:RegenerateDiagram", {}); + } + } +} + +void DiagramDialog::populateTree(const weld::TreeIter* pParent, const OUString& rParentId) +{ + if (!m_rDiagram.isDiagram()) + return; + + const std::shared_ptr< svx::diagram::IDiagramHelper >& pDiagramHelper(m_rDiagram.getDiagramHelper()); + + if (!pDiagramHelper) + return; + + auto aItems = pDiagramHelper->getChildren(rParentId); + for (auto& aItem : aItems) + { + std::unique_ptr<weld::TreeIter> pEntry(mpTreeDiagram->make_iterator()); + mpTreeDiagram->insert(pParent, -1, &aItem.second, &aItem.first, nullptr, nullptr, false, + pEntry.get()); + populateTree(pEntry.get(), aItem.first); + } +} + +DiagramDialog::~DiagramDialog() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/FontFeaturesDialog.cxx b/cui/source/dialogs/FontFeaturesDialog.cxx new file mode 100644 index 000000000..a129191b3 --- /dev/null +++ b/cui/source/dialogs/FontFeaturesDialog.cxx @@ -0,0 +1,233 @@ +/* -*- 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/. + * + */ + +#include <FontFeaturesDialog.hxx> +#include <rtl/ustrbuf.hxx> +#include <utility> +#include <vcl/font/FeatureParser.hxx> +#include <FontFeatures.hxx> +#include <unordered_set> + +using namespace css; + +namespace cui +{ +FontFeaturesDialog::FontFeaturesDialog(weld::Window* pParent, OUString aFontName) + : GenericDialogController(pParent, "cui/ui/fontfeaturesdialog.ui", "FontFeaturesDialog") + , m_sFontName(std::move(aFontName)) + , m_xContentWindow(m_xBuilder->weld_scrolled_window("contentWindow")) + , m_xContentGrid(m_xBuilder->weld_container("contentGrid")) + , m_xPreviewWindow(new weld::CustomWeld(*m_xBuilder, "preview", m_aPreviewWindow)) +{ + initialize(); +} + +FontFeaturesDialog::~FontFeaturesDialog() {} + +static sal_Int32 makeEnumComboBox(weld::ComboBox& rNameBox, + vcl::font::FeatureDefinition const& rFeatureDefinition, + uint32_t nDefault) +{ + sal_Int32 nRes = 0; + int count = 0; + for (vcl::font::FeatureParameter const& rParameter : rFeatureDefinition.getEnumParameters()) + { + rNameBox.append(OUString::number(rParameter.getCode()), rParameter.getDescription()); + if (rParameter.getCode() == nDefault) + nRes = count; + ++count; + } + return nRes; +} + +void FontFeaturesDialog::initialize() +{ + ScopedVclPtrInstance<VirtualDevice> aVDev(*Application::GetDefaultDevice(), + DeviceFormat::DEFAULT, DeviceFormat::DEFAULT); + std::vector<vcl::font::Feature> rFontFeatures = getFontFeatureList(m_sFontName, *aVDev); + + std::unordered_set<sal_uInt32> aDoneFeatures; + std::vector<vcl::font::Feature> rFilteredFontFeatures; + + for (vcl::font::Feature const& rFontFeature : rFontFeatures) + { + sal_uInt32 nFontFeatureCode = rFontFeature.m_aID.m_aFeatureCode; + if (!aDoneFeatures.insert(nFontFeatureCode).second) + continue; + rFilteredFontFeatures.push_back(rFontFeature); + } + + int nRowHeight = fillGrid(rFilteredFontFeatures); + + m_xContentWindow->set_size_request( + -1, std::min(std::max(m_xContentWindow->get_preferred_size().Height(), + m_xContentGrid->get_preferred_size().Height()), + static_cast<tools::Long>(300L))); + + if (nRowHeight) + { + // tdf#141333 use row height + the 6 px spacing of contentGrid + m_xContentWindow->vadjustment_set_step_increment(nRowHeight + 6); + } + + updateFontPreview(); +} + +int FontFeaturesDialog::fillGrid(std::vector<vcl::font::Feature> const& rFontFeatures) +{ + int nRowHeight(0); + + vcl::font::FeatureParser aParser(m_sFontName); + auto aExistingFeatures = aParser.getFeaturesMap(); + + sal_Int32 i = 0; + for (vcl::font::Feature const& rFontFeature : rFontFeatures) + { + sal_uInt32 nFontFeatureCode = rFontFeature.m_aID.m_aFeatureCode; + + vcl::font::FeatureDefinition aDefinition; + if (rFontFeature.m_aDefinition) + aDefinition = rFontFeature.m_aDefinition; + if (!aDefinition) + aDefinition = { nFontFeatureCode, "" }; + + m_aFeatureItems.emplace_back(m_xContentGrid.get()); + + uint32_t nValue = 0; + if (aExistingFeatures.find(nFontFeatureCode) != aExistingFeatures.end()) + nValue = aExistingFeatures.at(nFontFeatureCode); + else + nValue = aDefinition.getDefault(); + + FontFeatureItem& aCurrentItem = m_aFeatureItems.back(); + aCurrentItem.m_aFeatureCode = nFontFeatureCode; + aCurrentItem.m_nDefault = aDefinition.getDefault(); + + sal_Int32 nGridPositionX = (i % 2) * 2; + sal_Int32 nGridPositionY = i / 2; + aCurrentItem.m_xContainer->set_grid_left_attach(nGridPositionX); + aCurrentItem.m_xContainer->set_grid_top_attach(nGridPositionY); + + Link<weld::ComboBox&, void> aComboBoxSelectHandler + = LINK(this, FontFeaturesDialog, ComboBoxSelectedHdl); + Link<weld::Toggleable&, void> aCheckBoxToggleHandler + = LINK(this, FontFeaturesDialog, CheckBoxToggledHdl); + + if (aDefinition.getType() == vcl::font::FeatureParameterType::ENUM) + { + aCurrentItem.m_xText->set_label(aDefinition.getDescription()); + aCurrentItem.m_xText->show(); + + sal_Int32 nInit = makeEnumComboBox(*aCurrentItem.m_xCombo, aDefinition, nValue); + + aCurrentItem.m_xCombo->set_active(nInit); + aCurrentItem.m_xCombo->connect_changed(aComboBoxSelectHandler); + aCurrentItem.m_xCombo->show(); + } + else + { + aCurrentItem.m_xCheck->set_active(nValue > 0); + aCurrentItem.m_xCheck->set_label(aDefinition.getDescription()); + aCurrentItem.m_xCheck->connect_toggled(aCheckBoxToggleHandler); + aCurrentItem.m_xCheck->show(); + } + + nRowHeight + = std::max<int>(nRowHeight, aCurrentItem.m_xContainer->get_preferred_size().Height()); + + i++; + } + + return nRowHeight; +} + +void FontFeaturesDialog::updateFontPreview() +{ + vcl::Font rPreviewFont = m_aPreviewWindow.GetFont(); + vcl::Font rPreviewFontCJK = m_aPreviewWindow.GetCJKFont(); + vcl::Font rPreviewFontCTL = m_aPreviewWindow.GetCTLFont(); + + OUString sNewFontName = createFontNameWithFeatures(); + + rPreviewFont.SetFamilyName(sNewFontName); + rPreviewFontCJK.SetFamilyName(sNewFontName); + rPreviewFontCTL.SetFamilyName(sNewFontName); + + m_aPreviewWindow.SetFont(rPreviewFont, rPreviewFontCJK, rPreviewFontCTL); +} + +IMPL_LINK_NOARG(FontFeaturesDialog, CheckBoxToggledHdl, weld::Toggleable&, void) +{ + updateFontPreview(); +} + +IMPL_LINK_NOARG(FontFeaturesDialog, ComboBoxSelectedHdl, weld::ComboBox&, void) +{ + updateFontPreview(); +} + +OUString FontFeaturesDialog::createFontNameWithFeatures() +{ + OUString sResultFontName; + OUStringBuffer sNameSuffix; + bool bFirst = true; + + for (const FontFeatureItem& rItem : m_aFeatureItems) + { + if (rItem.m_xCheck->get_visible()) + { + if (sal_uInt32(rItem.m_xCheck->get_active()) != rItem.m_nDefault) + { + if (!bFirst) + sNameSuffix.append(vcl::font::FeatureSeparator); + else + bFirst = false; + + sNameSuffix.append(vcl::font::featureCodeAsString(rItem.m_aFeatureCode)); + if (!rItem.m_xCheck->get_active()) + sNameSuffix.append("=0"); + } + } + else if (rItem.m_xCombo->get_visible() && rItem.m_xText->get_visible()) + { + sal_Int32 nSelection = rItem.m_xCombo->get_active_id().toInt32(); + if (nSelection != int(rItem.m_nDefault)) + { + if (!bFirst) + sNameSuffix.append(vcl::font::FeatureSeparator); + else + bFirst = false; + + sNameSuffix.append(vcl::font::featureCodeAsString(rItem.m_aFeatureCode)); + sNameSuffix.append("="); + sNameSuffix.append(OUString::number(nSelection)); + } + } + } + sResultFontName = vcl::font::trimFontNameFeatures(m_sFontName); + if (!sNameSuffix.isEmpty()) + sResultFontName + += OUStringChar(vcl::font::FeaturePrefix) + sNameSuffix.makeStringAndClear(); + return sResultFontName; +} + +short FontFeaturesDialog::run() +{ + short nResult = GenericDialogController::run(); + if (nResult == RET_OK) + { + m_sResultFontName = createFontNameWithFeatures(); + } + return nResult; +} + +} // end svx namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/GraphicTestsDialog.cxx b/cui/source/dialogs/GraphicTestsDialog.cxx new file mode 100644 index 000000000..ad0c25aab --- /dev/null +++ b/cui/source/dialogs/GraphicTestsDialog.cxx @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <comphelper/backupfilehelper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/DirectoryHelper.hxx> +#include <osl/file.hxx> +#include <svx/FileExportedDialog.hxx> +#include <unotools/ZipPackageHelper.hxx> +#include <GraphicsTestsDialog.hxx> +#include <vcl/test/GraphicsRenderTests.hxx> +#include <svl/svlresid.hxx> +#include <svl/svl.hrc> +#include <vcl/svapp.hxx> + +#include <dialmgr.hxx> +#include <strings.hrc> +#include <ImageViewerDialog.hxx> + +GraphicTestEntry::GraphicTestEntry(weld::Container* pParent, weld::Dialog* pDialog, + OUString aTestName, OUString aTestStatus, Bitmap aTestBitmap) + : m_xBuilder(Application::CreateBuilder(pParent, "cui/ui/graphictestentry.ui")) + , m_xContainer(m_xBuilder->weld_container("gptestbox")) + , m_xTestLabel(m_xBuilder->weld_label("gptestlabel")) + , m_xTestButton(m_xBuilder->weld_button("gptestbutton")) + , m_xResultBitmap(aTestBitmap) +{ + m_xParentDialog = pDialog; + m_xTestLabel->set_label(aTestName); + m_xTestButton->set_label(aTestStatus); + m_xTestButton->set_tooltip_text(aTestName); + m_xTestButton->set_background( + aTestStatus == SvlResId(GRTSTR_PASSED) + ? COL_LIGHTGREEN + : aTestStatus == SvlResId(GRTSTR_QUIRKY) + ? COL_YELLOW + : aTestStatus == SvlResId(GRTSTR_FAILED) ? COL_LIGHTRED : COL_LIGHTGRAY); + m_xTestButton->connect_clicked(LINK(this, GraphicTestEntry, HandleResultViewRequest)); + m_xContainer->show(); +} + +IMPL_LINK(GraphicTestEntry, HandleResultViewRequest, weld::Button&, rButton, void) +{ + if (rButton.get_label() == SvlResId(GRTSTR_SKIPPED)) + { + return; + } + ImageViewerDialog m_ImgVwDialog(m_xParentDialog, BitmapEx(m_xResultBitmap), + rButton.get_tooltip_text()); + m_ImgVwDialog.run(); +} + +GraphicsTestsDialog::GraphicsTestsDialog(weld::Container* pParent) + : GenericDialogController(pParent, "cui/ui/graphictestdlg.ui", "GraphicTestsDialog") + , m_xResultLog(m_xBuilder->weld_text_view("gptest_txtVW")) + , m_xDownloadResults(m_xBuilder->weld_button("gptest_downld")) + , m_xContainerBox(m_xBuilder->weld_box("gptest_box")) +{ + OUString userProfile = comphelper::BackupFileHelper::getUserProfileURL(); + m_xZipFileUrl = userProfile + "/GraphicTestResults.zip"; + m_xCreateFolderUrl = userProfile + "/GraphicTestResults"; + osl::Directory::create(m_xCreateFolderUrl); + m_xDownloadResults->connect_clicked(LINK(this, GraphicsTestsDialog, HandleDownloadRequest)); +} + +short GraphicsTestsDialog::run() +{ + GraphicsRenderTests aTestObject; + aTestObject.run(true); + OUString aResultLog + = aTestObject.getResultString(true) + "\n" + CuiResId(RID_CUISTR_CLICK_RESULT); + m_xResultLog->set_text(aResultLog); + sal_Int32 nTestNumber = 0; + for (VclTestResult& test : aTestObject.getTestResults()) + { + auto xGpTest = std::make_unique<GraphicTestEntry>(m_xContainerBox.get(), m_xDialog.get(), + test.getTestName(), test.getStatus(true), + test.getBitmap()); + m_xContainerBox->reorder_child(xGpTest->get_widget(), nTestNumber++); + m_xGraphicTestEntries.push_back(std::move(xGpTest)); + } + return GenericDialogController::run(); +} + +IMPL_LINK_NOARG(GraphicsTestsDialog, HandleDownloadRequest, weld::Button&, void) +{ + osl::File::remove(m_xZipFileUrl); // Remove the previous export + try + { + utl::ZipPackageHelper aZipHelper(comphelper::getProcessComponentContext(), m_xZipFileUrl); + aZipHelper.addFolderWithContent(aZipHelper.getRootFolder(), m_xCreateFolderUrl); + aZipHelper.savePackage(); + } + catch (const std::exception&) + { + std::unique_ptr<weld::MessageDialog> xBox( + Application::CreateMessageDialog(m_xDialog.get(), VclMessageType::Warning, + VclButtonsType::Ok, CuiResId(RID_CUISTR_ZIPFAIL))); + xBox->run(); + return; + } + FileExportedDialog aDialog(m_xDialog.get(), CuiResId(RID_CUISTR_SAVED)); + aDialog.run(); +} + +GraphicsTestsDialog::~GraphicsTestsDialog() +{ + comphelper::DirectoryHelper::deleteDirRecursively(m_xCreateFolderUrl); +} diff --git a/cui/source/dialogs/ImageViewerDialog.cxx b/cui/source/dialogs/ImageViewerDialog.cxx new file mode 100644 index 000000000..b245c8c08 --- /dev/null +++ b/cui/source/dialogs/ImageViewerDialog.cxx @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <vcl/virdev.hxx> +#include <ImageViewerDialog.hxx> + +ImageViewerDialog::ImageViewerDialog(weld::Dialog* pParent, BitmapEx aBitmap, OUString atitle) + : GenericDialogController(pParent, "cui/ui/imageviewer.ui", "ImageViewerDialog") + , m_xDisplayImage(m_xBuilder->weld_image("ImgVW_mainImage")) +{ + m_xDialog->set_title(atitle); + aBitmap.Scale(Size(300, 300), BmpScaleFlag::Fast); + ScopedVclPtr<VirtualDevice> m_pVirDev = m_xDisplayImage->create_virtual_device(); + m_pVirDev->SetOutputSizePixel(aBitmap.GetSizePixel()); + m_pVirDev->DrawBitmapEx(Point(0, 0), aBitmap); + m_xDisplayImage->set_image(m_pVirDev.get()); + m_pVirDev.disposeAndClear(); +} diff --git a/cui/source/dialogs/QrCodeGenDialog.cxx b/cui/source/dialogs/QrCodeGenDialog.cxx new file mode 100644 index 000000000..e028d4d10 --- /dev/null +++ b/cui/source/dialogs/QrCodeGenDialog.cxx @@ -0,0 +1,394 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <QrCodeGenDialog.hxx> + +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <tools/stream.hxx> +#include <dialmgr.hxx> +#include <strings.hrc> +#include <unotools/streamwrap.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +#if ENABLE_ZXING +#include <rtl/strbuf.hxx> + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshadow" +#endif + +#include <BarcodeFormat.h> +#include <BitArray.h> +#include <BitMatrix.h> +#include <MultiFormatWriter.h> +#include <TextUtfEncoding.h> + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#endif // ENABLE_ZXING + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/drawing/BarCode.hpp> +#include <com/sun/star/drawing/BarCodeErrorCorrection.hpp> +#include <com/sun/star/graphic/XGraphicProvider.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/sheet/XSpreadsheet.hpp> +#include <com/sun/star/sheet/XSpreadsheetView.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/text/XTextViewCursor.hpp> +#include <com/sun/star/text/XTextViewCursorSupplier.hpp> +#include <com/sun/star/drawing/XDrawView.hpp> +#include <com/sun/star/drawing/XDrawPage.hpp> + +using namespace css; +using namespace css::uno; +using namespace css::beans; +using namespace css::container; +using namespace css::frame; +using namespace css::io; +using namespace css::lang; +using namespace css::sheet; +using namespace css::text; +using namespace css::drawing; +using namespace css::graphic; + +namespace +{ +#if ENABLE_ZXING +// Implementation adapted from the answer: https://stackoverflow.com/questions/10789059/create-qr-code-in-vector-image/60638350#60638350 +OString ConvertToSVGFormat(const ZXing::BitMatrix& bitmatrix) +{ + OStringBuffer sb; + const int width = bitmatrix.width(); + const int height = bitmatrix.height(); + ZXing::BitArray row(width); + sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 " + + OString::number(width) + " " + OString::number(height) + + "\" stroke=\"none\">\n" + "<path d=\""); + for (int i = 0; i < height; ++i) + { + bitmatrix.getRow(i, row); + for (int j = 0; j < width; ++j) + { + if (row.get(j)) + { + sb.append("M" + OString::number(j) + "," + OString::number(i) + "h1v1h-1z"); + } + } + } + sb.append("\"/>\n</svg>"); + return sb.toString(); +} + +std::string GetBarCodeType(int type) +{ + switch (type) + { + case 1: + return "Code128"; + default: + return "QRCode"; + } +} + +OString GenerateQRCode(std::u16string_view aQRText, tools::Long aQRECC, int aQRBorder, int aQRType) +{ + // Associated ZXing error correction levels (0-8) to our constants arbitrarily. + int bqrEcc = 1; + + switch (aQRECC) + { + case css::drawing::BarCodeErrorCorrection::LOW: + { + bqrEcc = 1; + break; + } + case css::drawing::BarCodeErrorCorrection::MEDIUM: + { + bqrEcc = 3; + break; + } + case css::drawing::BarCodeErrorCorrection::QUARTILE: + { + bqrEcc = 5; + break; + } + case css::drawing::BarCodeErrorCorrection::HIGH: + { + bqrEcc = 7; + break; + } + } + + OString o = OUStringToOString(aQRText, RTL_TEXTENCODING_UTF8); + std::string QRText(o.getStr(), o.getLength()); + ZXing::BarcodeFormat format = ZXing::BarcodeFormatFromString(GetBarCodeType(aQRType)); + auto writer = ZXing::MultiFormatWriter(format).setMargin(aQRBorder).setEccLevel(bqrEcc); + writer.setEncoding(ZXing::CharacterSet::UTF8); + ZXing::BitMatrix bitmatrix = writer.encode(ZXing::TextUtfEncoding::FromUtf8(QRText), 0, 0); + return ConvertToSVGFormat(bitmatrix); +} +#endif + +} // anonymous namespace + +QrCodeGenDialog::QrCodeGenDialog(weld::Widget* pParent, Reference<XModel> xModel, + bool bEditExisting) + : GenericDialogController(pParent, "cui/ui/qrcodegen.ui", "QrCodeGenDialog") + , m_xModel(std::move(xModel)) + , m_xEdittext(m_xBuilder->weld_text_view("edit_text")) + , m_xECC{ m_xBuilder->weld_radio_button("button_low"), + m_xBuilder->weld_radio_button("button_medium"), + m_xBuilder->weld_radio_button("button_quartile"), + m_xBuilder->weld_radio_button("button_high") } + , m_xSpinBorder(m_xBuilder->weld_spin_button("edit_margin")) + , m_xComboType(m_xBuilder->weld_combo_box("choose_type")) +#if ENABLE_ZXING + , mpParent(pParent) +#endif +{ + m_xEdittext->set_size_request(m_xEdittext->get_approximate_digit_width() * 28, + m_xEdittext->get_height_rows(6)); + if (!bEditExisting) + { + OUString sSelection; + // TODO: This only works in Writer doc. Should also work in shapes + Reference<XIndexAccess> xSelections(m_xModel->getCurrentSelection(), UNO_QUERY); + if (xSelections.is()) + { + Reference<XTextRange> xSelection(xSelections->getByIndex(0), UNO_QUERY); + if (xSelection.is()) + sSelection = xSelection->getString(); + } + if (!sSelection.isEmpty()) + m_xEdittext->set_text(sSelection); + m_xEdittext->select_region(0, -1); + return; + } + + Reference<container::XIndexAccess> xIndexAccess(m_xModel->getCurrentSelection(), + UNO_QUERY_THROW); + Reference<XPropertySet> xProps(xIndexAccess->getByIndex(0), UNO_QUERY_THROW); + + // Read properties from selected QR Code + css::drawing::BarCode aBarCode; + xProps->getPropertyValue("BarCodeProperties") >>= aBarCode; + + m_xEdittext->set_text(aBarCode.Payload); + + //Get Error Correction Constant from selected QR Code + GetErrorCorrection(aBarCode.ErrorCorrection); + + m_xSpinBorder->set_value(aBarCode.Border); + + m_xComboType->set_active(aBarCode.Type); + + // Mark this as existing shape + m_xExistingShapeProperties = xProps; +} + +short QrCodeGenDialog::run() +{ +#if ENABLE_ZXING + short nRet; + while (true) + { + nRet = GenericDialogController::run(); + if (nRet == RET_OK) + { + try + { + Apply(); + break; + } + catch (const std::exception&) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog( + mpParent, VclMessageType::Warning, VclButtonsType::Ok, + CuiResId(RID_CUISTR_QRCODEDATALONG))); + xBox->run(); + } + } + else + break; + } + return nRet; +#else + return RET_CANCEL; +#endif +} + +void QrCodeGenDialog::Apply() +{ +#if ENABLE_ZXING + css::drawing::BarCode aBarCode; + aBarCode.Payload = m_xEdittext->get_text(); + aBarCode.Type = m_xComboType->get_active(); + + bool bLowECCActive(m_xECC[0]->get_active()); + bool bMediumECCActive(m_xECC[1]->get_active()); + bool bQuartileECCActive(m_xECC[2]->get_active()); + + if (bLowECCActive) + { + aBarCode.ErrorCorrection = css::drawing::BarCodeErrorCorrection::LOW; + } + else if (bMediumECCActive) + { + aBarCode.ErrorCorrection = css::drawing::BarCodeErrorCorrection::MEDIUM; + } + else if (bQuartileECCActive) + { + aBarCode.ErrorCorrection = css::drawing::BarCodeErrorCorrection::QUARTILE; + } + else + { + aBarCode.ErrorCorrection = css::drawing::BarCodeErrorCorrection::HIGH; + } + + aBarCode.Border = m_xSpinBorder->get_value(); + + // Read svg and replace placeholder texts + OString aSvgImage = GenerateQRCode(aBarCode.Payload, aBarCode.ErrorCorrection, aBarCode.Border, + aBarCode.Type); + + // Insert/Update graphic + SvMemoryStream aSvgStream(4096, 4096); + aSvgStream.WriteOString(aSvgImage); + Reference<XInputStream> xInputStream(new utl::OSeekableInputStreamWrapper(aSvgStream)); + Reference<XComponentContext> xContext(comphelper::getProcessComponentContext()); + Reference<XGraphicProvider> xProvider = css::graphic::GraphicProvider::create(xContext); + + Sequence<PropertyValue> aMediaProperties{ comphelper::makePropertyValue("InputStream", + xInputStream) }; + Reference<XGraphic> xGraphic(xProvider->queryGraphic(aMediaProperties)); + + bool bIsExistingQRCode = m_xExistingShapeProperties.is(); + Reference<XPropertySet> xShapeProps; + if (bIsExistingQRCode) + xShapeProps = m_xExistingShapeProperties; + else + xShapeProps.set(Reference<lang::XMultiServiceFactory>(m_xModel, UNO_QUERY_THROW) + ->createInstance("com.sun.star.drawing.GraphicObjectShape"), + UNO_QUERY); + + xShapeProps->setPropertyValue("Graphic", Any(xGraphic)); + + // Set QRCode properties + xShapeProps->setPropertyValue("BarCodeProperties", Any(aBarCode)); + + if (bIsExistingQRCode) + return; + + // Default size + Reference<XShape> xShape(xShapeProps, UNO_QUERY); + awt::Size aShapeSize; + aShapeSize.Height = 4000; + aShapeSize.Width = 4000; + xShape->setSize(aShapeSize); + + // Default anchoring + xShapeProps->setPropertyValue("AnchorType", Any(TextContentAnchorType_AT_PARAGRAPH)); + + const Reference<XServiceInfo> xServiceInfo(m_xModel, UNO_QUERY_THROW); + + // Writer + if (xServiceInfo->supportsService("com.sun.star.text.TextDocument")) + { + Reference<XTextContent> xTextContent(xShape, UNO_QUERY_THROW); + Reference<XTextViewCursorSupplier> xViewCursorSupplier(m_xModel->getCurrentController(), + UNO_QUERY_THROW); + Reference<XTextViewCursor> xCursor = xViewCursorSupplier->getViewCursor(); + // use cursor's XText - it might be in table cell, frame, ... + Reference<XText> const xText(xCursor->getText()); + assert(xText.is()); + xText->insertTextContent(xCursor, xTextContent, true); + return; + } + + // Calc + else if (xServiceInfo->supportsService("com.sun.star.sheet.SpreadsheetDocument")) + { + Reference<XPropertySet> xSheetCell(m_xModel->getCurrentSelection(), UNO_QUERY_THROW); + awt::Point aCellPosition; + xSheetCell->getPropertyValue("Position") >>= aCellPosition; + xShape->setPosition(aCellPosition); + + Reference<XSpreadsheetView> xView(m_xModel->getCurrentController(), UNO_QUERY_THROW); + Reference<XSpreadsheet> xSheet(xView->getActiveSheet(), UNO_SET_THROW); + Reference<XDrawPageSupplier> xDrawPageSupplier(xSheet, UNO_QUERY_THROW); + Reference<XDrawPage> xDrawPage(xDrawPageSupplier->getDrawPage(), UNO_SET_THROW); + Reference<XShapes> xShapes(xDrawPage, UNO_QUERY_THROW); + + xShapes->add(xShape); + return; + } + + //Impress and Draw + else if (xServiceInfo->supportsService("com.sun.star.presentation.PresentationDocument") + || xServiceInfo->supportsService("com.sun.star.drawing.DrawingDocument")) + { + Reference<XDrawView> xView(m_xModel->getCurrentController(), UNO_QUERY_THROW); + Reference<XDrawPage> xPage(xView->getCurrentPage(), UNO_SET_THROW); + Reference<XShapes> xShapes(xPage, UNO_QUERY_THROW); + + xShapes->add(xShape); + return; + } + + else + { + //Not implemented for math,base and other apps. + throw uno::RuntimeException("Not implemented"); + } +#endif +} + +void QrCodeGenDialog::GetErrorCorrection(tools::Long ErrorCorrection) +{ + switch (ErrorCorrection) + { + case css::drawing::BarCodeErrorCorrection::LOW: + { + m_xECC[0]->set_active(true); + break; + } + case css::drawing::BarCodeErrorCorrection::MEDIUM: + { + m_xECC[1]->set_active(true); + break; + } + case css::drawing::BarCodeErrorCorrection::QUARTILE: + { + m_xECC[2]->set_active(true); + break; + } + case css::drawing::BarCodeErrorCorrection::HIGH: + { + m_xECC[3]->set_active(true); + break; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/cui/source/dialogs/SignSignatureLineDialog.cxx b/cui/source/dialogs/SignSignatureLineDialog.cxx new file mode 100644 index 000000000..94306764b --- /dev/null +++ b/cui/source/dialogs/SignSignatureLineDialog.cxx @@ -0,0 +1,258 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <SignSignatureLineDialog.hxx> + +#include <sal/log.hxx> +#include <sal/types.h> + +#include <dialmgr.hxx> +#include <strings.hrc> + +#include <comphelper/graphicmimetype.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/objsh.hxx> +#include <svx/xoutbmp.hxx> +#include <utility> +#include <vcl/graph.hxx> +#include <vcl/weld.hxx> +#include <svx/signaturelinehelper.hxx> +#include <tools/urlobj.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/graphic/XGraphicProvider.hpp> +#include <com/sun/star/security/CertificateKind.hpp> +#include <com/sun/star/security/XCertificate.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> + +using namespace comphelper; +using namespace css; +using namespace css::uno; +using namespace css::beans; +using namespace css::frame; +using namespace css::io; +using namespace css::lang; +using namespace css::frame; +using namespace css::text; +using namespace css::graphic; +using namespace css::security; +using namespace css::ui::dialogs; + +SignSignatureLineDialog::SignSignatureLineDialog(weld::Widget* pParent, Reference<XModel> xModel) + : SignatureLineDialogBase(pParent, std::move(xModel), "cui/ui/signsignatureline.ui", + "SignSignatureLineDialog") + , m_xEditName(m_xBuilder->weld_entry("edit_name")) + , m_xEditComment(m_xBuilder->weld_text_view("edit_comment")) + , m_xBtnLoadImage(m_xBuilder->weld_button("btn_load_image")) + , m_xBtnClearImage(m_xBuilder->weld_button("btn_clear_image")) + , m_xBtnChooseCertificate(m_xBuilder->weld_button("btn_select_certificate")) + , m_xBtnSign(m_xBuilder->weld_button("ok")) + , m_xLabelHint(m_xBuilder->weld_label("label_hint")) + , m_xLabelHintText(m_xBuilder->weld_label("label_hint_text")) + , m_xLabelAddComment(m_xBuilder->weld_label("label_add_comment")) + , m_bShowSignDate(false) +{ + Reference<container::XIndexAccess> xIndexAccess(m_xModel->getCurrentSelection(), + UNO_QUERY_THROW); + m_xShapeProperties.set(xIndexAccess->getByIndex(0), UNO_QUERY_THROW); + + bool bIsSignatureLine(false); + m_xShapeProperties->getPropertyValue("IsSignatureLine") >>= bIsSignatureLine; + if (!bIsSignatureLine) + { + SAL_WARN("cui.dialogs", "No signature line selected!"); + return; + } + + m_xBtnLoadImage->connect_clicked(LINK(this, SignSignatureLineDialog, loadImage)); + m_xBtnClearImage->connect_clicked(LINK(this, SignSignatureLineDialog, clearImage)); + m_xBtnChooseCertificate->connect_clicked( + LINK(this, SignSignatureLineDialog, chooseCertificate)); + m_xEditName->connect_changed(LINK(this, SignSignatureLineDialog, entryChanged)); + + // Read properties from selected signature line + m_xShapeProperties->getPropertyValue("SignatureLineId") >>= m_aSignatureLineId; + m_xShapeProperties->getPropertyValue("SignatureLineSuggestedSignerName") + >>= m_aSuggestedSignerName; + m_xShapeProperties->getPropertyValue("SignatureLineSuggestedSignerTitle") + >>= m_aSuggestedSignerTitle; + OUString aSigningInstructions; + m_xShapeProperties->getPropertyValue("SignatureLineSigningInstructions") + >>= aSigningInstructions; + m_xShapeProperties->getPropertyValue("SignatureLineShowSignDate") >>= m_bShowSignDate; + bool bCanAddComment(false); + m_xShapeProperties->getPropertyValue("SignatureLineCanAddComment") >>= bCanAddComment; + + if (aSigningInstructions.isEmpty()) + { + m_xLabelHint->hide(); + m_xLabelHintText->hide(); + } + else + { + m_xLabelHintText->set_label(aSigningInstructions); + } + + if (bCanAddComment) + { + m_xEditComment->set_size_request(m_xEditComment->get_approximate_digit_width() * 48, + m_xEditComment->get_text_height() * 5); + } + else + { + m_xLabelAddComment->hide(); + m_xEditComment->hide(); + m_xEditComment->set_size_request(0, 0); + } + + ValidateFields(); +} + +IMPL_LINK_NOARG(SignSignatureLineDialog, loadImage, weld::Button&, void) +{ + Reference<XComponentContext> xContext = comphelper::getProcessComponentContext(); + sfx2::FileDialogHelper aHelper(TemplateDescription::FILEOPEN_PREVIEW, FileDialogFlags::NONE, + m_xDialog.get()); + aHelper.SetContext(sfx2::FileDialogHelper::SignatureLine); + Reference<XFilePicker3> xFilePicker = aHelper.GetFilePicker(); + if (!xFilePicker->execute()) + return; + + Sequence<OUString> aSelectedFiles = xFilePicker->getSelectedFiles(); + if (!aSelectedFiles.hasElements()) + return; + + Reference<XGraphicProvider> xProvider = GraphicProvider::create(xContext); + Sequence<PropertyValue> aMediaProperties{ comphelper::makePropertyValue("URL", + aSelectedFiles[0]) }; + m_xSignatureImage = xProvider->queryGraphic(aMediaProperties); + m_sOriginalImageBtnLabel = m_xBtnLoadImage->get_label(); + + INetURLObject aObj(aSelectedFiles[0]); + m_xBtnLoadImage->set_label(aObj.GetLastName()); + + ValidateFields(); +} + +IMPL_LINK_NOARG(SignSignatureLineDialog, clearImage, weld::Button&, void) +{ + m_xSignatureImage.set(nullptr); + m_xBtnLoadImage->set_label(m_sOriginalImageBtnLabel); + ValidateFields(); +} + +IMPL_LINK_NOARG(SignSignatureLineDialog, chooseCertificate, weld::Button&, void) +{ + // Document needs to be saved before selecting a certificate + SfxObjectShell* pShell = SfxObjectShell::Current(); + if (!pShell || !pShell->PrepareForSigning(m_xDialog.get())) + return; + + Reference<XCertificate> xSignCertificate + = svx::SignatureLineHelper::getSignatureCertificate(pShell, m_xDialog.get()); + + if (xSignCertificate.is()) + { + m_xSelectedCertifate = xSignCertificate; + m_xBtnChooseCertificate->set_label( + svx::SignatureLineHelper::getSignerName(xSignCertificate)); + } + ValidateFields(); +} + +IMPL_LINK_NOARG(SignSignatureLineDialog, entryChanged, weld::Entry&, void) { ValidateFields(); } + +void SignSignatureLineDialog::ValidateFields() +{ + bool bEnableSignBtn = m_xSelectedCertifate.is() + && (!m_xEditName->get_text().isEmpty() || m_xSignatureImage.is()); + m_xBtnSign->set_sensitive(bEnableSignBtn); + + m_xEditName->set_sensitive(!m_xSignatureImage.is()); + m_xBtnLoadImage->set_sensitive(m_xEditName->get_text().isEmpty()); + m_xBtnClearImage->set_sensitive(m_xSignatureImage.is()); +} + +void SignSignatureLineDialog::Apply() +{ + if (!m_xSelectedCertifate.is()) + { + SAL_WARN("cui.dialogs", "No certificate selected!"); + return; + } + + SfxObjectShell* pShell = SfxObjectShell::Current(); + if (!pShell) + { + SAL_WARN("cui.dialogs", "No SfxObjectShell!"); + return; + } + + Reference<XGraphic> xValidGraphic = getSignedGraphic(true); + Reference<XGraphic> xInvalidGraphic = getSignedGraphic(false); + pShell->SignSignatureLine(m_xDialog.get(), m_aSignatureLineId, m_xSelectedCertifate, + xValidGraphic, xInvalidGraphic, m_xEditComment->get_text()); +} + +css::uno::Reference<css::graphic::XGraphic> SignSignatureLineDialog::getSignedGraphic(bool bValid) +{ + // Read svg and replace placeholder texts + OUString aSvgImage(svx::SignatureLineHelper::getSignatureImage()); + aSvgImage = aSvgImage.replaceAll("[SIGNER_NAME]", getCDataString(m_aSuggestedSignerName)); + aSvgImage = aSvgImage.replaceAll("[SIGNER_TITLE]", getCDataString(m_aSuggestedSignerTitle)); + + OUString aIssuerLine + = CuiResId(RID_CUISTR_SIGNATURELINE_SIGNED_BY) + .replaceFirst("%1", svx::SignatureLineHelper::getSignerName(m_xSelectedCertifate)); + aSvgImage = aSvgImage.replaceAll("[SIGNED_BY]", getCDataString(aIssuerLine)); + if (bValid) + aSvgImage = aSvgImage.replaceAll("[INVALID_SIGNATURE]", ""); + + OUString aDate; + if (m_bShowSignDate && bValid) + { + aDate = svx::SignatureLineHelper::getLocalizedDate(); + } + aSvgImage = aSvgImage.replaceAll("[DATE]", aDate); + + // Custom signature image + if (m_xSignatureImage.is()) + { + OUString aGraphicInBase64; + Graphic aGraphic(m_xSignatureImage); + if (!XOutBitmap::GraphicToBase64(aGraphic, aGraphicInBase64, false)) + SAL_WARN("cui.dialogs", "Could not convert graphic to base64"); + + OUString aImagePart = "<image y=\"825\" x=\"1300\" " + "xlink:href=\"data:[MIMETYPE];base64,[BASE64_IMG]>\" " + "preserveAspectRatio=\"xMidYMid\" height=\"1520\" " + "width=\"7600\" />"; + aImagePart = aImagePart.replaceAll( + "[MIMETYPE]", GraphicMimeTypeHelper::GetMimeTypeForXGraphic(m_xSignatureImage)); + aImagePart = aImagePart.replaceAll("[BASE64_IMG]", aGraphicInBase64); + aSvgImage = aSvgImage.replaceAll("[SIGNATURE_IMAGE]", aImagePart); + + aSvgImage = aSvgImage.replaceAll("[SIGNATURE]", ""); + } + else + { + aSvgImage = aSvgImage.replaceAll("[SIGNATURE_IMAGE]", ""); + aSvgImage = aSvgImage.replaceAll("[SIGNATURE]", getCDataString(m_xEditName->get_text())); + } + + // Create graphic + return svx::SignatureLineHelper::importSVG(aSvgImage); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/cui/source/dialogs/SignatureLineDialog.cxx b/cui/source/dialogs/SignatureLineDialog.cxx new file mode 100644 index 000000000..224dcdecd --- /dev/null +++ b/cui/source/dialogs/SignatureLineDialog.cxx @@ -0,0 +1,194 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <SignatureLineDialog.hxx> + +#include <comphelper/xmltools.hxx> +#include <utility> +#include <vcl/weld.hxx> +#include <svx/signaturelinehelper.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/sheet/XSpreadsheet.hpp> +#include <com/sun/star/sheet/XSpreadsheetDocument.hpp> +#include <com/sun/star/sheet/XSpreadsheetView.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/text/XTextViewCursor.hpp> +#include <com/sun/star/text/XTextViewCursorSupplier.hpp> + +using namespace css; +using namespace css::uno; +using namespace css::beans; +using namespace css::container; +using namespace css::frame; +using namespace css::lang; +using namespace css::frame; +using namespace css::sheet; +using namespace css::text; +using namespace css::drawing; +using namespace css::graphic; + +SignatureLineDialog::SignatureLineDialog(weld::Widget* pParent, Reference<XModel> xModel, + bool bEditExisting) + : SignatureLineDialogBase(pParent, std::move(xModel), "cui/ui/signatureline.ui", + "SignatureLineDialog") + , m_xEditName(m_xBuilder->weld_entry("edit_name")) + , m_xEditTitle(m_xBuilder->weld_entry("edit_title")) + , m_xEditEmail(m_xBuilder->weld_entry("edit_email")) + , m_xEditInstructions(m_xBuilder->weld_text_view("edit_instructions")) + , m_xCheckboxCanAddComments(m_xBuilder->weld_check_button("checkbox_can_add_comments")) + , m_xCheckboxShowSignDate(m_xBuilder->weld_check_button("checkbox_show_sign_date")) +{ + m_xEditInstructions->set_size_request(m_xEditInstructions->get_approximate_digit_width() * 48, + m_xEditInstructions->get_text_height() * 5); + + // No signature line selected - start with empty dialog and set some default values + if (!bEditExisting) + { + m_xCheckboxCanAddComments->set_active(true); + m_xCheckboxShowSignDate->set_active(true); + return; + } + + Reference<container::XIndexAccess> xIndexAccess(m_xModel->getCurrentSelection(), + UNO_QUERY_THROW); + Reference<XPropertySet> xProps(xIndexAccess->getByIndex(0), UNO_QUERY_THROW); + + // Read properties from selected signature line + xProps->getPropertyValue("SignatureLineId") >>= m_aSignatureLineId; + OUString aSuggestedSignerName; + xProps->getPropertyValue("SignatureLineSuggestedSignerName") >>= aSuggestedSignerName; + m_xEditName->set_text(aSuggestedSignerName); + OUString aSuggestedSignerTitle; + xProps->getPropertyValue("SignatureLineSuggestedSignerTitle") >>= aSuggestedSignerTitle; + m_xEditTitle->set_text(aSuggestedSignerTitle); + OUString aSuggestedSignerEmail; + xProps->getPropertyValue("SignatureLineSuggestedSignerEmail") >>= aSuggestedSignerEmail; + m_xEditEmail->set_text(aSuggestedSignerEmail); + OUString aSigningInstructions; + xProps->getPropertyValue("SignatureLineSigningInstructions") >>= aSigningInstructions; + m_xEditInstructions->set_text(aSigningInstructions); + bool bCanAddComments = false; + xProps->getPropertyValue("SignatureLineCanAddComment") >>= bCanAddComments; + m_xCheckboxCanAddComments->set_active(bCanAddComments); + bool bShowSignDate = false; + xProps->getPropertyValue("SignatureLineShowSignDate") >>= bShowSignDate; + m_xCheckboxShowSignDate->set_active(bShowSignDate); + + // Mark this as existing shape + m_xExistingShapeProperties = xProps; +} + +void SignatureLineDialog::Apply() +{ + if (m_aSignatureLineId.isEmpty()) + m_aSignatureLineId + = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_ASCII_US); + OUString aSignerName(m_xEditName->get_text()); + OUString aSignerTitle(m_xEditTitle->get_text()); + OUString aSignerEmail(m_xEditEmail->get_text()); + OUString aSigningInstructions(m_xEditInstructions->get_text()); + bool bCanAddComments(m_xCheckboxCanAddComments->get_active()); + bool bShowSignDate(m_xCheckboxShowSignDate->get_active()); + + // Read svg and replace placeholder texts + OUString aSvgImage(svx::SignatureLineHelper::getSignatureImage()); + aSvgImage = aSvgImage.replaceAll("[SIGNER_NAME]", getCDataString(aSignerName)); + aSvgImage = aSvgImage.replaceAll("[SIGNER_TITLE]", getCDataString(aSignerTitle)); + + // These are only filled if the signature line is signed. + aSvgImage = aSvgImage.replaceAll("[SIGNATURE]", ""); + aSvgImage = aSvgImage.replaceAll("[SIGNED_BY]", ""); + aSvgImage = aSvgImage.replaceAll("[INVALID_SIGNATURE]", ""); + aSvgImage = aSvgImage.replaceAll("[DATE]", ""); + + // Insert/Update graphic + Reference<XGraphic> xGraphic = svx::SignatureLineHelper::importSVG(aSvgImage); + + bool bIsExistingSignatureLine = m_xExistingShapeProperties.is(); + Reference<XPropertySet> xShapeProps; + if (bIsExistingSignatureLine) + xShapeProps = m_xExistingShapeProperties; + else + xShapeProps.set(Reference<lang::XMultiServiceFactory>(m_xModel, UNO_QUERY_THROW) + ->createInstance("com.sun.star.drawing.GraphicObjectShape"), + UNO_QUERY); + + xShapeProps->setPropertyValue("Graphic", Any(xGraphic)); + xShapeProps->setPropertyValue("SignatureLineUnsignedImage", Any(xGraphic)); + + // Set signature line properties + xShapeProps->setPropertyValue("IsSignatureLine", Any(true)); + xShapeProps->setPropertyValue("SignatureLineId", Any(m_aSignatureLineId)); + if (!aSignerName.isEmpty()) + xShapeProps->setPropertyValue("SignatureLineSuggestedSignerName", Any(aSignerName)); + if (!aSignerTitle.isEmpty()) + xShapeProps->setPropertyValue("SignatureLineSuggestedSignerTitle", Any(aSignerTitle)); + if (!aSignerEmail.isEmpty()) + xShapeProps->setPropertyValue("SignatureLineSuggestedSignerEmail", Any(aSignerEmail)); + if (!aSigningInstructions.isEmpty()) + xShapeProps->setPropertyValue("SignatureLineSigningInstructions", + Any(aSigningInstructions)); + xShapeProps->setPropertyValue("SignatureLineShowSignDate", Any(bShowSignDate)); + xShapeProps->setPropertyValue("SignatureLineCanAddComment", Any(bCanAddComments)); + + if (bIsExistingSignatureLine) + return; + + // Default size + Reference<XShape> xShape(xShapeProps, UNO_QUERY); + awt::Size aShapeSize; + aShapeSize.Height = 3000; + aShapeSize.Width = 6000; + xShape->setSize(aShapeSize); + + // Default anchoring + xShapeProps->setPropertyValue("AnchorType", Any(TextContentAnchorType_AT_PARAGRAPH)); + + // Writer + const Reference<XTextDocument> xTextDocument(m_xModel, UNO_QUERY); + if (xTextDocument.is()) + { + Reference<XTextContent> xTextContent(xShape, UNO_QUERY_THROW); + Reference<XTextViewCursorSupplier> xViewCursorSupplier(m_xModel->getCurrentController(), + UNO_QUERY_THROW); + Reference<XTextViewCursor> xCursor = xViewCursorSupplier->getViewCursor(); + // use cursor's XText - it might be in table cell, frame, ... + Reference<XText> const xText(xCursor->getText()); + assert(xText.is()); + xText->insertTextContent(xCursor, xTextContent, true); + return; + } + + // Calc + const Reference<XSpreadsheetDocument> xSpreadsheetDocument(m_xModel, UNO_QUERY); + if (!xSpreadsheetDocument.is()) + return; + + Reference<XPropertySet> xSheetCell(m_xModel->getCurrentSelection(), UNO_QUERY_THROW); + awt::Point aCellPosition; + xSheetCell->getPropertyValue("Position") >>= aCellPosition; + xShape->setPosition(aCellPosition); + + Reference<XSpreadsheetView> xView(m_xModel->getCurrentController(), UNO_QUERY_THROW); + Reference<XSpreadsheet> xSheet(xView->getActiveSheet(), UNO_SET_THROW); + Reference<XDrawPageSupplier> xDrawPageSupplier(xSheet, UNO_QUERY_THROW); + Reference<XDrawPage> xDrawPage(xDrawPageSupplier->getDrawPage(), UNO_SET_THROW); + Reference<XShapes> xShapes(xDrawPage, UNO_QUERY_THROW); + + xShapes->add(xShape); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/cui/source/dialogs/SignatureLineDialogBase.cxx b/cui/source/dialogs/SignatureLineDialogBase.cxx new file mode 100644 index 000000000..4e591124b --- /dev/null +++ b/cui/source/dialogs/SignatureLineDialogBase.cxx @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <SignatureLineDialogBase.hxx> + +#include <utility> + +#include <vcl/weld.hxx> + +using namespace css; +using namespace css::uno; +using namespace css::frame; + +SignatureLineDialogBase::SignatureLineDialogBase(weld::Widget* pParent, Reference<XModel> xModel, + const OUString& rUIFile, const OString& rDialogId) + : GenericDialogController(pParent, rUIFile, rDialogId) + , m_xModel(std::move(xModel)) +{ +} + +short SignatureLineDialogBase::run() +{ + short nRet = GenericDialogController::run(); + if (nRet == RET_OK) + Apply(); + return nRet; +} + +OUString SignatureLineDialogBase::getCDataString(std::u16string_view rString) +{ + return OUString::Concat("<![CDATA[") + rString + "]]>"; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/cui/source/dialogs/SpellAttrib.hxx b/cui/source/dialogs/SpellAttrib.hxx new file mode 100644 index 000000000..cdc0cf266 --- /dev/null +++ b/cui/source/dialogs/SpellAttrib.hxx @@ -0,0 +1,118 @@ +/* -*- 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 . + */ +#pragma once + +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/linguistic2/XProofreader.hpp> +#include <utility> + +namespace svx{ +struct SpellErrorDescription +{ + bool bIsGrammarError; + OUString sErrorText; + OUString sDialogTitle; + OUString sExplanation; + OUString sExplanationURL; + css::lang::Locale aLocale; + css::uno::Reference< css::linguistic2::XProofreader > xGrammarChecker; + css::uno::Sequence< OUString > aSuggestions; + OUString sRuleId; + + SpellErrorDescription( bool bGrammar, + OUString aText, + css::lang::Locale _aLocale, + const css::uno::Sequence< OUString >& rSuggestions, + css::uno::Reference< css::linguistic2::XProofreader > _xGrammarChecker, + const OUString* pDialogTitle = nullptr, + const OUString* pExplanation = nullptr, + const OUString* pRuleId = nullptr, + const OUString* pExplanationURL = nullptr ) : + bIsGrammarError( bGrammar ), + sErrorText(std::move( aText )), + sDialogTitle( ), + sExplanation( ), + sExplanationURL( ), + aLocale(std::move( _aLocale )), + xGrammarChecker(std::move( _xGrammarChecker )), + aSuggestions( rSuggestions ) + { + if( pDialogTitle ) + sDialogTitle = *pDialogTitle; + if( pExplanation ) + sExplanation = *pExplanation; + if( pExplanationURL ) + sExplanationURL = *pExplanationURL; + if( pRuleId ) + sRuleId = *pRuleId; + }; + + SpellErrorDescription() + : bIsGrammarError(false) + { + } + + bool operator==( const SpellErrorDescription& rDesc ) const + { + return bIsGrammarError == rDesc.bIsGrammarError && + sErrorText == rDesc.sErrorText && + aLocale.Language == rDesc.aLocale.Language && + aLocale.Country == rDesc.aLocale.Country && + aLocale.Variant == rDesc.aLocale.Variant && + aSuggestions == rDesc.aSuggestions && + xGrammarChecker == rDesc.xGrammarChecker && + sDialogTitle == rDesc.sDialogTitle && + sExplanation == rDesc.sExplanation && + sExplanationURL == rDesc.sExplanationURL && + sRuleId == rDesc.sRuleId; + } + + css::uno::Sequence<css::uno::Any> toSequence() const + { + css::uno::Sequence<css::uno::Any> aEntries{ css::uno::Any(bIsGrammarError), + css::uno::Any(sErrorText), + css::uno::Any(sDialogTitle), + css::uno::Any(sExplanation), + css::uno::Any(sExplanationURL), + css::uno::Any(aLocale), + css::uno::Any(xGrammarChecker), + css::uno::Any(aSuggestions), + css::uno::Any(sRuleId) }; + return aEntries; + } + + void fromSequence(const css::uno::Sequence<css::uno::Any>& rEntries) + { + rEntries[0] >>= bIsGrammarError; + rEntries[1] >>= sErrorText; + rEntries[2] >>= sDialogTitle; + rEntries[3] >>= sExplanation; + rEntries[4] >>= sExplanationURL; + rEntries[5] >>= aLocale; + rEntries[6] >>= xGrammarChecker; + rEntries[7] >>= aSuggestions; + rEntries[8] >>= sRuleId; + } +}; + +}//namespace svx + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/SpellDialog.cxx b/cui/source/dialogs/SpellDialog.cxx new file mode 100644 index 000000000..e233dffff --- /dev/null +++ b/cui/source/dialogs/SpellDialog.cxx @@ -0,0 +1,2099 @@ +/* -*- 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 <memory> +#include "SpellAttrib.hxx" +#include <sfx2/bindings.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/viewfrm.hxx> +#include <svl/grabbagitem.hxx> +#include <svl/undo.hxx> +#include <tools/debug.hxx> +#include <unotools/lingucfg.hxx> +#include <editeng/colritem.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/splwrap.hxx> +#include <editeng/unolingu.hxx> +#include <editeng/wghtitem.hxx> +#include <linguistic/misc.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/linguistic2/XDictionary.hpp> +#include <com/sun/star/linguistic2/XSpellAlternatives.hpp> +#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp> +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> +#include <sfx2/app.hxx> +#include <rtl/ustrbuf.hxx> +#include <vcl/specialchars.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <vcl/texteng.hxx> +#include <vcl/weld.hxx> +#include <svx/SpellDialogChildWindow.hxx> +#include <SpellDialog.hxx> +#include <optlingu.hxx> +#include <treeopt.hxx> +#include <svtools/colorcfg.hxx> +#include <svtools/langtab.hxx> +#include <sal/log.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <comphelper/lok.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::linguistic2; +using namespace linguistic; + + +// struct SpellDialog_Impl --------------------------------------------- + +struct SpellDialog_Impl +{ + Sequence< Reference< XDictionary > > aDics; +}; + + +#define SPELLUNDO_START 200 + +#define SPELLUNDO_CHANGE_LANGUAGE (SPELLUNDO_START + 1) +#define SPELLUNDO_CHANGE_TEXTENGINE (SPELLUNDO_START + 2) +#define SPELLUNDO_CHANGE_NEXTERROR (SPELLUNDO_START + 3) +#define SPELLUNDO_CHANGE_ADD_TO_DICTIONARY (SPELLUNDO_START + 4) +#define SPELLUNDO_CHANGE_GROUP (SPELLUNDO_START + 5) //undo list +#define SPELLUNDO_MOVE_ERROREND (SPELLUNDO_START + 6) +#define SPELLUNDO_UNDO_EDIT_MODE (SPELLUNDO_START + 7) +#define SPELLUNDO_ADD_IGNORE_RULE (SPELLUNDO_START + 8) + +namespace svx{ +class SpellUndoAction_Impl : public SfxUndoAction +{ + sal_uInt16 m_nId; + const Link<SpellUndoAction_Impl&,void>& m_rActionLink; + //undo of button enabling + bool m_bEnableChangePB; + bool m_bEnableChangeAllPB; + //undo of MarkNextError - used in change and change all, ignore and ignore all + tools::Long m_nOldErrorStart; + tools::Long m_nOldErrorEnd; + bool m_bIsErrorLanguageSelected; + //undo of AddToDictionary + Reference<XDictionary> m_xDictionary; + OUString m_sAddedWord; + //move end of error - ::ChangeMarkedWord() + tools::Long m_nOffset; + +public: + SpellUndoAction_Impl(sal_uInt16 nId, const Link<SpellUndoAction_Impl&,void>& rActionLink) : + m_nId(nId), + m_rActionLink( rActionLink), + m_bEnableChangePB(false), + m_bEnableChangeAllPB(false), + m_nOldErrorStart(-1), + m_nOldErrorEnd(-1), + m_bIsErrorLanguageSelected(false), + m_nOffset(0) + {} + + virtual void Undo() override; + sal_uInt16 GetId() const; + + void SetEnableChangePB(){m_bEnableChangePB = true;} + bool IsEnableChangePB() const {return m_bEnableChangePB;} + + void SetEnableChangeAllPB(){m_bEnableChangeAllPB = true;} + bool IsEnableChangeAllPB() const {return m_bEnableChangeAllPB;} + + void SetErrorMove(tools::Long nOldStart, tools::Long nOldEnd) + { + m_nOldErrorStart = nOldStart; + m_nOldErrorEnd = nOldEnd; + } + tools::Long GetOldErrorStart() const { return m_nOldErrorStart;} + tools::Long GetOldErrorEnd() const { return m_nOldErrorEnd;} + + void SetErrorLanguageSelected(bool bSet){ m_bIsErrorLanguageSelected = bSet;} + bool IsErrorLanguageSelected() const {return m_bIsErrorLanguageSelected;} + + void SetDictionary(const Reference<XDictionary>& xDict) { m_xDictionary = xDict; } + const Reference<XDictionary>& GetDictionary() const { return m_xDictionary; } + void SetAddedWord(const OUString& rWord) {m_sAddedWord = rWord;} + const OUString& GetAddedWord() const { return m_sAddedWord;} + + void SetOffset(tools::Long nSet) {m_nOffset = nSet;} + tools::Long GetOffset() const {return m_nOffset;} +}; +}//namespace svx +using namespace ::svx; + +void SpellUndoAction_Impl::Undo() +{ + m_rActionLink.Call(*this); +} + + +sal_uInt16 SpellUndoAction_Impl::GetId()const +{ + return m_nId; +} + +// class SvxSpellCheckDialog --------------------------------------------- + +SpellDialog::SpellDialog(SpellDialogChildWindow* pChildWindow, + weld::Window * pParent, SfxBindings* _pBindings) + : SfxModelessDialogController (_pBindings, pChildWindow, + pParent, "cui/ui/spellingdialog.ui", "SpellingDialog") + , aDialogUndoLink(LINK (this, SpellDialog, DialogUndoHdl)) + , m_pInitHdlEvent(nullptr) + , bFocusLocked(true) + , rParent(*pChildWindow) + , pImpl( new SpellDialog_Impl ) + , m_xAltTitle(m_xBuilder->weld_label("alttitleft")) + , m_xResumeFT(m_xBuilder->weld_label("resumeft")) + , m_xNoSuggestionsFT(m_xBuilder->weld_label("nosuggestionsft")) + , m_xLanguageFT(m_xBuilder->weld_label("languageft")) + , m_xLanguageLB(new SvxLanguageBox(m_xBuilder->weld_combo_box("languagelb"))) + , m_xExplainFT(m_xBuilder->weld_label("explain")) + , m_xExplainLink(m_xBuilder->weld_link_button("explainlink")) + , m_xNotInDictFT(m_xBuilder->weld_label("notindictft")) + , m_xSentenceED(new SentenceEditWindow_Impl) + , m_xSuggestionFT(m_xBuilder->weld_label("suggestionsft")) + , m_xSuggestionLB(m_xBuilder->weld_tree_view("suggestionslb")) + , m_xIgnorePB(m_xBuilder->weld_button("ignore")) + , m_xIgnoreAllPB(m_xBuilder->weld_button("ignoreall")) + , m_xIgnoreRulePB(m_xBuilder->weld_button("ignorerule")) + , m_xAddToDictPB(m_xBuilder->weld_button("add")) + , m_xAddToDictMB(m_xBuilder->weld_menu_button("addmb")) + , m_xChangePB(m_xBuilder->weld_button("change")) + , m_xChangeAllPB(m_xBuilder->weld_button("changeall")) + , m_xAutoCorrPB(m_xBuilder->weld_button("autocorrect")) + , m_xCheckGrammarCB(m_xBuilder->weld_check_button("checkgrammar")) + , m_xOptionsPB(m_xBuilder->weld_button("options")) + , m_xUndoPB(m_xBuilder->weld_button("undo")) + , m_xClosePB(m_xBuilder->weld_button("close")) + , m_xToolbar(m_xBuilder->weld_toolbar("toolbar")) + , m_xSentenceEDWeld(new weld::CustomWeld(*m_xBuilder, "sentence", *m_xSentenceED)) +{ + m_xSentenceED->SetSpellDialog(this); + m_xSentenceED->Init(m_xToolbar.get()); + + m_sTitleSpellingGrammar = m_xDialog->get_title(); + m_sTitleSpelling = m_xAltTitle->get_label(); + + // fdo#68794 set initial title for cases where no text has been processed + // yet to show its language attributes + OUString sTitle = rParent.HasGrammarChecking() ? m_sTitleSpellingGrammar : m_sTitleSpelling; + m_xDialog->set_title(m_xDialog->strip_mnemonic(sTitle.replaceFirst("$LANGUAGE ($LOCATION)", ""))); + + m_sResumeST = m_xResumeFT->get_label(); + m_sNoSuggestionsST = m_xNoSuggestionsFT->strip_mnemonic(m_xNoSuggestionsFT->get_label()); + + Size aEdSize(m_xSuggestionLB->get_approximate_digit_width() * 60, + m_xSuggestionLB->get_height_rows(6)); + m_xSuggestionLB->set_size_request(aEdSize.Width(), -1); + m_sIgnoreOnceST = m_xIgnorePB->get_label(); + m_xAddToDictMB->set_help_id(m_xAddToDictPB->get_help_id()); + xSpell = LinguMgr::GetSpellChecker(); + + Init_Impl(); + + // disable controls if service is missing + m_xDialog->set_sensitive(xSpell.is()); + + //InitHdl wants to use virtual methods, so it + //can't be called during the ctor, so init + //it on next event cycle post-ctor + m_pInitHdlEvent = Application::PostUserEvent(LINK(this, SpellDialog, InitHdl)); +} + +SpellDialog::~SpellDialog() +{ + if (m_pInitHdlEvent) + Application::RemoveUserEvent(m_pInitHdlEvent); + if (pImpl) + { + // save possibly modified user-dictionaries + Reference< XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() ); + if (xDicList.is()) + SaveDictionaries( xDicList ); + + pImpl.reset(); + } +} + +void SpellDialog::Init_Impl() +{ + // initialize handler + m_xClosePB->connect_clicked(LINK( this, SpellDialog, CancelHdl ) ); + m_xChangePB->connect_clicked(LINK( this, SpellDialog, ChangeHdl ) ); + m_xChangeAllPB->connect_clicked(LINK( this, SpellDialog, ChangeAllHdl ) ); + m_xIgnorePB->connect_clicked(LINK( this, SpellDialog, IgnoreHdl ) ); + m_xIgnoreAllPB->connect_clicked(LINK( this, SpellDialog, IgnoreAllHdl ) ); + m_xIgnoreRulePB->connect_clicked(LINK( this, SpellDialog, IgnoreAllHdl ) ); + m_xUndoPB->connect_clicked(LINK( this, SpellDialog, UndoHdl ) ); + + m_xAutoCorrPB->connect_clicked( LINK( this, SpellDialog, ExtClickHdl ) ); + m_xCheckGrammarCB->connect_toggled( LINK( this, SpellDialog, CheckGrammarHdl )); + m_xOptionsPB->connect_clicked( LINK( this, SpellDialog, ExtClickHdl ) ); + + m_xSuggestionLB->connect_row_activated( LINK( this, SpellDialog, DoubleClickChangeHdl ) ); + + m_xSentenceED->SetModifyHdl(LINK ( this, SpellDialog, ModifyHdl) ); + + m_xAddToDictMB->connect_selected(LINK ( this, SpellDialog, AddToDictSelectHdl ) ); + m_xAddToDictPB->connect_clicked(LINK ( this, SpellDialog, AddToDictClickHdl ) ); + + m_xLanguageLB->connect_changed(LINK( this, SpellDialog, LanguageSelectHdl ) ); + + // initialize language ListBox + m_xLanguageLB->SetLanguageList(SvxLanguageListFlags::SPELL_USED, false, false, true); + + m_xSentenceED->ClearModifyFlag(); + LinguMgr::GetChangeAllList()->clear(); +} + +void SpellDialog::UpdateBoxes_Impl(bool bCallFromSelectHdl) +{ + sal_Int32 i; + m_xSuggestionLB->clear(); + + SpellErrorDescription aSpellErrorDescription; + bool bSpellErrorDescription = m_xSentenceED->GetAlternatives(aSpellErrorDescription); + + LanguageType nAltLanguage = LANGUAGE_NONE; + Sequence< OUString > aNewWords; + bool bIsGrammarError = false; + if( bSpellErrorDescription ) + { + nAltLanguage = LanguageTag::convertToLanguageType( aSpellErrorDescription.aLocale ); + aNewWords = aSpellErrorDescription.aSuggestions; + bIsGrammarError = aSpellErrorDescription.bIsGrammarError; + m_xExplainLink->set_uri( aSpellErrorDescription.sExplanationURL ); + m_xExplainFT->set_label( aSpellErrorDescription.sExplanation ); + } + if( bSpellErrorDescription && !aSpellErrorDescription.sDialogTitle.isEmpty() ) + { + // use this function to apply the correct image to be used... + SetTitle_Impl( nAltLanguage ); + // then change the title to the one to be actually used + m_xDialog->set_title(m_xDialog->strip_mnemonic(aSpellErrorDescription.sDialogTitle)); + } + else + SetTitle_Impl( nAltLanguage ); + if( !bCallFromSelectHdl ) + m_xLanguageLB->set_active_id( nAltLanguage ); + int nDicts = InitUserDicts(); + + // enter alternatives + const OUString *pNewWords = aNewWords.getConstArray(); + const sal_Int32 nSize = aNewWords.getLength(); + for ( i = 0; i < nSize; ++i ) + { + OUString aTmp( pNewWords[i] ); + if (m_xSuggestionLB->find_text(aTmp) == -1) + m_xSuggestionLB->append_text(aTmp); + } + if(!nSize) + m_xSuggestionLB->append_text(m_sNoSuggestionsST); + m_xAutoCorrPB->set_sensitive( nSize > 0 ); + + m_xSuggestionFT->set_sensitive(nSize > 0); + m_xSuggestionLB->set_sensitive(nSize > 0); + if( nSize ) + { + m_xSuggestionLB->select(0); + } + m_xChangePB->set_sensitive( nSize > 0); + m_xChangeAllPB->set_sensitive(nSize > 0); + bool bShowChangeAll = !bIsGrammarError; + m_xChangeAllPB->set_visible( bShowChangeAll ); + m_xExplainFT->set_visible( !bShowChangeAll ); + m_xLanguageLB->set_sensitive( bShowChangeAll ); + m_xIgnoreAllPB->set_visible( bShowChangeAll ); + + m_xAddToDictMB->set_visible( bShowChangeAll && nDicts > 1 && !comphelper::LibreOfficeKit::isActive()); + m_xAddToDictPB->set_visible( bShowChangeAll && nDicts <= 1 && !comphelper::LibreOfficeKit::isActive()); + m_xIgnoreRulePB->set_visible( !bShowChangeAll ); + m_xIgnoreRulePB->set_sensitive(bSpellErrorDescription && !aSpellErrorDescription.sRuleId.isEmpty()); + m_xAutoCorrPB->set_visible( bShowChangeAll && rParent.HasAutoCorrection() ); + + bool bOldShowGrammar = m_xCheckGrammarCB->get_visible(); + bool bOldShowExplain = m_xExplainLink->get_visible(); + + m_xCheckGrammarCB->set_visible(rParent.HasGrammarChecking()); + m_xExplainLink->set_visible(!m_xExplainLink->get_uri().isEmpty()); + if (m_xExplainFT->get_label().isEmpty()) + { + m_xExplainFT->hide(); + m_xExplainLink->hide(); + } + + if (bOldShowExplain != m_xExplainLink->get_visible() || bOldShowGrammar != m_xCheckGrammarCB->get_visible()) + m_xDialog->resize_to_request(); +} + +void SpellDialog::SpellContinue_Impl(std::unique_ptr<UndoChangeGroupGuard>* pGuard, bool bUseSavedSentence, bool bIgnoreCurrentError) +{ + //initially or after the last error of a sentence MarkNextError will fail + //then GetNextSentence() has to be called followed again by MarkNextError() + //MarkNextError is not initially called if the UndoEdit mode is active + bool bNextSentence = false; + if (!m_xSentenceED) + { + return; + } + + if(!((!m_xSentenceED->IsUndoEditMode() && m_xSentenceED->MarkNextError( bIgnoreCurrentError, xSpell )) || + ( bNextSentence = GetNextSentence_Impl(pGuard, bUseSavedSentence, m_xSentenceED->IsUndoEditMode()) && m_xSentenceED->MarkNextError( false, xSpell )))) + return; + + SpellErrorDescription aSpellErrorDescription; + bool bSpellErrorDescription = m_xSentenceED->GetAlternatives(aSpellErrorDescription); + if (bSpellErrorDescription) + { + UpdateBoxes_Impl(); + weld::Widget* aControls[] = + { + m_xNotInDictFT.get(), + m_xSentenceED->GetDrawingArea(), + m_xLanguageFT.get() + }; + for (weld::Widget* pWidget : aControls) + pWidget->set_sensitive(true); + } + if( bNextSentence ) + { + //remove undo if a new sentence is active + m_xSentenceED->ResetUndo(); + m_xUndoPB->set_sensitive(false); + } +} +/* Initialize, asynchronous to prevent virtual calls + from a constructor + */ +IMPL_LINK_NOARG( SpellDialog, InitHdl, void*, void) +{ + m_pInitHdlEvent = nullptr; + m_xDialog->freeze(); + //show or hide AutoCorrect depending on the modules abilities + m_xAutoCorrPB->set_visible(rParent.HasAutoCorrection()); + SpellContinue_Impl(nullptr); + m_xSentenceED->ResetUndo(); + m_xUndoPB->set_sensitive(false); + + // get current language + UpdateBoxes_Impl(); + + // fill dictionary PopupMenu + InitUserDicts(); + + LockFocusChanges(true); + if(m_xSentenceED->IsEnabled()) + m_xSentenceED->GrabFocus(); + else if( m_xChangePB->get_sensitive() ) + m_xChangePB->grab_focus(); + else if( m_xIgnorePB->get_sensitive() ) + m_xIgnorePB->grab_focus(); + else if( m_xClosePB->get_sensitive() ) + m_xClosePB->grab_focus(); + LockFocusChanges(false); + //show grammar CheckBox depending on the modules abilities + m_xCheckGrammarCB->set_active(rParent.IsGrammarChecking()); + m_xDialog->thaw(); +}; + +IMPL_LINK( SpellDialog, ExtClickHdl, weld::Button&, rBtn, void ) +{ + if (m_xOptionsPB.get() == &rBtn) + StartSpellOptDlg_Impl(); + else if (m_xAutoCorrPB.get() == &rBtn) + { + //get the currently selected wrong word + OUString sCurrentErrorText = m_xSentenceED->GetErrorText(); + //get the wrong word from the XSpellAlternative + SpellErrorDescription aSpellErrorDescription; + bool bSpellErrorDescription = m_xSentenceED->GetAlternatives(aSpellErrorDescription); + if (bSpellErrorDescription) + { + OUString sWrong(aSpellErrorDescription.sErrorText); + //if the word has not been edited in the MultiLineEdit then + //the current suggestion should be used + //if it's not the 'no suggestions' entry + if(sWrong == sCurrentErrorText && + m_xSuggestionLB->get_sensitive() && m_xSuggestionLB->get_selected_index() != -1 && + m_sNoSuggestionsST != m_xSuggestionLB->get_selected_text()) + { + sCurrentErrorText = m_xSuggestionLB->get_selected_text(); + } + if(sWrong != sCurrentErrorText) + { + SvxPrepareAutoCorrect( sWrong, sCurrentErrorText ); + LanguageType eLang = GetSelectedLang_Impl(); + rParent.AddAutoCorrection( sWrong, sCurrentErrorText, eLang ); + //correct the word immediately + ChangeHdl(*m_xAutoCorrPB); + } + } + } +} + +IMPL_LINK_NOARG(SpellDialog, CheckGrammarHdl, weld::Toggleable&, void) +{ + rParent.SetGrammarChecking(m_xCheckGrammarCB->get_active()); + Impl_Restore(true); +} + +void SpellDialog::StartSpellOptDlg_Impl() +{ + SfxItemSetFixed<SID_AUTOSPELL_CHECK,SID_AUTOSPELL_CHECK> aSet( SfxGetpApp()->GetPool() ); + SfxSingleTabDialogController aDlg(m_xDialog.get(), &aSet, "cui/ui/spelloptionsdialog.ui", "SpellOptionsDialog"); + + std::unique_ptr<SfxTabPage> xPage = SvxLinguTabPage::Create(aDlg.get_content_area(), &aDlg, &aSet); + static_cast<SvxLinguTabPage*>(xPage.get())->HideGroups( GROUP_MODULES ); + aDlg.SetTabPage(std::move(xPage)); + if (RET_OK == aDlg.run()) + { + InitUserDicts(); + const SfxItemSet* pOutSet = aDlg.GetOutputItemSet(); + if(pOutSet) + OfaTreeOptionsDialog::ApplyLanguageOptions(*pOutSet); + } +} + +namespace +{ + OUString getDotReplacementString(const OUString &rErrorText, const OUString &rSuggestedReplacement) + { + OUString aString = rErrorText; + + //dots are sometimes part of the spelled word but they are not necessarily part of the replacement + bool bDot = aString.endsWith("."); + + aString = rSuggestedReplacement; + + if(bDot && (aString.isEmpty() || !aString.endsWith("."))) + aString += "."; + + return aString; + } +} + +OUString SpellDialog::getReplacementString() const +{ + OUString sOrigString = m_xSentenceED->GetErrorText(); + + OUString sReplacement(sOrigString); + + if(m_xSuggestionLB->get_sensitive() && + m_xSuggestionLB->get_selected_index() != -1 && + m_sNoSuggestionsST != m_xSuggestionLB->get_selected_text()) + sReplacement = m_xSuggestionLB->get_selected_text(); + + return getDotReplacementString(sOrigString, sReplacement); +} + +IMPL_LINK_NOARG(SpellDialog, DoubleClickChangeHdl, weld::TreeView&, bool) +{ + ChangeHdl(*m_xChangePB); + return true; +} + +/* tdf#132822 start an undo group in ctor and close it in the dtor. This can + then be passed to SpellContinue_Impl which can delete it in advance of its + natural scope to force closing the undo group if SpellContinue_Impl needs to + fetch a new paragraph and discard all undo information which can only be + done properly if there are no open undo groups */ +class UndoChangeGroupGuard +{ +private: + SentenceEditWindow_Impl& m_rSentenceED; +public: + UndoChangeGroupGuard(SentenceEditWindow_Impl& rSentenceED) + : m_rSentenceED(rSentenceED) + { + m_rSentenceED.UndoActionStart(SPELLUNDO_CHANGE_GROUP); + } + ~UndoChangeGroupGuard() + { + m_rSentenceED.UndoActionEnd(); + } +}; + +IMPL_LINK_NOARG(SpellDialog, ChangeHdl, weld::Button&, void) +{ + if (m_xSentenceED->IsUndoEditMode()) + { + SpellContinue_Impl(); + } + else + { + auto xGuard(std::make_unique<UndoChangeGroupGuard>(*m_xSentenceED)); + OUString aString = getReplacementString(); + m_xSentenceED->ChangeMarkedWord(aString, GetSelectedLang_Impl()); + SpellContinue_Impl(&xGuard); + } + if(!m_xChangePB->get_sensitive()) + m_xIgnorePB->grab_focus(); +} + +IMPL_LINK_NOARG(SpellDialog, ChangeAllHdl, weld::Button&, void) +{ + auto xGuard(std::make_unique<UndoChangeGroupGuard>(*m_xSentenceED)); + OUString aString = getReplacementString(); + LanguageType eLang = GetSelectedLang_Impl(); + + // add new word to ChangeAll list + OUString aOldWord( m_xSentenceED->GetErrorText() ); + SvxPrepareAutoCorrect( aOldWord, aString ); + Reference<XDictionary> aXDictionary = LinguMgr::GetChangeAllList(); + DictionaryError nAdded = AddEntryToDic( aXDictionary, + aOldWord, true, + aString ); + + if(nAdded == DictionaryError::NONE) + { + std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl( + SPELLUNDO_CHANGE_ADD_TO_DICTIONARY, aDialogUndoLink)); + pAction->SetDictionary(aXDictionary); + pAction->SetAddedWord(aOldWord); + m_xSentenceED->AddUndoAction(std::move(pAction)); + } + + m_xSentenceED->ChangeMarkedWord(aString, eLang); + SpellContinue_Impl(&xGuard); +} + +IMPL_LINK( SpellDialog, IgnoreAllHdl, weld::Button&, rButton, void ) +{ + auto xGuard(std::make_unique<UndoChangeGroupGuard>(*m_xSentenceED)); + // add word to IgnoreAll list + Reference< XDictionary > aXDictionary = LinguMgr::GetIgnoreAllList(); + //in case the error has been changed manually it has to be restored + m_xSentenceED->RestoreCurrentError(); + if (&rButton == m_xIgnoreRulePB.get()) + { + SpellErrorDescription aSpellErrorDescription; + bool bSpellErrorDescription = m_xSentenceED->GetAlternatives(aSpellErrorDescription); + try + { + if( bSpellErrorDescription && aSpellErrorDescription.xGrammarChecker.is() ) + { + aSpellErrorDescription.xGrammarChecker->ignoreRule(aSpellErrorDescription.sRuleId, + aSpellErrorDescription.aLocale); + // refresh the layout (workaround to launch a dictionary event) + aXDictionary->setActive(false); + aXDictionary->setActive(true); + } + } + catch( const uno::Exception& ) + { + } + } + else + { + OUString sErrorText(m_xSentenceED->GetErrorText()); + DictionaryError nAdded = AddEntryToDic( aXDictionary, + sErrorText, false, + OUString() ); + if (nAdded == DictionaryError::NONE) + { + std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl( + SPELLUNDO_CHANGE_ADD_TO_DICTIONARY, aDialogUndoLink)); + pAction->SetDictionary(aXDictionary); + pAction->SetAddedWord(sErrorText); + m_xSentenceED->AddUndoAction(std::move(pAction)); + } + } + + SpellContinue_Impl(&xGuard); +} + +IMPL_LINK_NOARG(SpellDialog, UndoHdl, weld::Button&, void) +{ + m_xSentenceED->Undo(); + if(!m_xSentenceED->GetUndoActionCount()) + m_xUndoPB->set_sensitive(false); +} + + +IMPL_LINK( SpellDialog, DialogUndoHdl, SpellUndoAction_Impl&, rAction, void ) +{ + switch(rAction.GetId()) + { + case SPELLUNDO_CHANGE_TEXTENGINE: + { + if(rAction.IsEnableChangePB()) + m_xChangePB->set_sensitive(false); + if(rAction.IsEnableChangeAllPB()) + m_xChangeAllPB->set_sensitive(false); + } + break; + case SPELLUNDO_CHANGE_NEXTERROR: + { + m_xSentenceED->MoveErrorMarkTo(static_cast<sal_Int32>(rAction.GetOldErrorStart()), + static_cast<sal_Int32>(rAction.GetOldErrorEnd()), + false); + if(rAction.IsErrorLanguageSelected()) + { + UpdateBoxes_Impl(); + } + } + break; + case SPELLUNDO_CHANGE_ADD_TO_DICTIONARY: + { + if(rAction.GetDictionary().is()) + rAction.GetDictionary()->remove(rAction.GetAddedWord()); + } + break; + case SPELLUNDO_MOVE_ERROREND : + { + if(rAction.GetOffset() != 0) + m_xSentenceED->MoveErrorEnd(rAction.GetOffset()); + } + break; + case SPELLUNDO_UNDO_EDIT_MODE : + { + //refill the dialog with the currently spelled sentence - throw away all changes + SpellContinue_Impl(nullptr, true); + } + break; + case SPELLUNDO_ADD_IGNORE_RULE: + //undo of ignored rules is not supported + break; + } +} + +void SpellDialog::Impl_Restore(bool bUseSavedSentence) +{ + //clear the "ChangeAllList" + LinguMgr::GetChangeAllList()->clear(); + //get a new sentence + m_xSentenceED->SetText(OUString()); + m_xSentenceED->ResetModified(); + //Resolves: fdo#39348 refill the dialog with the currently spelled sentence + SpellContinue_Impl(nullptr, bUseSavedSentence); + m_xIgnorePB->set_label(m_sIgnoreOnceST); +} + +IMPL_LINK_NOARG(SpellDialog, IgnoreHdl, weld::Button&, void) +{ + if (m_sResumeST == m_xIgnorePB->get_label()) + { + Impl_Restore(false); + } + else + { + //in case the error has been changed manually it has to be restored, + // since the users choice now was to ignore the error + m_xSentenceED->RestoreCurrentError(); + + // the word is being ignored + SpellContinue_Impl(nullptr, false, true); + } +} + +void SpellDialog::Close() +{ + if (IsClosing()) + return; + + // We have to call ToggleChildWindow directly; calling SfxDispatcher's + // Execute() does not work here when we are in a document with protected + // section - in that case, the cursor can move from the editable field to + // the protected area, and the slots get disabled because of + // SfxDisableFlags::SwOnProtectedCursor (see FN_SPELL_GRAMMAR_DIALOG in .sdi). + SfxViewFrame* pViewFrame = SfxViewFrame::Current(); + if (pViewFrame) + pViewFrame->ToggleChildWindow(rParent.GetType()); +} + +LanguageType SpellDialog::GetSelectedLang_Impl() const +{ + LanguageType nLang = m_xLanguageLB->get_active_id(); + return nLang; +} + +IMPL_LINK_NOARG(SpellDialog, LanguageSelectHdl, weld::ComboBox&, void) +{ + //If selected language changes, then add->list should be regenerated to + //match + InitUserDicts(); + + //if currently an error is selected then search for alternatives for + //this word and fill the alternatives ListBox accordingly + OUString sError = m_xSentenceED->GetErrorText(); + m_xSuggestionLB->clear(); + if (!sError.isEmpty()) + { + LanguageType eLanguage = m_xLanguageLB->get_active_id(); + Reference <XSpellAlternatives> xAlt = xSpell->spell( sError, static_cast<sal_uInt16>(eLanguage), + Sequence< PropertyValue >() ); + if( xAlt.is() ) + m_xSentenceED->SetAlternatives( xAlt ); + else + { + m_xSentenceED->ChangeMarkedWord( sError, eLanguage ); + SpellContinue_Impl(); + } + + m_xSentenceED->AddUndoAction(std::make_unique<SpellUndoAction_Impl>(SPELLUNDO_CHANGE_LANGUAGE, aDialogUndoLink)); + } + SpellDialog::UpdateBoxes_Impl(true); +} + +void SpellDialog::SetTitle_Impl(LanguageType nLang) +{ + OUString sTitle = rParent.HasGrammarChecking() ? m_sTitleSpellingGrammar : m_sTitleSpelling; + sTitle = sTitle.replaceFirst( "$LANGUAGE ($LOCATION)", SvtLanguageTable::GetLanguageString(nLang) ); + m_xDialog->set_title(m_xDialog->strip_mnemonic(sTitle)); +} + +int SpellDialog::InitUserDicts() +{ + const LanguageType nLang = m_xLanguageLB->get_active_id(); + + const Reference< XDictionary > *pDic = nullptr; + + // get list of dictionaries + Reference< XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() ); + if (xDicList.is()) + { + // add active, positive dictionary to dic-list (if not already done). + // This is to ensure that there is at least on dictionary to which + // words could be added. + Reference< XDictionary > xDic( LinguMgr::GetStandardDic() ); + if (xDic.is()) + xDic->setActive( true ); + + pImpl->aDics = xDicList->getDictionaries(); + } + + SvtLinguConfig aCfg; + + // list suitable dictionaries + bool bEnable = false; + const sal_Int32 nSize = pImpl->aDics.getLength(); + pDic = pImpl->aDics.getConstArray(); + m_xAddToDictMB->clear(); + sal_uInt16 nItemId = 1; // menu items should be enumerated from 1 and not 0 + for (sal_Int32 i = 0; i < nSize; ++i) + { + uno::Reference< linguistic2::XDictionary > xDicTmp = pDic[i]; + if (!xDicTmp.is() || LinguMgr::GetIgnoreAllList() == xDicTmp) + continue; + + uno::Reference< frame::XStorable > xStor( xDicTmp, uno::UNO_QUERY ); + LanguageType nActLanguage = LanguageTag( xDicTmp->getLocale() ).getLanguageType(); + if( xDicTmp->isActive() + && xDicTmp->getDictionaryType() != linguistic2::DictionaryType_NEGATIVE + && (nLang == nActLanguage || LANGUAGE_NONE == nActLanguage ) + && (!xStor.is() || !xStor->isReadonly()) ) + { + bEnable = true; + + OUString aDictionaryImageUrl; + uno::Reference< lang::XServiceInfo > xSvcInfo( xDicTmp, uno::UNO_QUERY ); + if (xSvcInfo.is()) + { + aDictionaryImageUrl = aCfg.GetSpellAndGrammarContextDictionaryImage( + xSvcInfo->getImplementationName()); + } + + m_xAddToDictMB->append_item(OUString::number(nItemId), xDicTmp->getName(), aDictionaryImageUrl); + + ++nItemId; + } + } + m_xAddToDictMB->set_sensitive( bEnable ); + m_xAddToDictPB->set_sensitive( bEnable ); + + int nDicts = nItemId-1; + + m_xAddToDictMB->set_visible(nDicts > 1 && !comphelper::LibreOfficeKit::isActive()); + m_xAddToDictPB->set_visible(nDicts <= 1 && !comphelper::LibreOfficeKit::isActive()); + + return nDicts; +} + +IMPL_LINK_NOARG(SpellDialog, AddToDictClickHdl, weld::Button&, void) +{ + AddToDictionaryExecute(OString::number(1)); +} + +IMPL_LINK(SpellDialog, AddToDictSelectHdl, const OString&, rIdent, void) +{ + AddToDictionaryExecute(rIdent); +} + +void SpellDialog::AddToDictionaryExecute(const OString& rItemId) +{ + auto xGuard(std::make_unique<UndoChangeGroupGuard>(*m_xSentenceED)); + + //GetErrorText() returns the current error even if the text is already + //manually changed + const OUString aNewWord = m_xSentenceED->GetErrorText(); + + OUString aDicName(m_xAddToDictMB->get_item_label(rItemId)); + + uno::Reference< linguistic2::XDictionary > xDic; + uno::Reference< linguistic2::XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() ); + if (xDicList.is()) + xDic = xDicList->getDictionaryByName( aDicName ); + + DictionaryError nAddRes = DictionaryError::UNKNOWN; + if (xDic.is()) + { + nAddRes = AddEntryToDic( xDic, aNewWord, false, OUString() ); + // save modified user-dictionary if it is persistent + uno::Reference< frame::XStorable > xSavDic( xDic, uno::UNO_QUERY ); + if (xSavDic.is()) + xSavDic->store(); + + if (nAddRes == DictionaryError::NONE) + { + std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl( + SPELLUNDO_CHANGE_ADD_TO_DICTIONARY, aDialogUndoLink)); + pAction->SetDictionary( xDic ); + pAction->SetAddedWord( aNewWord ); + m_xSentenceED->AddUndoAction( std::move(pAction) ); + } + // failed because there is already an entry? + if (DictionaryError::NONE != nAddRes && xDic->getEntry( aNewWord ).is()) + nAddRes = DictionaryError::NONE; + } + if (DictionaryError::NONE != nAddRes) + { + SvxDicError(m_xDialog.get(), nAddRes); + return; // don't continue + } + + // go on + SpellContinue_Impl(&xGuard); +} + +IMPL_LINK_NOARG(SpellDialog, ModifyHdl, LinkParamNone*, void) +{ + m_xSuggestionLB->unselect_all(); + m_xSuggestionLB->set_sensitive(false); + m_xAutoCorrPB->set_sensitive(false); + std::unique_ptr<SpellUndoAction_Impl> pSpellAction(new SpellUndoAction_Impl(SPELLUNDO_CHANGE_TEXTENGINE, aDialogUndoLink)); + if(!m_xChangeAllPB->get_sensitive()) + { + m_xChangeAllPB->set_sensitive(true); + pSpellAction->SetEnableChangeAllPB(); + } + if(!m_xChangePB->get_sensitive()) + { + m_xChangePB->set_sensitive(true); + pSpellAction->SetEnableChangePB(); + } + m_xSentenceED->AddUndoAction(std::move(pSpellAction)); +} + +IMPL_LINK_NOARG(SpellDialog, CancelHdl, weld::Button&, void) +{ + //apply changes and ignored text parts first - if there are any + if (m_xSentenceED->IsModified()) + { + rParent.ApplyChangedSentence(m_xSentenceED->CreateSpellPortions(), false); + } + Close(); +} + +void SpellDialog::ToplevelFocusChanged() +{ + /* #i38338# + * FIXME: LoseFocus and GetFocus are signals from vcl that + * a window actually got/lost the focus, it never should be + * forwarded from another window, that is simply wrong. + * FIXME: overriding the virtual methods GetFocus and LoseFocus + * in SpellDialogChildWindow by making them pure is at least questionable. + * The only sensible thing would be to call the new Method differently, + * e.g. DialogGot/LostFocus or so. + */ + if (!m_xDialog->get_visible() || bFocusLocked) + return; + + if (m_xDialog->has_toplevel_focus()) + { + //notify the child window of the focus change + rParent.GetFocus(); + } + else + { + //notify the child window of the focus change + rParent.LoseFocus(); + } +} + +void SpellDialog::Activate() +{ + SfxModelessDialogController::Activate(); + ToplevelFocusChanged(); +} + +void SpellDialog::Deactivate() +{ + SfxModelessDialogController::Deactivate(); + ToplevelFocusChanged(); +} + +void SpellDialog::InvalidateDialog() +{ + if( bFocusLocked ) + return; + m_xIgnorePB->set_label(m_sResumeST); + weld::Widget* aDisableArr[] = + { + m_xNotInDictFT.get(), + m_xSentenceED->GetDrawingArea(), + m_xSuggestionFT.get(), + m_xSuggestionLB.get(), + m_xLanguageFT.get(), + m_xLanguageLB->get_widget(), + m_xIgnoreAllPB.get(), + m_xIgnoreRulePB.get(), + m_xAddToDictMB.get(), + m_xAddToDictPB.get(), + m_xChangePB.get(), + m_xChangeAllPB.get(), + m_xAutoCorrPB.get(), + m_xUndoPB.get() + }; + for (weld::Widget* pWidget : aDisableArr) + pWidget->set_sensitive(false); + + SfxModelessDialogController::Deactivate(); +} + +bool SpellDialog::GetNextSentence_Impl(std::unique_ptr<UndoChangeGroupGuard>* pGuard, bool bUseSavedSentence, bool bRecheck) +{ + bool bRet = false; + if(!bUseSavedSentence) + { + //apply changes and ignored text parts + rParent.ApplyChangedSentence(m_xSentenceED->CreateSpellPortions(), bRecheck); + } + m_xSentenceED->ResetIgnoreErrorsAt(); + m_xSentenceED->ResetModified(); + SpellPortions aSentence = bUseSavedSentence ? m_aSavedSentence : rParent.GetNextWrongSentence( bRecheck ); + if(!bUseSavedSentence) + m_aSavedSentence = aSentence; + bool bHasReplaced = false; + while(!aSentence.empty()) + { + //apply all changes that are already part of the "ChangeAllList" + //returns true if the list still contains errors after the changes have been applied + + if(!ApplyChangeAllList_Impl(aSentence, bHasReplaced)) + { + rParent.ApplyChangedSentence(aSentence, bRecheck); + aSentence = rParent.GetNextWrongSentence( bRecheck ); + } + else + break; + } + + if(!aSentence.empty()) + { + OUStringBuffer sText; + for (auto const& elem : aSentence) + { + // hidden text has to be ignored + if(!elem.bIsHidden) + sText.append(elem.sText); + } + // tdf#132822 fire undo-stack UndoActionEnd to close undo stack because we're about to throw away the paragraph entirely + if (pGuard) + pGuard->reset(); + m_xSentenceED->SetText(sText.makeStringAndClear()); + sal_Int32 nStartPosition = 0; + sal_Int32 nEndPosition = 0; + + for (auto const& elem : aSentence) + { + // hidden text has to be ignored + if(!elem.bIsHidden) + { + nEndPosition += elem.sText.getLength(); + if(elem.xAlternatives.is()) + { + SpellErrorDescription aDesc( false, elem.xAlternatives->getWord(), + elem.xAlternatives->getLocale(), elem.xAlternatives->getAlternatives(), nullptr); + SfxGrabBagItem aSpellErrorDescription(EE_CHAR_GRABBAG); + aSpellErrorDescription.GetGrabBag()["SpellErrorDescription"] <<= aDesc.toSequence(); + m_xSentenceED->SetAttrib(aSpellErrorDescription, nStartPosition, nEndPosition); + } + else if(elem.bIsGrammarError ) + { + beans::PropertyValues aProperties = elem.aGrammarError.aProperties; + OUString sFullCommentURL; + sal_Int32 i = 0; + while ( sFullCommentURL.isEmpty() && i < aProperties.getLength() ) + { + if ( aProperties[i].Name == "FullCommentURL" ) + { + uno::Any aValue = aProperties[i].Value; + aValue >>= sFullCommentURL; + } + ++i; + } + + SpellErrorDescription aDesc( true, + elem.sText, + LanguageTag::convertToLocale( elem.eLanguage ), + elem.aGrammarError.aSuggestions, + elem.xGrammarChecker, + &elem.sDialogTitle, + &elem.aGrammarError.aFullComment, + &elem.aGrammarError.aRuleIdentifier, + &sFullCommentURL ); + + SfxGrabBagItem aSpellErrorDescriptionItem(EE_CHAR_GRABBAG); + aSpellErrorDescriptionItem.GetGrabBag()["SpellErrorDescription"] <<= aDesc.toSequence(); + m_xSentenceED->SetAttrib(aSpellErrorDescriptionItem, nStartPosition, nEndPosition); + } + + if (elem.bIsField) + m_xSentenceED->SetAttrib(SvxColorItem(COL_LIGHTGRAY, EE_CHAR_BKGCOLOR), nStartPosition, nEndPosition); + m_xSentenceED->SetAttrib(SvxLanguageItem(elem.eLanguage, EE_CHAR_LANGUAGE), nStartPosition, nEndPosition); + nStartPosition = nEndPosition; + } + } + //the edit field needs to be modified to apply the change from the ApplyChangeAllList + if(!bHasReplaced) + m_xSentenceED->ClearModifyFlag(); + m_xSentenceED->ResetUndo(); + m_xUndoPB->set_sensitive(false); + bRet = nStartPosition > 0; + } + return bRet; +} +/*------------------------------------------------------------------------- + replace errors that have a replacement in the ChangeAllList + returns false if the result doesn't contain errors after the replacement + -----------------------------------------------------------------------*/ +bool SpellDialog::ApplyChangeAllList_Impl(SpellPortions& rSentence, bool &bHasReplaced) +{ + bHasReplaced = false; + bool bRet = true; + Reference<XDictionary> xChangeAll = LinguMgr::GetChangeAllList(); + if(!xChangeAll->getCount()) + return bRet; + bRet = false; + for (auto & elem : rSentence) + { + if(elem.xAlternatives.is()) + { + const OUString &rString = elem.sText; + + Reference<XDictionaryEntry> xEntry = xChangeAll->getEntry(rString); + + if(xEntry.is()) + { + elem.sText = getDotReplacementString(rString, xEntry->getReplacementText()); + elem.xAlternatives = nullptr; + bHasReplaced = true; + } + else + bRet = true; + } + else if( elem.bIsGrammarError ) + bRet = true; + } + return bRet; +} + +SentenceEditWindow_Impl::SentenceEditWindow_Impl() + : m_pSpellDialog(nullptr) + , m_pToolbar(nullptr) + , m_nErrorStart(0) + , m_nErrorEnd(0) + , m_bIsUndoEditMode(false) +{ +} + +void SentenceEditWindow_Impl::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + Size aSize(pDrawingArea->get_approximate_digit_width() * 60, + pDrawingArea->get_text_height() * 6); + pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); + WeldEditView::SetDrawingArea(pDrawingArea); + // tdf#132288 don't merge equal adjacent attributes + m_xEditEngine->DisableAttributeExpanding(); + + // tdf#142631 use document background color in this widget + Color aBgColor = svtools::ColorConfig().GetColorValue(svtools::DOCCOLOR).nColor; + OutputDevice& rDevice = pDrawingArea->get_ref_device(); + rDevice.SetBackground(aBgColor); + m_xEditView->SetBackgroundColor(aBgColor); + m_xEditEngine->SetBackgroundColor(aBgColor); +} + +SentenceEditWindow_Impl::~SentenceEditWindow_Impl() +{ +} + +namespace +{ + const EECharAttrib* FindCharAttrib(int nPosition, sal_uInt16 nWhich, std::vector<EECharAttrib>& rAttribList) + { + for (auto it = rAttribList.rbegin(); it != rAttribList.rend(); ++it) + { + const auto& rTextAtr = *it; + if (rTextAtr.pAttr->Which() != nWhich) + continue; + if (rTextAtr.nStart <= nPosition && rTextAtr.nEnd >= nPosition) + { + return &rTextAtr; + } + } + + return nullptr; + } + + void ExtractErrorDescription(const EECharAttrib& rEECharAttrib, SpellErrorDescription& rSpellErrorDescription) + { + css::uno::Sequence<css::uno::Any> aSequence; + static_cast<const SfxGrabBagItem*>(rEECharAttrib.pAttr)->GetGrabBag().find("SpellErrorDescription")->second >>= aSequence; + rSpellErrorDescription.fromSequence(aSequence); + } +} + +/*------------------------------------------------------------------------- + The selection before inputting a key may have a range or not + and it may be inside or outside of field or error attributes. + A range may include the attribute partially, completely or together + with surrounding text. It may also contain more than one attribute + or no attribute at all. + Depending on this starting conditions some actions are necessary: + Attempts to delete a field are only allowed if the selection is the same + as the field's selection. Otherwise the field has to be selected and the key + input action has to be skipped. + Input of text at the start of the field requires the field attribute to be + corrected - it is not allowed to grow. + + In case of errors the appending of text should grow the error attribute because + that is what the user usually wants to do. + + Backspace at the start of the attribute requires to find out if a field ends + directly in front of the cursor position. In case of a field this attribute has to be + selected otherwise the key input method is allowed. + + All changes outside of the error attributes switch the dialog mode to a "Undo edit" state that + removes all visible attributes and switches off further attribute checks. + Undo in this restarts the dialog with a current sentence newly presented. + All changes to the sentence are undone including the ones before the "Undo edit state" has been reached + + We end up with 9 types of selection + 1 (LEFT_NO) - no range, start of attribute - can also be 3 at the same time + 2 (INSIDE_NO) - no range, inside of attribute + 3 (RIGHT_NO) - no range, end of attribute - can also be 1 at the same time + 4 (FULL) - range, same as attribute + 5 (INSIDE_YES) - range, inside of the attribute + 6 (BRACE)- range, from outside of the attribute to the inside or + including the complete attribute and something outside, + maybe more than one attribute + 7 (OUTSIDE_NO) - no range, not at an attribute + 8 (OUTSIDE_YES) - range, completely outside of all attributes + + What has to be done depending on the attribute type involved + possible actions: UE - Undo edit mode + CO - Continue, no additional action is required + FS - Field has to be completely selected + EX - The attribute has to be expanded to include the added text + + 1 - backspace delete any other + UE on field FS on error CO on field FS on error CO + + 2 - on field FS on error C + 3 - backspace delete any other + on field FS on error CO UE on field UE on error EX + + if 1 and 3 happen to apply both then backspace and other handling is 1 delete is 3 + + 4 - on field UE and on error CO + 5 - on field FS and on error CO + 6 - on field FS and on error UE + 7 - UE + 8 - UE + -----------------------------------------------------------------------*/ +#define INVALID 0 +#define LEFT_NO 1 +#define INSIDE_NO 2 +#define RIGHT_NO 3 +#define FULL 4 +#define INSIDE_YES 5 +#define BRACE 6 +#define OUTSIDE_NO 7 +#define OUTSIDE_YES 8 + +#define ACTION_UNDOEDIT 0 +#define ACTION_CONTINUE 1 +#define ACTION_SELECTFIELD 2 +#define ACTION_EXPAND 3 + +bool SentenceEditWindow_Impl::KeyInput(const KeyEvent& rKeyEvt) +{ + if (rKeyEvt.GetKeyCode().GetCode() == KEY_TAB) + return false; + + bool bConsumed = false; + + bool bChange = TextEngine::DoesKeyChangeText( rKeyEvt ); + if (bChange && !IsUndoEditMode()) + { + bConsumed = true; + + ESelection aCurrentSelection(m_xEditView->GetSelection()); + aCurrentSelection.Adjust(); + + //determine if the selection contains a field + bool bHasFieldLeft = false; + bool bHasErrorLeft = false; + + bool bHasRange = aCurrentSelection.HasRange(); + sal_uInt8 nSelectionType = 0; // invalid type! + + std::vector<EECharAttrib> aAttribList; + m_xEditEngine->GetCharAttribs(0, aAttribList); + + auto nCursor = aCurrentSelection.nStartPos; + const EECharAttrib* pBackAttr = FindCharAttrib(nCursor, EE_CHAR_BKGCOLOR, aAttribList); + const EECharAttrib* pErrorAttr = FindCharAttrib(nCursor, EE_CHAR_GRABBAG, aAttribList); + const EECharAttrib* pBackAttrLeft = nullptr; + const EECharAttrib* pErrorAttrLeft = nullptr; + + bool bHasField = pBackAttr != nullptr && (bHasRange || pBackAttr->nEnd > nCursor); + bool bHasError = pErrorAttr != nullptr && (bHasRange || pErrorAttr->nEnd > nCursor); + if (bHasRange) + { + if (pBackAttr && + pBackAttr->nStart == aCurrentSelection.nStartPos && + pBackAttr->nEnd == aCurrentSelection.nEndPos) + { + nSelectionType = FULL; + } + else if (pErrorAttr && + pErrorAttr->nStart <= aCurrentSelection.nStartPos && + pErrorAttr->nEnd >= aCurrentSelection.nEndPos) + { + nSelectionType = INSIDE_YES; + } + else + { + nSelectionType = bHasField||bHasError ? BRACE : OUTSIDE_NO; + while (nCursor < aCurrentSelection.nEndPos) + { + ++nCursor; + const EECharAttrib* pIntBackAttr = FindCharAttrib(nCursor, EE_CHAR_BKGCOLOR, aAttribList); + const EECharAttrib* pIntErrorAttr = FindCharAttrib(nCursor, EE_CHAR_GRABBAG, aAttribList); + //if any attr has been found then BRACE + if (pIntBackAttr || pIntErrorAttr) + nSelectionType = BRACE; + //the field has to be selected + if (pIntBackAttr && !pBackAttr) + pBackAttr = pIntBackAttr; + bHasField |= pIntBackAttr != nullptr; + } + } + } + else + { + //no range selection: then 1 2 3 and 8 are possible + const EECharAttrib* pCurAttr = pBackAttr ? pBackAttr : pErrorAttr; + if (pCurAttr) + { + nSelectionType = pCurAttr->nStart == aCurrentSelection.nStartPos ? + LEFT_NO : pCurAttr->nEnd == aCurrentSelection.nEndPos ? RIGHT_NO : INSIDE_NO; + } + else + nSelectionType = OUTSIDE_NO; + + bHasFieldLeft = pBackAttr && pBackAttr->nEnd == nCursor; + if(bHasFieldLeft) + { + pBackAttrLeft = pBackAttr; + pBackAttr = nullptr; + } + bHasErrorLeft = pErrorAttr && pErrorAttr->nEnd == nCursor; + if(bHasErrorLeft) + { + pErrorAttrLeft = pErrorAttr; + pErrorAttr = nullptr; + } + + //check previous position if this exists + //that is a redundant in the case the attribute found above already is on the left cursor side + //but it's o.k. for two errors/fields side by side + if (nCursor) + { + --nCursor; + pBackAttrLeft = FindCharAttrib(nCursor, EE_CHAR_BKGCOLOR, aAttribList); + pErrorAttrLeft = FindCharAttrib(nCursor, EE_CHAR_GRABBAG, aAttribList); + bHasFieldLeft = pBackAttrLeft !=nullptr; + bHasErrorLeft = pErrorAttrLeft != nullptr; + ++nCursor; + } + } + //Here we have to determine if the error found is the one currently active + bool bIsErrorActive = (pErrorAttr && pErrorAttr->nStart == m_nErrorStart) || + (pErrorAttrLeft && pErrorAttrLeft->nStart == m_nErrorStart); + + SAL_WARN_IF( + nSelectionType == INVALID, "cui.dialogs", + "selection type not set"); + + const vcl::KeyCode& rKeyCode = rKeyEvt.GetKeyCode(); + bool bDelete = rKeyCode.GetCode() == KEY_DELETE; + bool bBackspace = rKeyCode.GetCode() == KEY_BACKSPACE; + + sal_Int8 nAction = ACTION_CONTINUE; + switch(nSelectionType) + { +// 1 - backspace delete any other +// UE on field FS on error CO on field FS on error CO + case LEFT_NO : + if(bBackspace) + { + nAction = bHasFieldLeft ? ACTION_SELECTFIELD : ACTION_UNDOEDIT; + //to force the use of pBackAttrLeft + pBackAttr = nullptr; + } + else if(bDelete) + nAction = bHasField ? ACTION_SELECTFIELD : ACTION_CONTINUE; + else + nAction = bHasError && !nCursor ? ACTION_CONTINUE : + bHasError ? ACTION_EXPAND : bHasErrorLeft ? ACTION_CONTINUE : ACTION_UNDOEDIT; + break; +// 2 - on field FS on error C + case INSIDE_NO : + nAction = bHasField ? ACTION_SELECTFIELD : + bIsErrorActive ? ACTION_CONTINUE : ACTION_UNDOEDIT; + break; +// 3 - backspace delete any other +// on field FS on error CO UE on field UE on error EX + case RIGHT_NO : + if(bBackspace) + nAction = bHasFieldLeft ? ACTION_SELECTFIELD : ACTION_CONTINUE; + else if(bDelete) + nAction = bHasFieldLeft && bHasError ? ACTION_CONTINUE : ACTION_UNDOEDIT; + else + nAction = bHasFieldLeft && bHasError ? ACTION_EXPAND : + bHasError ? ACTION_CONTINUE : bHasErrorLeft ? ACTION_EXPAND :ACTION_UNDOEDIT; + break; +// 4 - on field UE and on error CO + case FULL : + nAction = ACTION_UNDOEDIT; + break; +// 5 - on field FS and on error CO + case INSIDE_YES : + nAction = bHasField ? ACTION_SELECTFIELD : ACTION_CONTINUE; + break; +// 6 - on field FS and on error UE + case BRACE : + nAction = bHasField ? ACTION_SELECTFIELD : ACTION_UNDOEDIT; + break; +// 7 - UE +// 8 - UE + case OUTSIDE_NO : + case OUTSIDE_YES: + nAction = ACTION_UNDOEDIT; + break; + } + //save the current paragraph + sal_Int32 nCurrentLen = m_xEditEngine->GetText().getLength(); + if (nAction != ACTION_SELECTFIELD) + { + m_xEditView->PostKeyEvent(rKeyEvt); + } + else + { + const EECharAttrib* pCharAttr = pBackAttr ? pBackAttr : pBackAttrLeft; + if (pCharAttr) + m_xEditView->SetSelection(ESelection(0, pCharAttr->nStart, 0, pCharAttr->nEnd)); + } + if(nAction == ACTION_EXPAND) + { + DBG_ASSERT(pErrorAttrLeft || pErrorAttr, "where is the error"); + //text has been added on the right and only the 'error attribute has to be corrected + if (pErrorAttrLeft) + { + SpellErrorDescription aSpellErrorDescription; + ExtractErrorDescription(*pErrorAttrLeft, aSpellErrorDescription); + + std::unique_ptr<SfxPoolItem> xNewError(pErrorAttrLeft->pAttr->Clone()); + sal_Int32 nStart = pErrorAttrLeft->nStart; + sal_Int32 nEnd = pErrorAttrLeft->nEnd + 1; + m_xEditEngine->RemoveAttribs(ESelection(0, nStart, 0, nEnd), false, EE_CHAR_GRABBAG); + SetAttrib(*xNewError, nStart, nEnd); + //only active errors move the mark + if (bIsErrorActive) + { + bool bGrammar = aSpellErrorDescription.bIsGrammarError; + MoveErrorMarkTo(nStart, nEnd, bGrammar); + } + } + //text has been added on the left then the error attribute has to be expanded and the + //field attribute on the right - if any - has to be contracted + else if (pErrorAttr) + { + SpellErrorDescription aSpellErrorDescription; + ExtractErrorDescription(*pErrorAttr, aSpellErrorDescription); + + //determine the change + sal_Int32 nAddedChars = m_xEditEngine->GetText().getLength() - nCurrentLen; + + std::unique_ptr<SfxPoolItem> xNewError(pErrorAttr->pAttr->Clone()); + sal_Int32 nStart = pErrorAttr->nStart + nAddedChars; + sal_Int32 nEnd = pErrorAttr->nEnd + nAddedChars; + m_xEditEngine->RemoveAttribs(ESelection(0, nStart, 0, nEnd), false, EE_CHAR_GRABBAG); + nStart = pErrorAttr->nStart; + SetAttrib(*xNewError, nStart, nEnd); + //only if the error is active the mark is moved here + if (bIsErrorActive) + { + bool bGrammar = aSpellErrorDescription.bIsGrammarError; + MoveErrorMarkTo(nStart, nEnd, bGrammar); + } + xNewError.reset(); + + if (pBackAttrLeft) + { + std::unique_ptr<SfxPoolItem> xNewBack(pBackAttrLeft->pAttr->Clone()); + sal_Int32 _nStart = pBackAttrLeft->nStart + nAddedChars; + sal_Int32 _nEnd = pBackAttrLeft->nEnd + nAddedChars; + m_xEditEngine->RemoveAttribs(ESelection(0, _nStart, 0, _nEnd), false, EE_CHAR_BKGCOLOR); + _nStart = pBackAttrLeft->nStart; + SetAttrib(*xNewBack, _nStart, _nEnd); + } + } + } + else if(nAction == ACTION_UNDOEDIT) + { + SetUndoEditMode(true); + } + //make sure the error positions are correct after text changes + //the old attribute may have been deleted + //all changes inside of the current error leave the error attribute at the current + //start position + if (!IsUndoEditMode() && bIsErrorActive) + { + const EECharAttrib* pFontColor = FindCharAttrib(nCursor, EE_CHAR_COLOR, aAttribList); + const EECharAttrib* pErrorAttrib = FindCharAttrib(m_nErrorStart, EE_CHAR_GRABBAG, aAttribList); + if (pFontColor && pErrorAttrib) + { + m_nErrorStart = pFontColor->nStart; + m_nErrorEnd = pFontColor->nEnd; + if (pErrorAttrib->nStart != m_nErrorStart || pErrorAttrib->nEnd != m_nErrorEnd) + { + std::unique_ptr<SfxPoolItem> xNewError(pErrorAttrib->pAttr->Clone()); + assert(pErrorAttr); + m_xEditEngine->RemoveAttribs(ESelection(0, pErrorAttr->nStart, 0, pErrorAttr->nEnd), false, EE_CHAR_GRABBAG); + SetAttrib(*xNewError, m_nErrorStart, m_nErrorEnd); + } + } + } + //this is not a modification anymore + if(nAction != ACTION_SELECTFIELD && !m_bIsUndoEditMode) + CallModifyLink(); + } + else + bConsumed = m_xEditView->PostKeyEvent(rKeyEvt); + + return bConsumed; +} + +void SentenceEditWindow_Impl::Init(weld::Toolbar* pToolbar) +{ + m_pToolbar = pToolbar; + m_pToolbar->connect_clicked(LINK(this,SentenceEditWindow_Impl,ToolbarHdl)); +} + +IMPL_LINK(SentenceEditWindow_Impl, ToolbarHdl, const OString&, rCurItemId, void) +{ + if (rCurItemId == "paste") + { + m_xEditView->Paste(); + CallModifyLink(); + } + else if (rCurItemId == "insert") + { + if (auto pImplFncGetSpecialChars = vcl::GetGetSpecialCharsFunction()) + { + OUString aChars = pImplFncGetSpecialChars(GetDrawingArea(), m_xEditEngine->GetStandardFont(0)); + if (!aChars.isEmpty()) + { + ESelection aCurrentSelection(m_xEditView->GetSelection()); + m_xEditEngine->QuickInsertText(aChars, aCurrentSelection); + CallModifyLink(); + } + } + } +} + +bool SentenceEditWindow_Impl::MarkNextError( bool bIgnoreCurrentError, const css::uno::Reference<css::linguistic2::XSpellChecker1>& xSpell ) +{ + if (bIgnoreCurrentError) + m_aIgnoreErrorsAt.insert( m_nErrorStart ); + + const sal_Int32 nTextLen = m_xEditEngine->GetTextLen(0); + + if (m_nErrorEnd >= nTextLen - 1) + return false; + //if it's not already modified the modified flag has to be reset at the end of the marking + bool bModified = IsModified(); + bool bRet = false; + const sal_Int32 nOldErrorStart = m_nErrorStart; + const sal_Int32 nOldErrorEnd = m_nErrorEnd; + + //create a cursor behind the end of the last error + //- or at 0 at the start of the sentence + sal_Int32 nCursor(m_nErrorEnd ? m_nErrorEnd + 1 : 0); + + //search for SpellErrorDescription + SpellErrorDescription aSpellErrorDescription; + + std::vector<EECharAttrib> aAttribList; + m_xEditEngine->GetCharAttribs(0, aAttribList); + + //iterate over the text and search for the next error that maybe has + //to be replace by a ChangeAllList replacement + bool bGrammarError = false; + while (nCursor < nTextLen) + { + const SpellErrorDescription* pSpellErrorDescription = nullptr; + const EECharAttrib* pEECharAttrib = nullptr; + + sal_Int32 nMinPos = nTextLen + 1; + for (const auto& rTextAtr : aAttribList) + { + if (rTextAtr.pAttr->Which() != EE_CHAR_GRABBAG) + continue; + if (rTextAtr.nEnd > nCursor && rTextAtr.nStart < nMinPos) + { + nMinPos = rTextAtr.nStart; + pEECharAttrib = &rTextAtr; + } + } + + if (pEECharAttrib) + { + ExtractErrorDescription(*pEECharAttrib, aSpellErrorDescription); + + bGrammarError = aSpellErrorDescription.bIsGrammarError; + m_nErrorStart = pEECharAttrib->nStart; + m_nErrorEnd = pEECharAttrib->nEnd; + + pSpellErrorDescription = &aSpellErrorDescription; + } + + nCursor = std::max(nCursor, nMinPos); // move forward if possible + + // maybe the error found here is already in the ChangeAllList and has to be replaced + Reference<XDictionary> xChangeAll = LinguMgr::GetChangeAllList(); + Reference<XDictionaryEntry> xEntry; + + if (xChangeAll->getCount() && pSpellErrorDescription && + (xEntry = xChangeAll->getEntry( pSpellErrorDescription->sErrorText )).is()) + { + OUString sReplacement(getDotReplacementString(GetErrorText(), xEntry->getReplacementText())); + + int nLenChange = ChangeMarkedWord(sReplacement, LanguageTag::convertToLanguageType(pSpellErrorDescription->aLocale)); + + nCursor += sReplacement.getLength(); + + if (nLenChange) + m_xEditEngine->GetCharAttribs(0, aAttribList); + // maybe the error found here is already added to the dictionary and has to be ignored + } + else if(pSpellErrorDescription && !bGrammarError && + xSpell->isValid(GetErrorText(), + static_cast<sal_uInt16>(LanguageTag::convertToLanguageType( pSpellErrorDescription->aLocale )), + Sequence< PropertyValue >() )) + { + ++nCursor; + } + else + break; + } + + //if an attrib has been found search for the end of the error string + if (nCursor < nTextLen) + { + MoveErrorMarkTo(nCursor, m_nErrorEnd, bGrammarError); + bRet = true; + //add an undo action + std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl( + SPELLUNDO_CHANGE_NEXTERROR, GetSpellDialog()->aDialogUndoLink)); + pAction->SetErrorMove(nOldErrorStart, nOldErrorEnd); + + if (GetErrorDescription(aSpellErrorDescription, nOldErrorStart)) + { + pAction->SetErrorLanguageSelected(aSpellErrorDescription.aSuggestions.hasElements() && + LanguageTag(aSpellErrorDescription.aLocale).getLanguageType() == GetSpellDialog()->m_xLanguageLB->get_active_id()); + } + else + pAction->SetErrorLanguageSelected(false); + + AddUndoAction(std::move(pAction)); + } + else + m_nErrorStart = m_nErrorEnd = nTextLen; + if( !bModified ) + ClearModifyFlag(); + SpellDialog* pSpellDialog = GetSpellDialog(); + pSpellDialog->m_xIgnorePB->set_sensitive(bRet); + pSpellDialog->m_xIgnoreAllPB->set_sensitive(bRet); + pSpellDialog->m_xAutoCorrPB->set_sensitive(bRet); + pSpellDialog->m_xAddToDictMB->set_sensitive(bRet); + pSpellDialog->m_xAddToDictPB->set_sensitive(bRet); + return bRet; +} + +void SentenceEditWindow_Impl::MoveErrorMarkTo(sal_Int32 nStart, sal_Int32 nEnd, bool bGrammarError) +{ + ESelection aAll(0, 0, 0, EE_TEXTPOS_ALL); + m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_COLOR); + m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT); + m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT_CJK); + m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT_CTL); + + // tdf#116566 Use color defined in the current Color Scheme + Color aSpellErrorCollor = svtools::ColorConfig().GetColorValue(svtools::SPELL).nColor; + + // TODO: Create a new Color Scheme entry for grammar mistakes and use it below + // instead of using hardcoded COL_LIGHTBLUE + SfxItemSet aSet(m_xEditEngine->GetEmptyItemSet()); + aSet.Put(SvxColorItem(bGrammarError ? COL_LIGHTBLUE : aSpellErrorCollor, EE_CHAR_COLOR)); + aSet.Put(SvxWeightItem(WEIGHT_BOLD, EE_CHAR_WEIGHT)); + aSet.Put(SvxWeightItem(WEIGHT_BOLD, EE_CHAR_WEIGHT_CJK)); + aSet.Put(SvxWeightItem(WEIGHT_BOLD, EE_CHAR_WEIGHT_CTL)); + + m_xEditEngine->QuickSetAttribs(aSet, ESelection(0, nStart, 0, nEnd)); + + // Set the selection so the editview will autoscroll to make this visible + // unless (tdf#133958) the selection already overlaps this range + ESelection aCurrentSelection = m_xEditView->GetSelection(); + aCurrentSelection.Adjust(); + bool bCurrentSelectionInRange = nStart <= aCurrentSelection.nEndPos && aCurrentSelection.nStartPos <= nEnd; + if (!bCurrentSelectionInRange) + { + m_xEditView->SetSelection(ESelection(0, nStart)); + } + + Invalidate(); + + m_nErrorStart = nStart; + m_nErrorEnd = nEnd; +} + +int SentenceEditWindow_Impl::ChangeMarkedWord(const OUString& rNewWord, LanguageType eLanguage) +{ + std::vector<EECharAttrib> aAttribList; + m_xEditEngine->GetCharAttribs(0, aAttribList); + + //calculate length changes + auto nDiffLen = rNewWord.getLength() - m_nErrorEnd + m_nErrorStart; + //Remove spell error attribute + m_xEditEngine->UndoActionStart(SPELLUNDO_MOVE_ERROREND); + const EECharAttrib* pErrorAttrib = FindCharAttrib(m_nErrorStart, EE_CHAR_GRABBAG, aAttribList); + DBG_ASSERT(pErrorAttrib, "no error attribute found"); + bool bSpellErrorDescription = false; + SpellErrorDescription aSpellErrorDescription; + if (pErrorAttrib) + { + ExtractErrorDescription(*pErrorAttrib, aSpellErrorDescription); + m_xEditEngine->RemoveAttribs(ESelection(0, pErrorAttrib->nStart, 0, pErrorAttrib->nEnd), false, EE_CHAR_GRABBAG); + bSpellErrorDescription = true; + } + + const EECharAttrib* pBackAttrib = FindCharAttrib(m_nErrorStart, EE_CHAR_BKGCOLOR, aAttribList); + + ESelection aSel(0, m_nErrorStart, 0, m_nErrorEnd); + m_xEditEngine->QuickInsertText(rNewWord, aSel); + + const sal_Int32 nTextLen = m_xEditEngine->GetTextLen(0); + + if (nDiffLen) + m_xEditEngine->GetCharAttribs(0, aAttribList); + + if (!m_nErrorStart) + { + //attributes following an error at the start of the text are not moved but expanded from the + //text engine - this is done to keep full-paragraph-attributes + //in the current case that handling is not desired + const EECharAttrib* pLangAttrib = FindCharAttrib(m_nErrorEnd, EE_CHAR_LANGUAGE, aAttribList); + + if (pLangAttrib && !pLangAttrib->nStart && pLangAttrib->nEnd == nTextLen) + { + LanguageType eNewLanguage = static_cast<const SvxLanguageItem*>(pLangAttrib->pAttr)->GetLanguage(); + m_xEditEngine->RemoveAttribs(ESelection(0, pLangAttrib->nStart, 0, pLangAttrib->nEnd), false, EE_CHAR_LANGUAGE); + SetAttrib(SvxLanguageItem(eNewLanguage, EE_CHAR_LANGUAGE), m_nErrorEnd + nDiffLen, nTextLen); + } + } + + // undo expanded attributes! + if (pBackAttrib && pBackAttrib->nStart < m_nErrorStart && pBackAttrib->nEnd == m_nErrorEnd + nDiffLen) + { + std::unique_ptr<SfxPoolItem> xNewBackground(pBackAttrib->pAttr->Clone()); + const sal_Int32 nStart = pBackAttrib->nStart; + + m_xEditEngine->RemoveAttribs(ESelection(0, pBackAttrib->nStart, 0, pBackAttrib->nEnd), false, EE_CHAR_BKGCOLOR); + + SetAttrib(*xNewBackground, nStart, m_nErrorStart); + } + m_xEditEngine->SetModified(); + + //adjust end position + tools::Long nEndTemp = m_nErrorEnd; + nEndTemp += nDiffLen; + m_nErrorEnd = static_cast<sal_Int32>(nEndTemp); + + std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl( + SPELLUNDO_MOVE_ERROREND, GetSpellDialog()->aDialogUndoLink)); + pAction->SetOffset(nDiffLen); + AddUndoAction(std::move(pAction)); + if (bSpellErrorDescription) + { + SfxGrabBagItem aSpellErrorDescriptionItem(EE_CHAR_GRABBAG); + aSpellErrorDescriptionItem.GetGrabBag()["SpellErrorDescription"] <<= aSpellErrorDescription.toSequence(); + SetAttrib(aSpellErrorDescriptionItem, m_nErrorStart, m_nErrorEnd); + } + SetAttrib(SvxLanguageItem(eLanguage, EE_CHAR_LANGUAGE), m_nErrorStart, m_nErrorEnd); + m_xEditEngine->UndoActionEnd(); + + Invalidate(); + + return nDiffLen; +} + +OUString SentenceEditWindow_Impl::GetErrorText() const +{ + return m_xEditEngine->GetText(ESelection(0, m_nErrorStart, 0, m_nErrorEnd)); +} + +bool SentenceEditWindow_Impl::GetErrorDescription(SpellErrorDescription& rSpellErrorDescription, sal_Int32 nPosition) +{ + std::vector<EECharAttrib> aAttribList; + m_xEditEngine->GetCharAttribs(0, aAttribList); + + if (const EECharAttrib* pEECharAttrib = FindCharAttrib(nPosition, EE_CHAR_GRABBAG, aAttribList)) + { + ExtractErrorDescription(*pEECharAttrib, rSpellErrorDescription); + return true; + } + + return false; +} + +bool SentenceEditWindow_Impl::GetAlternatives(SpellErrorDescription& rSpellErrorDescription) +{ + return GetErrorDescription(rSpellErrorDescription, m_nErrorStart); +} + +void SentenceEditWindow_Impl::RestoreCurrentError() +{ + SpellErrorDescription aSpellErrorDescription; + if (GetErrorDescription(aSpellErrorDescription, m_nErrorStart)) + { + if (aSpellErrorDescription.sErrorText != GetErrorText() ) + ChangeMarkedWord(aSpellErrorDescription.sErrorText, LanguageTag::convertToLanguageType(aSpellErrorDescription.aLocale)); + } +} + +void SentenceEditWindow_Impl::SetAlternatives( const Reference< XSpellAlternatives>& xAlt ) +{ + OUString aWord; + lang::Locale aLocale; + uno::Sequence< OUString > aAlts; + if (xAlt.is()) + { + aWord = xAlt->getWord(); + aLocale = xAlt->getLocale(); + aAlts = xAlt->getAlternatives(); + } + SpellErrorDescription aDesc( false, aWord, std::move(aLocale), aAlts, nullptr); + SfxGrabBagItem aSpellErrorDescription(EE_CHAR_GRABBAG); + aSpellErrorDescription.GetGrabBag()["SpellErrorDescription"] <<= aDesc.toSequence(); + SetAttrib(aSpellErrorDescription, m_nErrorStart, m_nErrorEnd); +} + +void SentenceEditWindow_Impl::SetAttrib(const SfxPoolItem& rItem, sal_Int32 nStart, sal_Int32 nEnd) +{ + SfxItemSet aSet(m_xEditEngine->GetEmptyItemSet()); + aSet.Put(rItem); + m_xEditEngine->QuickSetAttribs(aSet, ESelection(0, nStart, 0, nEnd)); + Invalidate(); +} + +void SentenceEditWindow_Impl::SetText( const OUString& rStr ) +{ + m_nErrorStart = m_nErrorEnd = 0; + m_xEditEngine->SetText(rStr); +} + +namespace { + +struct LanguagePosition_Impl +{ + sal_Int32 nPosition; + LanguageType eLanguage; + + LanguagePosition_Impl(sal_Int32 nPos, LanguageType eLang) : + nPosition(nPos), + eLanguage(eLang) + {} +}; + +} + +typedef std::vector<LanguagePosition_Impl> LanguagePositions_Impl; + +static void lcl_InsertBreakPosition_Impl( + LanguagePositions_Impl& rBreakPositions, sal_Int32 nInsert, LanguageType eLanguage) +{ + LanguagePositions_Impl::iterator aStart = rBreakPositions.begin(); + while(aStart != rBreakPositions.end()) + { + if(aStart->nPosition == nInsert) + { + //the language of following starts has to overwrite + //the one of previous ends + aStart->eLanguage = eLanguage; + return; + } + else if(aStart->nPosition > nInsert) + { + + rBreakPositions.insert(aStart, LanguagePosition_Impl(nInsert, eLanguage)); + return; + } + else + ++aStart; + } + rBreakPositions.emplace_back(nInsert, eLanguage); +} + +/*------------------------------------------------------------------------- + Returns the text in spell portions. Each portion contains text with an + equal language and attribute. The spell alternatives are empty. + -----------------------------------------------------------------------*/ +svx::SpellPortions SentenceEditWindow_Impl::CreateSpellPortions() const +{ + svx::SpellPortions aRet; + + const sal_Int32 nTextLen = m_xEditEngine->GetTextLen(0); + + std::vector<EECharAttrib> aAttribList; + m_xEditEngine->GetCharAttribs(0, aAttribList); + + if (nTextLen) + { + int nCursor(0); + LanguagePositions_Impl aBreakPositions; + const EECharAttrib* pLastLang = nullptr; + const EECharAttrib* pLastError = nullptr; + LanguageType eLang = LANGUAGE_DONTKNOW; + const EECharAttrib* pError = nullptr; + while (nCursor < nTextLen) + { + const EECharAttrib* pLang = FindCharAttrib(nCursor, EE_CHAR_LANGUAGE, aAttribList); + if(pLang && pLang != pLastLang) + { + eLang = static_cast<const SvxLanguageItem*>(pLang->pAttr)->GetLanguage(); + lcl_InsertBreakPosition_Impl(aBreakPositions, pLang->nStart, eLang); + lcl_InsertBreakPosition_Impl(aBreakPositions, pLang->nEnd, eLang); + pLastLang = pLang; + } + pError = FindCharAttrib(nCursor, EE_CHAR_GRABBAG, aAttribList); + if (pError && pLastError != pError) + { + lcl_InsertBreakPosition_Impl(aBreakPositions, pError->nStart, eLang); + lcl_InsertBreakPosition_Impl(aBreakPositions, pError->nEnd, eLang); + pLastError = pError; + + } + ++nCursor; + } + + if (aBreakPositions.empty()) + { + //if all content has been overwritten the attributes may have been removed, too + svx::SpellPortion aPortion1; + aPortion1.eLanguage = GetSpellDialog()->GetSelectedLang_Impl(); + + aPortion1.sText = m_xEditEngine->GetText(ESelection(0, 0, 0, nTextLen)); + + aRet.push_back(aPortion1); + } + else + { + LanguagePositions_Impl::iterator aStart = aBreakPositions.begin(); + //start should always be Null + eLang = aStart->eLanguage; + sal_Int32 nStart = aStart->nPosition; + DBG_ASSERT(!nStart, "invalid start position - language attribute missing?"); + ++aStart; + + while(aStart != aBreakPositions.end()) + { + svx::SpellPortion aPortion1; + aPortion1.eLanguage = eLang; + + aPortion1.sText = m_xEditEngine->GetText(ESelection(0, nStart, 0, aStart->nPosition)); + + bool bIsIgnoreError = m_aIgnoreErrorsAt.find( nStart ) != m_aIgnoreErrorsAt.end(); + if( bIsIgnoreError ) + { + aPortion1.bIgnoreThisError = true; + } + aRet.push_back(aPortion1); + nStart = aStart->nPosition; + eLang = aStart->eLanguage; + ++aStart; + } + } + + // quick partly fix of #i71318. Correct fix needs to patch the EditEngine itself... + // this one will only prevent text from disappearing. It may to not have the + // correct language and will probably not spell checked... + const sal_uInt32 nPara = m_xEditEngine->GetParagraphCount(); + if (nPara > 1) + { + OUStringBuffer aLeftOverText; + for (sal_uInt32 i = 1; i < nPara; ++i) + { + aLeftOverText.append("\x0a"); // the manual line break... + aLeftOverText.append(m_xEditEngine->GetText(i)); + } + if (pError) + { // we need to add a new portion containing the left-over text + svx::SpellPortion aPortion2; + aPortion2.eLanguage = eLang; + aPortion2.sText = aLeftOverText.makeStringAndClear(); + aRet.push_back( aPortion2 ); + } + else + { // we just need to append the left-over text to the last portion (which had no errors) + aRet[ aRet.size() - 1 ].sText += aLeftOverText; + } + } + } + + return aRet; +} + +void SentenceEditWindow_Impl::Undo() +{ + SfxUndoManager& rUndoMgr = m_xEditEngine->GetUndoManager(); + DBG_ASSERT(GetUndoActionCount(), "no undo actions available" ); + if(!GetUndoActionCount()) + return; + bool bSaveUndoEdit = IsUndoEditMode(); + SpellUndoAction_Impl* pUndoAction; + //if the undo edit mode is active then undo all changes until the UNDO_EDIT_MODE action has been found + do + { + pUndoAction = static_cast<SpellUndoAction_Impl*>(rUndoMgr.GetUndoAction()); + rUndoMgr.Undo(); + }while(bSaveUndoEdit && SPELLUNDO_UNDO_EDIT_MODE != pUndoAction->GetId() && GetUndoActionCount()); + + if(bSaveUndoEdit || SPELLUNDO_CHANGE_GROUP == pUndoAction->GetId()) + GetSpellDialog()->UpdateBoxes_Impl(); +} + +void SentenceEditWindow_Impl::ResetUndo() +{ + SfxUndoManager& rUndo = m_xEditEngine->GetUndoManager(); + rUndo.Clear(); +} + +void SentenceEditWindow_Impl::AddUndoAction( std::unique_ptr<SfxUndoAction> pAction ) +{ + SfxUndoManager& rUndoMgr = m_xEditEngine->GetUndoManager(); + rUndoMgr.AddUndoAction(std::move(pAction)); + GetSpellDialog()->m_xUndoPB->set_sensitive(true); +} + +size_t SentenceEditWindow_Impl::GetUndoActionCount() const +{ + return m_xEditEngine->GetUndoManager().GetUndoActionCount(); +} + +void SentenceEditWindow_Impl::UndoActionStart( sal_uInt16 nId ) +{ + m_xEditEngine->UndoActionStart(nId); +} + +void SentenceEditWindow_Impl::UndoActionEnd() +{ + m_xEditEngine->UndoActionEnd(); +} + +void SentenceEditWindow_Impl::MoveErrorEnd(tools::Long nOffset) +{ + // Shouldn't we always add the real signed value instead??? + if(nOffset > 0) + m_nErrorEnd = m_nErrorEnd - static_cast<sal_Int32>(nOffset); + else + m_nErrorEnd = m_nErrorEnd - static_cast<sal_Int32>(-nOffset); +} + + +void SentenceEditWindow_Impl::SetUndoEditMode(bool bSet) +{ + DBG_ASSERT(!bSet || m_bIsUndoEditMode != bSet, "SetUndoEditMode with equal values?"); + m_bIsUndoEditMode = bSet; + //disable all buttons except the Change + SpellDialog* pSpellDialog = GetSpellDialog(); + weld::Widget* aControls[] = + { + pSpellDialog->m_xChangeAllPB.get(), + pSpellDialog->m_xExplainFT.get(), + pSpellDialog->m_xIgnoreAllPB.get(), + pSpellDialog->m_xIgnoreRulePB.get(), + pSpellDialog->m_xIgnorePB.get(), + pSpellDialog->m_xSuggestionLB.get(), + pSpellDialog->m_xSuggestionFT.get(), + pSpellDialog->m_xLanguageFT.get(), + pSpellDialog->m_xLanguageLB->get_widget(), + pSpellDialog->m_xAddToDictMB.get(), + pSpellDialog->m_xAddToDictPB.get(), + pSpellDialog->m_xAutoCorrPB.get() + }; + for (weld::Widget* pWidget : aControls) + pWidget->set_sensitive(false); + + //remove error marks + ESelection aAll(0, 0, 0, EE_TEXTPOS_ALL); + m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_COLOR); + m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT); + m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT_CJK); + m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT_CTL); + Invalidate(); + + //put the appropriate action on the Undo-stack + AddUndoAction( std::make_unique<SpellUndoAction_Impl>( + SPELLUNDO_UNDO_EDIT_MODE, GetSpellDialog()->aDialogUndoLink) ); + pSpellDialog->m_xChangePB->set_sensitive(true); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/about.cxx b/cui/source/dialogs/about.cxx new file mode 100644 index 000000000..09717ddec --- /dev/null +++ b/cui/source/dialogs/about.cxx @@ -0,0 +1,278 @@ +/* -*- 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 <about.hxx> + +#include <osl/process.h> //osl_getProcessLocale +#include <sal/log.hxx> //SAL_WARN +#include <vcl/settings.hxx> //GetSettings +#include <vcl/svapp.hxx> //Application:: +#include <vcl/virdev.hxx> //VirtualDevice +#include <vcl/weld.hxx> +#include <unotools/resmgr.hxx> //Translate + +#include <config_buildid.h> //EXTRA_BUILDID +#include <config_features.h> +#include <dialmgr.hxx> //CuiResId +#include <i18nlangtag/languagetag.hxx> +#include <sfx2/app.hxx> //SfxApplication::loadBrandSvg +#include <strings.hrc> +#include <svtools/langhelp.hxx> +#include <unotools/bootstrap.hxx> //utl::Bootstrap::getBuildIdData +#include <unotools/configmgr.hxx> //ConfigManager:: + +#include <com/sun/star/datatransfer/clipboard/SystemClipboard.hpp> +#include <vcl/unohelp2.hxx> + +#include <config_feature_opencl.h> +#if HAVE_FEATURE_OPENCL +#include <opencl/openclwrapper.hxx> +#endif +#include <officecfg/Office/Calc.hxx> +#include <officecfg/Office/Common.hxx> + +using namespace ::com::sun::star::uno; + +AboutDialog::AboutDialog(weld::Window *pParent) + : GenericDialogController(pParent, "cui/ui/aboutdialog.ui", "AboutDialog"), + m_pCreditsButton(m_xBuilder->weld_link_button("btnCredits")), + m_pWebsiteButton(m_xBuilder->weld_link_button("btnWebsite")), + m_pReleaseNotesButton(m_xBuilder->weld_link_button("btnReleaseNotes")), + m_pCloseButton(m_xBuilder->weld_button("btnClose")), + m_pCopyButton(m_xBuilder->weld_button("btnCopyVersion")), + m_pBrandImage(m_xBuilder->weld_image("imBrand")), + m_pAboutImage(m_xBuilder->weld_image("imAbout")), + m_pVersionLabel(m_xBuilder->weld_label("lbVersionString")), + m_pBuildCaption(m_xBuilder->weld_label("lbBuild")), + m_pBuildLabel(m_xBuilder->weld_link_button("lbBuildString")), + m_pEnvLabel(m_xBuilder->weld_label("lbEnvString")), + m_pUILabel(m_xBuilder->weld_label("lbUIString")), + m_pLocaleLabel(m_xBuilder->weld_label("lbLocaleString")), + m_pMiscLabel(m_xBuilder->weld_label("lbMiscString")), + m_pCopyrightLabel(m_xBuilder->weld_label("lbCopyright")) { + + // Labels + m_pVersionLabel->set_label(GetVersionString()); + + OUString sbuildId = GetBuildString(); + if (IsStringValidGitHash(sbuildId)) { + const tools::Long nMaxChar = 25; + m_pBuildLabel->set_uri("https://gerrit.libreoffice.org/gitweb?p=core.git;a=log;h=" + + sbuildId); + m_pBuildLabel->set_label(sbuildId.getLength() > nMaxChar ? sbuildId.replaceAt( + nMaxChar, sbuildId.getLength() - nMaxChar, u"...") + : sbuildId); + } else { + m_pBuildCaption->hide(); + m_pBuildLabel->hide(); + } + + m_pEnvLabel->set_label(Application::GetHWOSConfInfo(1)); + m_pUILabel->set_label(Application::GetHWOSConfInfo(2)); + m_pLocaleLabel->set_label(GetLocaleString()); + m_pMiscLabel->set_label(GetMiscString()); + m_pCopyrightLabel->set_label(GetCopyrightString()); + + // Images + const tools::Long nWidth(m_pCopyrightLabel->get_preferred_size().getWidth()); + BitmapEx aBackgroundBitmap; + + if (SfxApplication::loadBrandSvg(Application::GetSettings() + .GetStyleSettings() + .GetDialogColor() + .IsDark() + ? "shell/logo_inverted" + : "shell/logo", + aBackgroundBitmap, nWidth * 0.8)) { + ScopedVclPtr<VirtualDevice> m_pVirDev = + m_pBrandImage->create_virtual_device(); + m_pVirDev->SetOutputSizePixel(aBackgroundBitmap.GetSizePixel()); + m_pVirDev->DrawBitmapEx(Point(0, 0), aBackgroundBitmap); + m_pBrandImage->set_image(m_pVirDev.get()); + m_pVirDev.disposeAndClear(); + } + if (SfxApplication::loadBrandSvg("shell/about", aBackgroundBitmap, nWidth * 0.9)) { + ScopedVclPtr<VirtualDevice> m_pVirDev = + m_pAboutImage->create_virtual_device(); + m_pVirDev->SetOutputSizePixel(aBackgroundBitmap.GetSizePixel()); + m_pVirDev->DrawBitmapEx(Point(0, 0), aBackgroundBitmap); + m_pAboutImage->set_image(m_pVirDev.get()); + m_pVirDev.disposeAndClear(); + } + + // Links + m_pCreditsButton->set_uri(officecfg::Office::Common::Menus::CreditsURL::get()); + + OUString sURL(officecfg::Office::Common::Help::StartCenter::InfoURL::get()); + localizeWebserviceURI(sURL); + m_pWebsiteButton->set_uri(sURL); + + // See also SID_WHATSNEW in sfx2/source/appl/appserv.cxx + sURL = officecfg::Office::Common::Menus::ReleaseNotesURL::get() + + "?LOvers=" + utl::ConfigManager::getProductVersion() + "&LOlocale=" + + LanguageTag(utl::ConfigManager::getUILocale()).getBcp47(); + m_pReleaseNotesButton->set_uri(sURL); + + // Handler + m_pCopyButton->connect_clicked(LINK(this, AboutDialog, HandleClick)); + m_pCloseButton->grab_focus(); +} + +AboutDialog::~AboutDialog() {} + +bool AboutDialog::IsStringValidGitHash(const OUString &hash) { + for (int i = 0; i < hash.getLength(); i++) { + if (!std::isxdigit(hash[i])) { + return false; + } + } + return true; +} + +OUString AboutDialog::GetVersionString() { + OUString sVersion = CuiResId(TranslateId(nullptr, "%ABOUTBOXPRODUCTVERSION%ABOUTBOXPRODUCTVERSIONSUFFIX")); + +#ifdef _WIN64 + sVersion += " (x64)"; +#elif defined(_WIN32) + sVersion += " (x86)"; +#endif + +#if HAVE_FEATURE_COMMUNITY_FLAVOR + sVersion += " / LibreOffice Community"; +#endif + + return sVersion; +} + +OUString AboutDialog::GetBuildString() +{ + OUString sBuildId(utl::Bootstrap::getBuildIdData("")); + SAL_WARN_IF(sBuildId.isEmpty(), "cui.dialogs", "No BUILDID in bootstrap file"); + + return sBuildId; +} + +OUString AboutDialog::GetLocaleString(const bool bLocalized) { + + OUString sLocaleStr; + + rtl_Locale *pLocale; + osl_getProcessLocale(&pLocale); + if (pLocale && pLocale->Language) { + if (pLocale->Country && rtl_uString_getLength(pLocale->Country) > 0) + sLocaleStr = OUString::unacquired(&pLocale->Language) + "_" + + OUString::unacquired(&pLocale->Country); + else + sLocaleStr = OUString(pLocale->Language); + if (pLocale->Variant && rtl_uString_getLength(pLocale->Variant) > 0) + sLocaleStr += OUString(pLocale->Variant); + } + + sLocaleStr = Application::GetSettings().GetLanguageTag().getBcp47() + " (" + + sLocaleStr + ")"; + + OUString aUILocaleStr = + Application::GetSettings().GetUILanguageTag().getBcp47(); + OUString sUILocaleStr; + if (bLocalized) + sUILocaleStr = CuiResId(RID_CUISTR_ABOUT_UILOCALE); + else + sUILocaleStr = Translate::get(RID_CUISTR_ABOUT_UILOCALE, Translate::Create("cui", LanguageTag("en-US"))); + + if (sUILocaleStr.indexOf("$LOCALE") == -1) { + SAL_WARN("cui.dialogs", "translated uilocale string in translations " + "doesn't contain $LOCALE placeholder"); + sUILocaleStr += " $LOCALE"; + } + sUILocaleStr = sUILocaleStr.replaceAll("$LOCALE", aUILocaleStr); + + return sLocaleStr + "; " + sUILocaleStr; +} + +OUString AboutDialog::GetMiscString() { + + OUString sMisc; + + bool const extra = EXTRA_BUILDID[0] != '\0'; + // extracted from the 'if' to avoid Clang -Wunreachable-code + if (extra) { + sMisc = EXTRA_BUILDID "\n"; + } + + OUString aCalcMode; // Calc calculation mode + +#if HAVE_FEATURE_OPENCL + bool bOpenCL = openclwrapper::GPUEnv::isOpenCLEnabled(); + if (bOpenCL) + aCalcMode += " CL"; +#else + const bool bOpenCL = false; +#endif + + static const bool bThreadingProhibited = + std::getenv("SC_NO_THREADED_CALCULATION"); + bool bThreadedCalc = officecfg::Office::Calc::Formula::Calculation:: + UseThreadedCalculationForFormulaGroups::get(); + + if (!bThreadingProhibited && !bOpenCL && bThreadedCalc) { + aCalcMode += " threaded"; + } + + if (officecfg::Office::Calc::Defaults::Sheet::JumboSheets::get()) + { + aCalcMode += " Jumbo"; + } + + if (aCalcMode.isEmpty()) + aCalcMode = " default"; + sMisc += "Calc:" + aCalcMode; + + return sMisc; +} + +OUString AboutDialog::GetCopyrightString() { + OUString sVendorTextStr(CuiResId(RID_CUISTR_ABOUT_VENDOR)); + OUString aCopyrightString = + sVendorTextStr + "\n" + CuiResId(RID_CUISTR_ABOUT_COPYRIGHT) + "\n"; + + if (utl::ConfigManager::getProductName() == "LibreOffice") + aCopyrightString += CuiResId(RID_CUISTR_ABOUT_BASED_ON); + else + aCopyrightString += CuiResId(RID_CUISTR_ABOUT_DERIVED); + + return aCopyrightString; +} + +// special labels to comply with previous version info +// untranslated English for QA +IMPL_LINK_NOARG(AboutDialog, HandleClick, weld::Button &, void) { + css::uno::Reference<css::datatransfer::clipboard::XClipboard> xClipboard = + css::datatransfer::clipboard::SystemClipboard::create( + comphelper::getProcessComponentContext()); + + OUString sInfo = "Version: " + m_pVersionLabel->get_label() + "\n" // version + "Build ID: " + GetBuildString() + "\n" + // build id + Application::GetHWOSConfInfo(0,false) + "\n" // env+UI + "Locale: " + GetLocaleString(false) + "\n" + // locale + GetMiscString(); // misc + + vcl::unohelper::TextDataObject::CopyStringTo(sInfo, xClipboard); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/colorpicker.cxx b/cui/source/dialogs/colorpicker.cxx new file mode 100644 index 000000000..341cf5bf0 --- /dev/null +++ b/cui/source/dialogs/colorpicker.cxx @@ -0,0 +1,1363 @@ +/* -*- 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 <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/ui/dialogs/XAsynchronousExecutableDialog.hpp> +#include <com/sun/star/beans/XPropertyAccess.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/awt/XWindow.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/basemutex.hxx> +#include <vcl/customweld.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weld.hxx> +#include <sfx2/basedlgs.hxx> +#include <svx/hexcolorcontrol.hxx> +#include <basegfx/color/bcolortools.hxx> +#include <cmath> +#include <o3tl/typed_flags_set.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::ui::dialogs; +using namespace ::com::sun::star::beans; +using namespace ::basegfx; + +namespace { + +enum class UpdateFlags +{ + NONE = 0x00, + RGB = 0x01, + CMYK = 0x02, + HSB = 0x04, + ColorChooser = 0x08, + ColorSlider = 0x10, + Hex = 0x20, + All = 0x3f, +}; + +} + +namespace o3tl { + template<> struct typed_flags<UpdateFlags> : is_typed_flags<UpdateFlags, 0x3f> {}; +} + + +namespace cui +{ + +namespace { + +enum class ColorComponent { + Red, + Green, + Blue, + Hue, + Saturation, + Brightness, + Cyan, + Yellow, + Magenta, + Key, +}; + +} + +// color space conversion helpers + +static void RGBtoHSV( double dR, double dG, double dB, double& dH, double& dS, double& dV ) +{ + BColor result = basegfx::utils::rgb2hsv( BColor( dR, dG, dB ) ); + + dH = result.getX(); + dS = result.getY(); + dV = result.getZ(); +} + +static void HSVtoRGB(double dH, double dS, double dV, double& dR, double& dG, double& dB ) +{ + BColor result = basegfx::utils::hsv2rgb( BColor( dH, dS, dV ) ); + + dR = result.getRed(); + dG = result.getGreen(); + dB = result.getBlue(); +} + +// CMYK values from 0 to 1 +static void CMYKtoRGB( double fCyan, double fMagenta, double fYellow, double fKey, double& dR, double& dG, double& dB ) +{ + fCyan = (fCyan * ( 1.0 - fKey )) + fKey; + fMagenta = (fMagenta * ( 1.0 - fKey )) + fKey; + fYellow = (fYellow * ( 1.0 - fKey )) + fKey; + + dR = std::clamp( 1.0 - fCyan, 0.0, 1.0 ); + dG = std::clamp( 1.0 - fMagenta, 0.0, 1.0 ); + dB = std::clamp( 1.0 - fYellow, 0.0, 1.0 ); +} + +// CMY results from 0 to 1 +static void RGBtoCMYK( double dR, double dG, double dB, double& fCyan, double& fMagenta, double& fYellow, double& fKey ) +{ + fCyan = 1 - dR; + fMagenta = 1 - dG; + fYellow = 1 - dB; + + //CMYK and CMY values from 0 to 1 + fKey = 1.0; + if( fCyan < fKey ) fKey = fCyan; + if( fMagenta < fKey ) fKey = fMagenta; + if( fYellow < fKey ) fKey = fYellow; + + if( fKey >= 1.0 ) + { + //Black + fCyan = 0.0; + fMagenta = 0.0; + fYellow = 0.0; + } + else + { + fCyan = ( fCyan - fKey ) / ( 1.0 - fKey ); + fMagenta = ( fMagenta - fKey ) / ( 1.0 - fKey ); + fYellow = ( fYellow - fKey ) / ( 1.0 - fKey ); + } +} + +namespace { + +class ColorPreviewControl : public weld::CustomWidgetController +{ +private: + Color m_aColor; + + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) override; +public: + ColorPreviewControl() + { + } + + virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override + { + CustomWidgetController::SetDrawingArea(pDrawingArea); + pDrawingArea->set_size_request(pDrawingArea->get_approximate_digit_width() * 10, + pDrawingArea->get_text_height() * 2); + } + + void SetColor(const Color& rCol) + { + if (rCol != m_aColor) + { + m_aColor = rCol; + Invalidate(); + } + } +}; + +} + +void ColorPreviewControl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + rRenderContext.SetFillColor(m_aColor); + rRenderContext.SetLineColor(m_aColor); + rRenderContext.DrawRect(tools::Rectangle(Point(0, 0), GetOutputSizePixel())); +} + +namespace { + +enum ColorMode { HUE, SATURATION, BRIGHTNESS, RED, GREEN, BLUE }; + +} + +const ColorMode DefaultMode = HUE; + +namespace { + +class ColorFieldControl : public weld::CustomWidgetController +{ +public: + ColorFieldControl() + : meMode( DefaultMode ) + , mnBaseValue(USHRT_MAX) + , mdX( -1.0 ) + , mdY( -1.0 ) + , mbMouseCaptured(false) + { + } + + virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override + { + CustomWidgetController::SetDrawingArea(pDrawingArea); + pDrawingArea->set_size_request(pDrawingArea->get_approximate_digit_width() * 40, + pDrawingArea->get_text_height() * 10); + } + + virtual ~ColorFieldControl() override + { + mxBitmap.disposeAndClear(); + } + + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override; + virtual void Resize() override; + virtual bool MouseButtonDown(const MouseEvent& rMEvt) override; + virtual bool MouseMove(const MouseEvent& rMEvt) override; + virtual bool MouseButtonUp(const MouseEvent& rMEvt) override; + + void UpdateBitmap(); + void ShowPosition( const Point& rPos, bool bUpdate ); + void UpdatePosition(); + void Modify(); + + void SetValues(sal_uInt16 nBaseValue, ColorMode eMode, double x, double y); + double GetX() const { return mdX;} + double GetY() const { return mdY;} + + void SetModifyHdl(const Link<ColorFieldControl&,void>& rLink) { maModifyHdl = rLink; } + +private: + ColorMode meMode; + sal_uInt16 mnBaseValue; + double mdX; + double mdY; + bool mbMouseCaptured; + Point maPosition; + VclPtr<VirtualDevice> mxBitmap; + Link<ColorFieldControl&,void> maModifyHdl; + std::vector<sal_uInt8> maRGB_Horiz; + std::vector<sal_uInt16> maGrad_Horiz; + std::vector<sal_uInt16> maPercent_Horiz; + std::vector<sal_uInt8> maRGB_Vert; + std::vector<sal_uInt16> maPercent_Vert; +}; + +} + +void ColorFieldControl::UpdateBitmap() +{ + const Size aSize(GetOutputSizePixel()); + + if (mxBitmap && mxBitmap->GetOutputSizePixel() != aSize) + mxBitmap.disposeAndClear(); + + const sal_Int32 nWidth = aSize.Width(); + const sal_Int32 nHeight = aSize.Height(); + + if (nWidth == 0 || nHeight == 0) + return; + + if (!mxBitmap) + { + mxBitmap = VclPtr<VirtualDevice>::Create(); + mxBitmap->SetOutputSizePixel(aSize); + + maRGB_Horiz.resize( nWidth ); + maGrad_Horiz.resize( nWidth ); + maPercent_Horiz.resize( nWidth ); + + sal_uInt8* pRGB = maRGB_Horiz.data(); + sal_uInt16* pGrad = maGrad_Horiz.data(); + sal_uInt16* pPercent = maPercent_Horiz.data(); + + for( sal_Int32 x = 0; x < nWidth; x++ ) + { + *pRGB++ = static_cast<sal_uInt8>((x * 256) / nWidth); + *pGrad++ = static_cast<sal_uInt16>((x * 359) / nWidth); + *pPercent++ = static_cast<sal_uInt16>((x * 100) / nWidth); + } + + maRGB_Vert.resize(nHeight); + maPercent_Vert.resize(nHeight); + + pRGB = maRGB_Vert.data(); + pPercent = maPercent_Vert.data(); + + sal_Int32 y = nHeight; + while (y--) + { + *pRGB++ = static_cast<sal_uInt8>((y * 256) / nHeight); + *pPercent++ = static_cast<sal_uInt16>((y * 100) / nHeight); + } + } + + sal_uInt8* pRGB_Horiz = maRGB_Horiz.data(); + sal_uInt16* pGrad_Horiz = maGrad_Horiz.data(); + sal_uInt16* pPercent_Horiz = maPercent_Horiz.data(); + sal_uInt8* pRGB_Vert = maRGB_Vert.data(); + sal_uInt16* pPercent_Vert = maPercent_Vert.data(); + + // this has been unlooped for performance reason, please do not merge back! + + sal_uInt16 y = nHeight,x; + + switch(meMode) + { + case HUE: + while (y--) + { + sal_uInt16 nBri = pPercent_Vert[y]; + x = nWidth; + while (x--) + { + sal_uInt16 nSat = pPercent_Horiz[x]; + mxBitmap->DrawPixel(Point(x,y), Color::HSBtoRGB(mnBaseValue, nSat, nBri)); + } + } + break; + case SATURATION: + while (y--) + { + sal_uInt16 nBri = pPercent_Vert[y]; + x = nWidth; + while (x--) + { + sal_uInt16 nHue = pGrad_Horiz[x]; + mxBitmap->DrawPixel(Point(x,y), Color::HSBtoRGB(nHue, mnBaseValue, nBri)); + } + } + break; + case BRIGHTNESS: + while (y--) + { + sal_uInt16 nSat = pPercent_Vert[y]; + x = nWidth; + while (x--) + { + sal_uInt16 nHue = pGrad_Horiz[x]; + mxBitmap->DrawPixel(Point(x,y), Color::HSBtoRGB(nHue, nSat, mnBaseValue)); + } + } + break; + case RED: + { + Color aBitmapColor; + aBitmapColor.SetRed(mnBaseValue); + while (y--) + { + aBitmapColor.SetGreen(pRGB_Vert[y]); + x = nWidth; + while (x--) + { + aBitmapColor.SetBlue(pRGB_Horiz[x]); + mxBitmap->DrawPixel(Point(x,y), aBitmapColor); + } + } + break; + } + case GREEN: + { + Color aBitmapColor; + aBitmapColor.SetGreen(mnBaseValue); + while (y--) + { + aBitmapColor.SetRed(pRGB_Vert[y]); + x = nWidth; + while (x--) + { + aBitmapColor.SetBlue(pRGB_Horiz[x]); + mxBitmap->DrawPixel(Point(x,y), aBitmapColor); + } + } + break; + } + case BLUE: + { + Color aBitmapColor; + aBitmapColor.SetBlue(mnBaseValue); + while (y--) + { + aBitmapColor.SetGreen(pRGB_Vert[y]); + x = nWidth; + while (x--) + { + aBitmapColor.SetRed(pRGB_Horiz[x]); + mxBitmap->DrawPixel(Point(x,y), aBitmapColor); + } + } + break; + } + } +} + +constexpr int nCenterOffset = 5; + +void ColorFieldControl::ShowPosition( const Point& rPos, bool bUpdate ) +{ + if (!mxBitmap) + { + UpdateBitmap(); + Invalidate(); + } + + if (!mxBitmap) + return; + + const Size aSize(mxBitmap->GetOutputSizePixel()); + + tools::Long nX = rPos.X(); + tools::Long nY = rPos.Y(); + if (nX < 0) + nX = 0; + else if (nX >= aSize.Width()) + nX = aSize.Width() - 1; + + if (nY < 0) + nY = 0; + else if (nY >= aSize.Height()) + nY = aSize.Height() - 1; + + Point aPos = maPosition; + maPosition.setX( nX - nCenterOffset ); + maPosition.setY( nY - nCenterOffset ); + Invalidate(tools::Rectangle(aPos, Size(11, 11))); + Invalidate(tools::Rectangle(maPosition, Size(11, 11))); + + if (bUpdate) + { + mdX = double(nX) / double(aSize.Width() - 1.0); + mdY = double(aSize.Height() - 1.0 - nY) / double(aSize.Height() - 1.0); + } +} + +bool ColorFieldControl::MouseButtonDown(const MouseEvent& rMEvt) +{ + CaptureMouse(); + mbMouseCaptured = true; + ShowPosition(rMEvt.GetPosPixel(), true); + Modify(); + return true; +} + +bool ColorFieldControl::MouseMove(const MouseEvent& rMEvt) +{ + if (mbMouseCaptured) + { + ShowPosition(rMEvt.GetPosPixel(), true); + Modify(); + } + return true; +} + +bool ColorFieldControl::MouseButtonUp(const MouseEvent&) +{ + ReleaseMouse(); + mbMouseCaptured = false; + return true; +} + +void ColorFieldControl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + if (!mxBitmap) + UpdateBitmap(); + + if (!mxBitmap) + return; + + Size aSize(GetOutputSizePixel()); + rRenderContext.DrawOutDev(Point(0, 0), aSize, Point(0, 0), aSize, *mxBitmap); + + // draw circle around current color + Point aPos(maPosition.X() + nCenterOffset, maPosition.Y() + nCenterOffset); + Color aColor = mxBitmap->GetPixel(aPos); + if (aColor.IsDark()) + rRenderContext.SetLineColor(COL_WHITE); + else + rRenderContext.SetLineColor(COL_BLACK); + + rRenderContext.SetFillColor(); + rRenderContext.DrawEllipse(::tools::Rectangle(maPosition, Size(11, 11))); +} + +void ColorFieldControl::Resize() +{ + CustomWidgetController::Resize(); + UpdateBitmap(); + UpdatePosition(); +} + +void ColorFieldControl::Modify() +{ + maModifyHdl.Call( *this ); +} + +void ColorFieldControl::SetValues(sal_uInt16 nBaseValue, ColorMode eMode, double x, double y) +{ + bool bUpdateBitmap = (mnBaseValue != nBaseValue) || (meMode != eMode); + if (!bUpdateBitmap && mdX == x && mdY == y) + return; + + mnBaseValue = nBaseValue; + meMode = eMode; + mdX = x; + mdY = y; + + if (bUpdateBitmap) + UpdateBitmap(); + UpdatePosition(); + if (bUpdateBitmap) + Invalidate(); +} + +void ColorFieldControl::UpdatePosition() +{ + Size aSize(GetOutputSizePixel()); + ShowPosition(Point(static_cast<tools::Long>(mdX * aSize.Width()), static_cast<tools::Long>((1.0 - mdY) * aSize.Height())), false); +} + +namespace { + +class ColorSliderControl : public weld::CustomWidgetController +{ +public: + ColorSliderControl(); + virtual ~ColorSliderControl() override; + + virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override; + + virtual bool MouseButtonDown(const MouseEvent& rMEvt) override; + virtual bool MouseMove(const MouseEvent& rMEvt) override; + virtual bool MouseButtonUp(const MouseEvent& rMEvt) override; + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) override; + virtual void Resize() override; + + void UpdateBitmap(); + void ChangePosition( tools::Long nY ); + void Modify(); + + void SetValue( const Color& rColor, ColorMode eMode, double dValue ); + double GetValue() const { return mdValue; } + + void SetModifyHdl( const Link<ColorSliderControl&,void>& rLink ) { maModifyHdl = rLink; } + + sal_Int16 GetLevel() const { return mnLevel; } + +private: + Link<ColorSliderControl&,void> maModifyHdl; + Color maColor; + ColorMode meMode; + VclPtr<VirtualDevice> mxBitmap; + sal_Int16 mnLevel; + double mdValue; +}; + +} + +ColorSliderControl::ColorSliderControl() + : meMode( DefaultMode ) + , mnLevel( 0 ) + , mdValue( -1.0 ) +{ +} + +void ColorSliderControl::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + CustomWidgetController::SetDrawingArea(pDrawingArea); + pDrawingArea->set_size_request(pDrawingArea->get_approximate_digit_width() * 3, -1); +} + +ColorSliderControl::~ColorSliderControl() +{ + mxBitmap.disposeAndClear(); +} + +void ColorSliderControl::UpdateBitmap() +{ + Size aSize(1, GetOutputSizePixel().Height()); + + if (mxBitmap && mxBitmap->GetOutputSizePixel() != aSize) + mxBitmap.disposeAndClear(); + + if (!mxBitmap) + { + mxBitmap = VclPtr<VirtualDevice>::Create(); + mxBitmap->SetOutputSizePixel(aSize); + } + + const tools::Long nY = aSize.Height() - 1; + + Color aBitmapColor(maColor); + + sal_uInt16 nHue, nSat, nBri; + maColor.RGBtoHSB(nHue, nSat, nBri); + + // this has been unlooped for performance reason, please do not merge back! + + switch (meMode) + { + case HUE: + nSat = 100; + nBri = 100; + for (tools::Long y = 0; y <= nY; y++) + { + nHue = static_cast<sal_uInt16>((359 * y) / nY); + mxBitmap->DrawPixel(Point(0, nY - y), Color::HSBtoRGB(nHue, nSat, nBri)); + } + break; + + case SATURATION: + nBri = std::max(sal_uInt16(32), nBri); + for (tools::Long y = 0; y <= nY; y++) + { + nSat = static_cast<sal_uInt16>((100 * y) / nY); + mxBitmap->DrawPixel(Point(0, nY - y), Color::HSBtoRGB(nHue, nSat, nBri)); + } + break; + + case BRIGHTNESS: + for (tools::Long y = 0; y <= nY; y++) + { + nBri = static_cast<sal_uInt16>((100 * y) / nY); + mxBitmap->DrawPixel(Point(0, nY - y), Color::HSBtoRGB(nHue, nSat, nBri)); + } + break; + + case RED: + for (tools::Long y = 0; y <= nY; y++) + { + aBitmapColor.SetRed(sal_uInt8((tools::Long(255) * y) / nY)); + mxBitmap->DrawPixel(Point(0, nY - y), aBitmapColor); + } + break; + + case GREEN: + for (tools::Long y = 0; y <= nY; y++) + { + aBitmapColor.SetGreen(sal_uInt8((tools::Long(255) * y) / nY)); + mxBitmap->DrawPixel(Point(0, nY - y), aBitmapColor); + } + break; + + case BLUE: + for (tools::Long y = 0; y <= nY; y++) + { + aBitmapColor.SetBlue(sal_uInt8((tools::Long(255) * y) / nY)); + mxBitmap->DrawPixel(Point(0, nY - y), aBitmapColor); + } + break; + } +} + +void ColorSliderControl::ChangePosition(tools::Long nY) +{ + const tools::Long nHeight = GetOutputSizePixel().Height() - 1; + + if (nY < 0) + nY = 0; + else if (nY > nHeight) + nY = nHeight; + + mnLevel = nY; + mdValue = double(nHeight - nY) / double(nHeight); +} + +bool ColorSliderControl::MouseButtonDown(const MouseEvent& rMEvt) +{ + CaptureMouse(); + ChangePosition(rMEvt.GetPosPixel().Y()); + Modify(); + return true; +} + +bool ColorSliderControl::MouseMove(const MouseEvent& rMEvt) +{ + if (IsMouseCaptured()) + { + ChangePosition(rMEvt.GetPosPixel().Y()); + Modify(); + } + return true; +} + +bool ColorSliderControl::MouseButtonUp(const MouseEvent&) +{ + ReleaseMouse(); + return true; +} + +void ColorSliderControl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + if (!mxBitmap) + UpdateBitmap(); + + const Size aSize(GetOutputSizePixel()); + + Point aPos; + int x = aSize.Width(); + while (x--) + { + rRenderContext.DrawOutDev(aPos, aSize, Point(0,0), aSize, *mxBitmap); + aPos.AdjustX(1); + } +} + +void ColorSliderControl::Resize() +{ + CustomWidgetController::Resize(); + UpdateBitmap(); +} + +void ColorSliderControl::Modify() +{ + maModifyHdl.Call(*this); +} + +void ColorSliderControl::SetValue(const Color& rColor, ColorMode eMode, double dValue) +{ + bool bUpdateBitmap = (rColor != maColor) || (eMode != meMode); + if( bUpdateBitmap || (mdValue != dValue)) + { + maColor = rColor; + mdValue = dValue; + mnLevel = static_cast<sal_Int16>((1.0-dValue) * GetOutputSizePixel().Height()); + meMode = eMode; + if (bUpdateBitmap) + UpdateBitmap(); + Invalidate(); + } +} + +namespace { + +class ColorPickerDialog : public SfxDialogController +{ +private: + ColorFieldControl m_aColorField; + ColorSliderControl m_aColorSlider; + ColorPreviewControl m_aColorPreview; + ColorPreviewControl m_aColorPrevious; + + std::unique_ptr<weld::CustomWeld> m_xColorField; + std::unique_ptr<weld::CustomWeld> m_xColorSlider; + std::unique_ptr<weld::CustomWeld> m_xColorPreview; + std::unique_ptr<weld::CustomWeld> m_xColorPrevious; + + std::unique_ptr<weld::Widget> m_xFISliderLeft; + std::unique_ptr<weld::Widget> m_xFISliderRight; + std::unique_ptr<weld::RadioButton> m_xRBRed; + std::unique_ptr<weld::RadioButton> m_xRBGreen; + std::unique_ptr<weld::RadioButton> m_xRBBlue; + std::unique_ptr<weld::RadioButton> m_xRBHue; + std::unique_ptr<weld::RadioButton> m_xRBSaturation; + std::unique_ptr<weld::RadioButton> m_xRBBrightness; + + std::unique_ptr<weld::SpinButton> m_xMFRed; + std::unique_ptr<weld::SpinButton> m_xMFGreen; + std::unique_ptr<weld::SpinButton> m_xMFBlue; + std::unique_ptr<weld::HexColorControl> m_xEDHex; + + std::unique_ptr<weld::MetricSpinButton> m_xMFHue; + std::unique_ptr<weld::MetricSpinButton> m_xMFSaturation; + std::unique_ptr<weld::MetricSpinButton> m_xMFBrightness; + + std::unique_ptr<weld::MetricSpinButton> m_xMFCyan; + std::unique_ptr<weld::MetricSpinButton> m_xMFMagenta; + std::unique_ptr<weld::MetricSpinButton> m_xMFYellow; + std::unique_ptr<weld::MetricSpinButton> m_xMFKey; + +public: + ColorPickerDialog(weld::Window* pParent, Color nColor, sal_Int16 nMode); + + void update_color(UpdateFlags n = UpdateFlags::All); + + DECL_LINK(ColorFieldControlModifydl, ColorFieldControl&, void); + DECL_LINK(ColorSliderControlModifyHdl, ColorSliderControl&, void); + DECL_LINK(ColorModifyMetricHdl, weld::MetricSpinButton&, void); + DECL_LINK(ColorModifySpinHdl, weld::SpinButton&, void); + DECL_LINK(ColorModifyEditHdl, weld::Entry&, void); + DECL_LINK(ModeModifyHdl, weld::Toggleable&, void); + + Color GetColor() const; + + void setColorComponent(ColorComponent nComp, double dValue); + +private: + ColorMode meMode; + + double mdRed, mdGreen, mdBlue; + double mdHue, mdSat, mdBri; + double mdCyan, mdMagenta, mdYellow, mdKey; +}; + +} + +ColorPickerDialog::ColorPickerDialog(weld::Window* pParent, Color nColor, sal_Int16 nDialogMode) + : SfxDialogController(pParent, "cui/ui/colorpickerdialog.ui", "ColorPicker") + , m_xColorField(new weld::CustomWeld(*m_xBuilder, "colorField", m_aColorField)) + , m_xColorSlider(new weld::CustomWeld(*m_xBuilder, "colorSlider", m_aColorSlider)) + , m_xColorPreview(new weld::CustomWeld(*m_xBuilder, "preview", m_aColorPreview)) + , m_xColorPrevious(new weld::CustomWeld(*m_xBuilder, "previous", m_aColorPrevious)) + , m_xFISliderLeft(m_xBuilder->weld_widget("leftImage")) + , m_xFISliderRight(m_xBuilder->weld_widget("rightImage")) + , m_xRBRed(m_xBuilder->weld_radio_button("redRadiobutton")) + , m_xRBGreen(m_xBuilder->weld_radio_button("greenRadiobutton")) + , m_xRBBlue(m_xBuilder->weld_radio_button("blueRadiobutton")) + , m_xRBHue(m_xBuilder->weld_radio_button("hueRadiobutton")) + , m_xRBSaturation(m_xBuilder->weld_radio_button("satRadiobutton")) + , m_xRBBrightness(m_xBuilder->weld_radio_button("brightRadiobutton")) + , m_xMFRed(m_xBuilder->weld_spin_button("redSpinbutton")) + , m_xMFGreen(m_xBuilder->weld_spin_button("greenSpinbutton")) + , m_xMFBlue(m_xBuilder->weld_spin_button("blueSpinbutton")) + , m_xEDHex(new weld::HexColorControl(m_xBuilder->weld_entry("hexEntry"))) + , m_xMFHue(m_xBuilder->weld_metric_spin_button("hueSpinbutton", FieldUnit::DEGREE)) + , m_xMFSaturation(m_xBuilder->weld_metric_spin_button("satSpinbutton", FieldUnit::PERCENT)) + , m_xMFBrightness(m_xBuilder->weld_metric_spin_button("brightSpinbutton", FieldUnit::PERCENT)) + , m_xMFCyan(m_xBuilder->weld_metric_spin_button("cyanSpinbutton", FieldUnit::PERCENT)) + , m_xMFMagenta(m_xBuilder->weld_metric_spin_button("magSpinbutton", FieldUnit::PERCENT)) + , m_xMFYellow(m_xBuilder->weld_metric_spin_button("yellowSpinbutton", FieldUnit::PERCENT)) + , m_xMFKey(m_xBuilder->weld_metric_spin_button("keySpinbutton", FieldUnit::PERCENT)) + , meMode( DefaultMode ) +{ + m_aColorField.SetModifyHdl( LINK( this, ColorPickerDialog, ColorFieldControlModifydl ) ); + m_aColorSlider.SetModifyHdl( LINK( this, ColorPickerDialog, ColorSliderControlModifyHdl ) ); + + int nMargin = (m_xFISliderLeft->get_preferred_size().Height() + 1) / 2; + m_xColorSlider->set_margin_top(nMargin); + m_xColorSlider->set_margin_bottom(nMargin); + + Link<weld::MetricSpinButton&,void> aLink3( LINK( this, ColorPickerDialog, ColorModifyMetricHdl ) ); + m_xMFCyan->connect_value_changed( aLink3 ); + m_xMFMagenta->connect_value_changed( aLink3 ); + m_xMFYellow->connect_value_changed( aLink3 ); + m_xMFKey->connect_value_changed( aLink3 ); + + m_xMFHue->connect_value_changed( aLink3 ); + m_xMFSaturation->connect_value_changed( aLink3 ); + m_xMFBrightness->connect_value_changed( aLink3 ); + + Link<weld::SpinButton&,void> aLink4(LINK(this, ColorPickerDialog, ColorModifySpinHdl)); + m_xMFRed->connect_value_changed(aLink4); + m_xMFGreen->connect_value_changed(aLink4); + m_xMFBlue->connect_value_changed(aLink4); + + m_xEDHex->connect_changed(LINK(this, ColorPickerDialog, ColorModifyEditHdl)); + + Link<weld::Toggleable&,void> aLink2 = LINK( this, ColorPickerDialog, ModeModifyHdl ); + m_xRBRed->connect_toggled( aLink2 ); + m_xRBGreen->connect_toggled( aLink2 ); + m_xRBBlue->connect_toggled( aLink2 ); + m_xRBHue->connect_toggled( aLink2 ); + m_xRBSaturation->connect_toggled( aLink2 ); + m_xRBBrightness->connect_toggled( aLink2 ); + + Color aColor(nColor); + + // modify + if (nDialogMode == 2) + { + m_aColorPrevious.SetColor(aColor); + m_xColorPrevious->show(); + } + + mdRed = static_cast<double>(aColor.GetRed()) / 255.0; + mdGreen = static_cast<double>(aColor.GetGreen()) / 255.0; + mdBlue = static_cast<double>(aColor.GetBlue()) / 255.0; + + RGBtoHSV( mdRed, mdGreen, mdBlue, mdHue, mdSat, mdBri ); + RGBtoCMYK( mdRed, mdGreen, mdBlue, mdCyan, mdMagenta, mdYellow, mdKey ); + + update_color(); +} + +static int toInt( double dValue, double dRange ) +{ + return static_cast< int >( std::floor((dValue * dRange) + 0.5 ) ); +} + +Color ColorPickerDialog::GetColor() const +{ + return Color( toInt(mdRed,255.0), toInt(mdGreen,255.0), toInt(mdBlue,255.0) ); +} + +void ColorPickerDialog::update_color( UpdateFlags n ) +{ + sal_uInt8 nRed = toInt(mdRed,255.0); + sal_uInt8 nGreen = toInt(mdGreen,255.0); + sal_uInt8 nBlue = toInt(mdBlue,255.0); + + sal_uInt16 nHue = toInt(mdHue, 1.0); + sal_uInt16 nSat = toInt(mdSat, 100.0); + sal_uInt16 nBri = toInt(mdBri, 100.0); + + if (n & UpdateFlags::RGB) // update RGB + { + m_xMFRed->set_value(nRed); + m_xMFGreen->set_value(nGreen); + m_xMFBlue->set_value(nBlue); + } + + if (n & UpdateFlags::CMYK) // update CMYK + { + m_xMFCyan->set_value(toInt(mdCyan, 100.0), FieldUnit::PERCENT); + m_xMFMagenta->set_value(toInt(mdMagenta, 100.0), FieldUnit::PERCENT); + m_xMFYellow->set_value(toInt(mdYellow, 100.0), FieldUnit::PERCENT); + m_xMFKey->set_value(toInt(mdKey, 100.0), FieldUnit::PERCENT); + } + + if (n & UpdateFlags::HSB ) // update HSB + { + m_xMFHue->set_value(nHue, FieldUnit::DEGREE); + m_xMFSaturation->set_value(nSat, FieldUnit::PERCENT); + m_xMFBrightness->set_value(nBri, FieldUnit::PERCENT); + } + + if (n & UpdateFlags::ColorChooser ) // update Color Chooser 1 + { + switch( meMode ) + { + case HUE: + m_aColorField.SetValues(nHue, meMode, mdSat, mdBri); + break; + case SATURATION: + m_aColorField.SetValues(nSat, meMode, mdHue / 360.0, mdBri); + break; + case BRIGHTNESS: + m_aColorField.SetValues(nBri, meMode, mdHue / 360.0, mdSat); + break; + case RED: + m_aColorField.SetValues(nRed, meMode, mdBlue, mdGreen); + break; + case GREEN: + m_aColorField.SetValues(nGreen, meMode, mdBlue, mdRed); + break; + case BLUE: + m_aColorField.SetValues(nBlue, meMode, mdRed, mdGreen); + break; + } + } + + Color aColor(nRed, nGreen, nBlue); + + if (n & UpdateFlags::ColorSlider) // update Color Chooser 2 + { + switch (meMode) + { + case HUE: + m_aColorSlider.SetValue(aColor, meMode, mdHue / 360.0); + break; + case SATURATION: + m_aColorSlider.SetValue(aColor, meMode, mdSat); + break; + case BRIGHTNESS: + m_aColorSlider.SetValue(aColor, meMode, mdBri); + break; + case RED: + m_aColorSlider.SetValue(aColor, meMode, mdRed); + break; + case GREEN: + m_aColorSlider.SetValue(aColor, meMode, mdGreen); + break; + case BLUE: + m_aColorSlider.SetValue(aColor, meMode, mdBlue); + break; + } + } + + if (n & UpdateFlags::Hex) // update hex + { + m_xFISliderLeft->set_margin_top(m_aColorSlider.GetLevel()); + m_xFISliderRight->set_margin_top(m_aColorSlider.GetLevel()); + m_xEDHex->SetColor(aColor); + } + m_aColorPreview.SetColor(aColor); +} + +IMPL_LINK_NOARG(ColorPickerDialog, ColorFieldControlModifydl, ColorFieldControl&, void) +{ + double x = m_aColorField.GetX(); + double y = m_aColorField.GetY(); + + switch( meMode ) + { + case HUE: + mdSat = x; + setColorComponent( ColorComponent::Brightness, y ); + break; + case SATURATION: + mdHue = x * 360.0; + setColorComponent( ColorComponent::Brightness, y ); + break; + case BRIGHTNESS: + mdHue = x * 360.0; + setColorComponent( ColorComponent::Saturation, y ); + break; + case RED: + mdBlue = x; + setColorComponent( ColorComponent::Green, y ); + break; + case GREEN: + mdBlue = x; + setColorComponent( ColorComponent::Red, y ); + break; + case BLUE: + mdRed = x; + setColorComponent( ColorComponent::Green, y ); + break; + } + + update_color(UpdateFlags::All & ~UpdateFlags::ColorChooser); +} + +IMPL_LINK_NOARG(ColorPickerDialog, ColorSliderControlModifyHdl, ColorSliderControl&, void) +{ + double dValue = m_aColorSlider.GetValue(); + switch (meMode) + { + case HUE: + setColorComponent( ColorComponent::Hue, dValue * 360.0 ); + break; + case SATURATION: + setColorComponent( ColorComponent::Saturation, dValue ); + break; + case BRIGHTNESS: + setColorComponent( ColorComponent::Brightness, dValue ); + break; + case RED: + setColorComponent( ColorComponent::Red, dValue ); + break; + case GREEN: + setColorComponent( ColorComponent::Green, dValue ); + break; + case BLUE: + setColorComponent( ColorComponent::Blue, dValue ); + break; + } + + update_color(UpdateFlags::All & ~UpdateFlags::ColorSlider); +} + +IMPL_LINK(ColorPickerDialog, ColorModifyMetricHdl, weld::MetricSpinButton&, rEdit, void) +{ + UpdateFlags n = UpdateFlags::NONE; + + if (&rEdit == m_xMFHue.get()) + { + setColorComponent( ColorComponent::Hue, static_cast<double>(m_xMFHue->get_value(FieldUnit::DEGREE)) ); + n = UpdateFlags::All & ~UpdateFlags::HSB; + } + else if (&rEdit == m_xMFSaturation.get()) + { + setColorComponent( ColorComponent::Saturation, static_cast<double>(m_xMFSaturation->get_value(FieldUnit::PERCENT)) / 100.0 ); + n = UpdateFlags::All & ~UpdateFlags::HSB; + } + else if (&rEdit == m_xMFBrightness.get()) + { + setColorComponent( ColorComponent::Brightness, static_cast<double>(m_xMFBrightness->get_value(FieldUnit::PERCENT)) / 100.0 ); + n = UpdateFlags::All & ~UpdateFlags::HSB; + } + else if (&rEdit == m_xMFCyan.get()) + { + setColorComponent( ColorComponent::Cyan, static_cast<double>(m_xMFCyan->get_value(FieldUnit::PERCENT)) / 100.0 ); + n = UpdateFlags::All & ~UpdateFlags::CMYK; + } + else if (&rEdit == m_xMFMagenta.get()) + { + setColorComponent( ColorComponent::Magenta, static_cast<double>(m_xMFMagenta->get_value(FieldUnit::PERCENT)) / 100.0 ); + n = UpdateFlags::All & ~UpdateFlags::CMYK; + } + else if (&rEdit == m_xMFYellow.get()) + { + setColorComponent( ColorComponent::Yellow, static_cast<double>(m_xMFYellow->get_value(FieldUnit::PERCENT)) / 100.0 ); + n = UpdateFlags::All & ~UpdateFlags::CMYK; + } + else if (&rEdit == m_xMFKey.get()) + { + setColorComponent( ColorComponent::Key, static_cast<double>(m_xMFKey->get_value(FieldUnit::PERCENT)) / 100.0 ); + n = UpdateFlags::All & ~UpdateFlags::CMYK; + } + + if (n != UpdateFlags::NONE) + update_color(n); +} + +IMPL_LINK_NOARG(ColorPickerDialog, ColorModifyEditHdl, weld::Entry&, void) +{ + UpdateFlags n = UpdateFlags::NONE; + + Color aColor = m_xEDHex->GetColor(); + + if (aColor != COL_AUTO && aColor != GetColor()) + { + mdRed = static_cast<double>(aColor.GetRed()) / 255.0; + mdGreen = static_cast<double>(aColor.GetGreen()) / 255.0; + mdBlue = static_cast<double>(aColor.GetBlue()) / 255.0; + + RGBtoHSV( mdRed, mdGreen, mdBlue, mdHue, mdSat, mdBri ); + RGBtoCMYK( mdRed, mdGreen, mdBlue, mdCyan, mdMagenta, mdYellow, mdKey ); + n = UpdateFlags::All & ~UpdateFlags::Hex; + } + + if (n != UpdateFlags::NONE) + update_color(n); +} + +IMPL_LINK(ColorPickerDialog, ColorModifySpinHdl, weld::SpinButton&, rEdit, void) +{ + UpdateFlags n = UpdateFlags::NONE; + + if (&rEdit == m_xMFRed.get()) + { + setColorComponent( ColorComponent::Red, static_cast<double>(m_xMFRed->get_value()) / 255.0 ); + n = UpdateFlags::All & ~UpdateFlags::RGB; + } + else if (&rEdit == m_xMFGreen.get()) + { + setColorComponent( ColorComponent::Green, static_cast<double>(m_xMFGreen->get_value()) / 255.0 ); + n = UpdateFlags::All & ~UpdateFlags::RGB; + } + else if (&rEdit == m_xMFBlue.get()) + { + setColorComponent( ColorComponent::Blue, static_cast<double>(m_xMFBlue->get_value()) / 255.0 ); + n = UpdateFlags::All & ~UpdateFlags::RGB; + } + + if (n != UpdateFlags::NONE) + update_color(n); +} + + +IMPL_LINK_NOARG(ColorPickerDialog, ModeModifyHdl, weld::Toggleable&, void) +{ + ColorMode eMode = HUE; + + if (m_xRBRed->get_active()) + { + eMode = RED; + } + else if (m_xRBGreen->get_active()) + { + eMode = GREEN; + } + else if (m_xRBBlue->get_active()) + { + eMode = BLUE; + } + else if (m_xRBSaturation->get_active()) + { + eMode = SATURATION; + } + else if (m_xRBBrightness->get_active()) + { + eMode = BRIGHTNESS; + } + + if (meMode != eMode) + { + meMode = eMode; + update_color(UpdateFlags::ColorChooser | UpdateFlags::ColorSlider); + } +} + +void ColorPickerDialog::setColorComponent( ColorComponent nComp, double dValue ) +{ + switch( nComp ) + { + case ColorComponent::Red: + mdRed = dValue; + break; + case ColorComponent::Green: + mdGreen = dValue; + break; + case ColorComponent::Blue: + mdBlue = dValue; + break; + case ColorComponent::Hue: + mdHue = dValue; + break; + case ColorComponent::Saturation: + mdSat = dValue; + break; + case ColorComponent::Brightness: + mdBri = dValue; + break; + case ColorComponent::Cyan: + mdCyan = dValue; + break; + case ColorComponent::Yellow: + mdYellow = dValue; + break; + case ColorComponent::Magenta: + mdMagenta = dValue; + break; + case ColorComponent::Key: + mdKey = dValue; + break; + } + + if (nComp == ColorComponent::Red || nComp == ColorComponent::Green || nComp == ColorComponent::Blue) + { + RGBtoHSV( mdRed, mdGreen, mdBlue, mdHue, mdSat, mdBri ); + RGBtoCMYK( mdRed, mdGreen, mdBlue, mdCyan, mdMagenta, mdYellow, mdKey ); + } + else if (nComp == ColorComponent::Hue || nComp == ColorComponent::Saturation || nComp == ColorComponent::Brightness) + { + HSVtoRGB( mdHue, mdSat, mdBri, mdRed, mdGreen, mdBlue ); + RGBtoCMYK( mdRed, mdGreen, mdBlue, mdCyan, mdMagenta, mdYellow, mdKey ); + } + else + { + CMYKtoRGB( mdCyan, mdMagenta, mdYellow, mdKey, mdRed, mdGreen, mdBlue ); + RGBtoHSV( mdRed, mdGreen, mdBlue, mdHue, mdSat, mdBri ); + } +} + +typedef ::cppu::WeakComponentImplHelper< XServiceInfo, XExecutableDialog, XAsynchronousExecutableDialog, XInitialization, XPropertyAccess > ColorPickerBase; + +namespace { + +class ColorPicker : protected ::cppu::BaseMutex, // Struct for right initialization of mutex member! Must be first of baseclasses. + public ColorPickerBase +{ +public: + explicit ColorPicker(); + + // XInitialization + virtual void SAL_CALL initialize( const Sequence< Any >& aArguments ) override; + + // XInitialization + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XPropertyAccess + virtual Sequence< PropertyValue > SAL_CALL getPropertyValues( ) override; + virtual void SAL_CALL setPropertyValues( const Sequence< PropertyValue >& aProps ) override; + + // XExecutableDialog + virtual void SAL_CALL setTitle( const OUString& aTitle ) override; + virtual sal_Int16 SAL_CALL execute( ) override; + + // XAsynchronousExecutableDialog + virtual void SAL_CALL setDialogTitle( const OUString& aTitle ) override; + virtual void SAL_CALL startExecuteModal( const css::uno::Reference< css::ui::dialogs::XDialogClosedListener >& xListener ) override; + +private: + Color mnColor; + sal_Int16 mnMode; + Reference<css::awt::XWindow> mxParent; +}; + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_cui_ColorPicker_get_implementation( + css::uno::XComponentContext*, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire( new ColorPicker ); +} + + +constexpr OUStringLiteral gsColorKey( u"Color" ); +constexpr OUStringLiteral gsModeKey( u"Mode" ); + +ColorPicker::ColorPicker() + : ColorPickerBase( m_aMutex ) + , mnColor( 0 ) + , mnMode( 0 ) +{ +} + +// XInitialization +void SAL_CALL ColorPicker::initialize( const Sequence< Any >& aArguments ) +{ + if( aArguments.getLength() == 1 ) + { + aArguments[0] >>= mxParent; + } +} + +// XInitialization +OUString SAL_CALL ColorPicker::getImplementationName( ) +{ + return "com.sun.star.cui.ColorPicker"; +} + +sal_Bool SAL_CALL ColorPicker::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +Sequence< OUString > SAL_CALL ColorPicker::getSupportedServiceNames( ) +{ + return { "com.sun.star.ui.dialogs.ColorPicker", + "com.sun.star.ui.dialogs.AsynchronousColorPicker" }; +} + +// XPropertyAccess +Sequence< PropertyValue > SAL_CALL ColorPicker::getPropertyValues( ) +{ + Sequence< PropertyValue > props{ comphelper::makePropertyValue(gsColorKey, mnColor) }; + return props; +} + +void SAL_CALL ColorPicker::setPropertyValues( const Sequence< PropertyValue >& aProps ) +{ + for ( const PropertyValue& rProp : aProps ) + { + if( rProp.Name == gsColorKey ) + { + rProp.Value >>= mnColor; + } + else if( rProp.Name == gsModeKey ) + { + rProp.Value >>= mnMode; + } + } +} + +// XExecutableDialog +void SAL_CALL ColorPicker::setTitle( const OUString& ) +{ +} + +sal_Int16 SAL_CALL ColorPicker::execute() +{ + std::unique_ptr<ColorPickerDialog> xDlg(new ColorPickerDialog(Application::GetFrameWeld(mxParent), mnColor, mnMode)); + sal_Int16 ret = xDlg->run(); + if (ret) + mnColor = xDlg->GetColor(); + return ret; +} + +// XAsynchronousExecutableDialog +void SAL_CALL ColorPicker::setDialogTitle( const OUString& ) +{ +} + +void SAL_CALL ColorPicker::startExecuteModal( const css::uno::Reference< css::ui::dialogs::XDialogClosedListener >& xListener ) +{ + std::shared_ptr<ColorPickerDialog> xDlg = std::make_shared<ColorPickerDialog>(Application::GetFrameWeld(mxParent), mnColor, mnMode); + weld::DialogController::runAsync(xDlg, [this, xDlg, xListener] (sal_Int32 nResult) { + if (nResult) + mnColor = xDlg->GetColor(); + + sal_Int16 nRet = static_cast<sal_Int16>(nResult); + css::ui::dialogs::DialogClosedEvent aEvent( *this, nRet ); + xListener->dialogClosed( aEvent ); + }); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/cuicharmap.cxx b/cui/source/dialogs/cuicharmap.cxx new file mode 100644 index 000000000..c6994000e --- /dev/null +++ b/cui/source/dialogs/cuicharmap.cxx @@ -0,0 +1,1283 @@ +/* -*- 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 <sal/config.h> + +#include <stdio.h> + +#include <utility> +#include <vcl/svapp.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <svl/itempool.hxx> + +#include <rtl/textenc.h> +#include <svx/ucsubset.hxx> +#include <vcl/settings.hxx> +#include <vcl/fontcharmap.hxx> +#include <vcl/virdev.hxx> +#include <svl/stritem.hxx> +#include <o3tl/temporary.hxx> +#include <officecfg/Office/Common.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/dispatchcommand.hxx> + +#include <dialmgr.hxx> +#include <cui/cuicharmap.hxx> +#include <sfx2/app.hxx> +#include <svx/svxids.hrc> +#include <editeng/editids.hrc> +#include <editeng/fontitem.hxx> +#include <strings.hrc> +#include <unicode/uchar.h> +#include <unicode/utypes.h> + +using namespace css; + +SvxCharacterMap::SvxCharacterMap(weld::Widget* pParent, const SfxItemSet* pSet, + css::uno::Reference<css::frame::XFrame> xFrame) + : SfxDialogController(pParent, "cui/ui/specialcharacters.ui", "SpecialCharactersDialog") + , m_xVirDev(VclPtr<VirtualDevice>::Create()) + , isSearchMode(true) + , m_xFrame(std::move(xFrame)) + , m_aRecentCharView{SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev)} + , m_aFavCharView{SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev), + SvxCharView(m_xVirDev)} + , m_aShowChar(m_xVirDev) + , m_xOKBtn(m_xFrame.is() ? m_xBuilder->weld_button("insert") : m_xBuilder->weld_button("ok")) + , m_xFontText(m_xBuilder->weld_label("fontft")) + , m_xFontLB(m_xBuilder->weld_combo_box("fontlb")) + , m_xSubsetText(m_xBuilder->weld_label("subsetft")) + , m_xSubsetLB(m_xBuilder->weld_combo_box("subsetlb")) + , m_xSearchText(m_xBuilder->weld_entry("search")) + , m_xHexCodeText(m_xBuilder->weld_entry("hexvalue")) + , m_xDecimalCodeText(m_xBuilder->weld_entry("decimalvalue")) + , m_xFavouritesBtn(m_xBuilder->weld_button("favbtn")) + , m_xCharName(m_xBuilder->weld_label("charname")) + , m_xRecentGrid(m_xBuilder->weld_widget("viewgrid")) + , m_xFavGrid(m_xBuilder->weld_widget("favgrid")) + , m_xShowChar(new weld::CustomWeld(*m_xBuilder, "showchar", m_aShowChar)) + , m_xRecentCharView{std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar1", m_aRecentCharView[0]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar2", m_aRecentCharView[1]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar3", m_aRecentCharView[2]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar4", m_aRecentCharView[3]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar5", m_aRecentCharView[4]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar6", m_aRecentCharView[5]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar7", m_aRecentCharView[6]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar8", m_aRecentCharView[7]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar9", m_aRecentCharView[8]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar10", m_aRecentCharView[9]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar11", m_aRecentCharView[10]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar12", m_aRecentCharView[11]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar13", m_aRecentCharView[12]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar14", m_aRecentCharView[13]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar15", m_aRecentCharView[14]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "viewchar16", m_aRecentCharView[15])} + , m_xFavCharView{std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar1", m_aFavCharView[0]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar2", m_aFavCharView[1]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar3", m_aFavCharView[2]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar4", m_aFavCharView[3]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar5", m_aFavCharView[4]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar6", m_aFavCharView[5]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar7", m_aFavCharView[6]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar8", m_aFavCharView[7]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar9", m_aFavCharView[8]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar10", m_aFavCharView[9]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar11", m_aFavCharView[10]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar12", m_aFavCharView[11]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar13", m_aFavCharView[12]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar14", m_aFavCharView[13]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar15", m_aFavCharView[14]), + std::make_unique<weld::CustomWeld>(*m_xBuilder, "favchar16", m_aFavCharView[15])} + , m_xShowSet(new SvxShowCharSet(m_xBuilder->weld_scrolled_window("showscroll", true), m_xVirDev)) + , m_xShowSetArea(new weld::CustomWeld(*m_xBuilder, "showcharset", *m_xShowSet)) + , m_xSearchSet(new SvxSearchCharSet(m_xBuilder->weld_scrolled_window("searchscroll", true), m_xVirDev)) + , m_xSearchSetArea(new weld::CustomWeld(*m_xBuilder, "searchcharset", *m_xSearchSet)) +{ + m_aShowChar.SetCentered(true); + m_xFontLB->make_sorted(); + //lock the size request of this widget to the width of all possible entries + fillAllSubsets(*m_xSubsetLB); + m_xSubsetLB->set_size_request(m_xSubsetLB->get_preferred_size().Width(), -1); + m_xCharName->set_size_request(m_aShowChar.get_preferred_size().Width(), m_xCharName->get_text_height() * 4); + //lock the size request of this widget to the width of the original .ui string + m_xHexCodeText->set_size_request(m_xHexCodeText->get_preferred_size().Width(), -1); + //so things don't jump around if all the children are hidden + m_xRecentGrid->set_size_request(-1, m_aRecentCharView[0].get_preferred_size().Height()); + m_xFavGrid->set_size_request(-1, m_aFavCharView[0].get_preferred_size().Height()); + + init(); + + const SfxInt32Item* pCharItem = SfxItemSet::GetItem<SfxInt32Item>(pSet, SID_ATTR_CHAR, false); + if ( pCharItem ) + SetChar( pCharItem->GetValue() ); + + const SfxBoolItem* pDisableItem = SfxItemSet::GetItem<SfxBoolItem>(pSet, FN_PARAM_2, false); + if ( pDisableItem && pDisableItem->GetValue() ) + DisableFontSelection(); + + const SvxFontItem* pFontItem = SfxItemSet::GetItem<SvxFontItem>(pSet, SID_ATTR_CHAR_FONT, false); + const SfxStringItem* pFontNameItem = SfxItemSet::GetItem<SfxStringItem>(pSet, SID_FONT_NAME, false); + if ( pFontItem ) + { + vcl::Font aTmpFont( pFontItem->GetFamilyName(), pFontItem->GetStyleName(), GetCharFont().GetFontSize() ); + aTmpFont.SetCharSet( pFontItem->GetCharSet() ); + aTmpFont.SetPitch( pFontItem->GetPitch() ); + SetCharFont( aTmpFont ); + } + else if ( pFontNameItem ) + { + vcl::Font aTmpFont( GetCharFont() ); + aTmpFont.SetFamilyName( pFontNameItem->GetValue() ); + SetCharFont( aTmpFont ); + } + + m_xOutputSet.reset(new SfxAllItemSet(pSet ? *pSet->GetPool() : SfxGetpApp()->GetPool())); + m_xShowSet->Show(); + m_xSearchSet->Hide(); +} + +short SvxCharacterMap::run() +{ + if( SvxShowCharSet::getSelectedChar() == ' ') + { + m_xOKBtn->set_sensitive(false); + setFavButtonState(u"", u""); + } + else + { + sal_UCS4 cChar = m_xShowSet->GetSelectCharacter(); + // using the new UCS4 constructor + OUString aOUStr( &cChar, 1 ); + m_aShowChar.SetText(aOUStr); + + setFavButtonState(aOUStr, m_aShowChar.GetFont().GetFamilyName()); + m_xOKBtn->set_sensitive(true); + } + + return SfxDialogController::run(); +} + +void SvxCharacterMap::SetChar( sal_UCS4 c ) +{ + m_xShowSet->SelectCharacter( c ); + setFavButtonState(OUString(&c, 1), aFont.GetFamilyName()); +} + +sal_UCS4 SvxCharacterMap::GetChar() const +{ + return m_aShowChar.GetText().iterateCodePoints(&o3tl::temporary(sal_Int32(0))); +} + +void SvxCharacterMap::DisableFontSelection() +{ + m_xFontText->set_sensitive(false); + m_xFontLB->set_sensitive(false); +} + + +void SvxCharacterMap::getRecentCharacterList() +{ + //retrieve recent character list + const css::uno::Sequence< OUString > rRecentCharList( officecfg::Office::Common::RecentCharacters::RecentCharacterList::get() ); + for (OUString const & s : rRecentCharList) + { + maRecentCharList.push_back(s); + } + + //retrieve recent character font list + const css::uno::Sequence< OUString > rRecentCharFontList( officecfg::Office::Common::RecentCharacters::RecentCharacterFontList::get() ); + for (OUString const & s : rRecentCharFontList) + { + maRecentCharFontList.push_back(s); + } + + // tdf#135997: make sure that the two lists are same length + const auto nCommonLength = std::min(maRecentCharList.size(), maRecentCharFontList.size()); + maRecentCharList.resize(nCommonLength); + maRecentCharFontList.resize(nCommonLength); +} + + +void SvxCharacterMap::getFavCharacterList() +{ + maFavCharList.clear(); + maFavCharFontList.clear(); + //retrieve recent character list + const css::uno::Sequence< OUString > rFavCharList( officecfg::Office::Common::FavoriteCharacters::FavoriteCharacterList::get() ); + for (const OUString& s : rFavCharList) + { + maFavCharList.push_back(s); + } + + //retrieve recent character font list + const css::uno::Sequence< OUString > rFavCharFontList( officecfg::Office::Common::FavoriteCharacters::FavoriteCharacterFontList::get() ); + for (const OUString& s : rFavCharFontList) + { + maFavCharFontList.push_back(s); + } + + // tdf#135997: make sure that the two lists are same length + const auto nCommonLength = std::min(maFavCharList.size(), maFavCharFontList.size()); + maFavCharList.resize(nCommonLength); + maFavCharFontList.resize(nCommonLength); +} + +static std::pair<std::deque<OUString>::const_iterator, std::deque<OUString>::const_iterator> +findInPair(std::u16string_view str1, const std::deque<OUString>& rContainer1, + std::u16string_view str2, const std::deque<OUString>& rContainer2) +{ + assert(rContainer1.size() == rContainer2.size()); + + if (auto it1 = std::find(rContainer1.begin(), rContainer1.end(), str1); + it1 != rContainer1.end()) + { + auto it2 = rContainer2.begin() + (it1 - rContainer1.begin()); + if (*it2 == str2) + return { it1, it2 }; + } + return { rContainer1.end(), rContainer2.end() }; +} + +std::pair<std::deque<OUString>::const_iterator, std::deque<OUString>::const_iterator> +SvxCharacterMap::getRecentChar(std::u16string_view sTitle, std::u16string_view rFont) const +{ + return findInPair(sTitle, maRecentCharList, rFont, maRecentCharFontList); +} + +std::pair<std::deque<OUString>::const_iterator, std::deque<OUString>::const_iterator> +SvxCharacterMap::getFavChar(std::u16string_view sTitle, std::u16string_view rFont) const +{ + return findInPair(sTitle, maFavCharList, rFont, maFavCharFontList); +} + + +void SvxCharacterMap::updateRecentCharControl() +{ + assert(maRecentCharList.size() == maRecentCharFontList.size()); + + int i = 0; + for ( std::deque< OUString >::iterator it = maRecentCharList.begin(), it2 = maRecentCharFontList.begin(); + it != maRecentCharList.end() && it2 != maRecentCharFontList.end(); + ++it, ++it2, i++) + { + m_aRecentCharView[i].SetText(*it); + vcl::Font rFont = m_aRecentCharView[i].GetFont(); + rFont.SetFamilyName( *it2 ); + m_aRecentCharView[i].SetFont(rFont); + m_aRecentCharView[i].Show(); + } + + for(; i < 16 ; i++) + { + m_aRecentCharView[i].SetText(OUString()); + m_aRecentCharView[i].Hide(); + } +} + +void SvxCharacterMap::updateRecentCharacterList(const OUString& sTitle, const OUString& rFont) +{ + // if recent char to be added is already in list, remove it + if( const auto& [itChar, itChar2] = getRecentChar(sTitle, rFont); + itChar != maRecentCharList.end() && itChar2 != maRecentCharFontList.end() ) + { + maRecentCharList.erase( itChar ); + maRecentCharFontList.erase( itChar2); + } + + if (maRecentCharList.size() == 16) + { + maRecentCharList.pop_back(); + maRecentCharFontList.pop_back(); + } + + maRecentCharList.push_front(sTitle); + maRecentCharFontList.push_front(rFont); + + css::uno::Sequence< OUString > aRecentCharList(maRecentCharList.size()); + auto aRecentCharListRange = asNonConstRange(aRecentCharList); + css::uno::Sequence< OUString > aRecentCharFontList(maRecentCharFontList.size()); + auto aRecentCharFontListRange = asNonConstRange(aRecentCharFontList); + + for (size_t i = 0; i < maRecentCharList.size(); ++i) + { + aRecentCharListRange[i] = maRecentCharList[i]; + aRecentCharFontListRange[i] = maRecentCharFontList[i]; + } + + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::RecentCharacters::RecentCharacterList::set(aRecentCharList, batch); + officecfg::Office::Common::RecentCharacters::RecentCharacterFontList::set(aRecentCharFontList, batch); + batch->commit(); + + updateRecentCharControl(); +} + + +void SvxCharacterMap::updateFavCharacterList(const OUString& sTitle, const OUString& rFont) +{ + // if Fav char to be added is already in list, remove it + if( const auto& [itChar, itChar2] = getFavChar(sTitle, rFont); + itChar != maFavCharList.end() && itChar2 != maFavCharFontList.end() ) + { + maFavCharList.erase( itChar ); + maFavCharFontList.erase( itChar2); + } + + if (maFavCharList.size() == 16) + { + maFavCharList.pop_back(); + maFavCharFontList.pop_back(); + } + + maFavCharList.push_back(sTitle); + maFavCharFontList.push_back(rFont); + + css::uno::Sequence< OUString > aFavCharList(maFavCharList.size()); + auto aFavCharListRange = asNonConstRange(aFavCharList); + css::uno::Sequence< OUString > aFavCharFontList(maFavCharFontList.size()); + auto aFavCharFontListRange = asNonConstRange(aFavCharFontList); + + for (size_t i = 0; i < maFavCharList.size(); ++i) + { + aFavCharListRange[i] = maFavCharList[i]; + aFavCharFontListRange[i] = maFavCharFontList[i]; + } + + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::FavoriteCharacters::FavoriteCharacterList::set(aFavCharList, batch); + officecfg::Office::Common::FavoriteCharacters::FavoriteCharacterFontList::set(aFavCharFontList, batch); + batch->commit(); +} + + +void SvxCharacterMap::updateFavCharControl() +{ + assert(maFavCharList.size() == maFavCharFontList.size()); + + int i = 0; + for ( std::deque< OUString >::iterator it = maFavCharList.begin(), it2 = maFavCharFontList.begin(); + it != maFavCharList.end() && it2 != maFavCharFontList.end(); + ++it, ++it2, i++) + { + m_aFavCharView[i].SetText(*it); + vcl::Font rFont = m_aFavCharView[i].GetFont(); + rFont.SetFamilyName( *it2 ); + m_aFavCharView[i].SetFont(rFont); + m_aFavCharView[i].Show(); + } + + for(; i < 16 ; i++) + { + m_aFavCharView[i].SetText(OUString()); + m_aFavCharView[i].Hide(); + } + m_xShowSet->getFavCharacterList(); + m_xSearchSet->getFavCharacterList(); + // tdf#109214 - redraw highlight of the favorite characters + m_xShowSet->Invalidate(); +} + +void SvxCharacterMap::deleteFavCharacterFromList(std::u16string_view sTitle, std::u16string_view rFont) +{ + // if Fav char is found, remove it + if( const auto& [itChar, itChar2] = getFavChar(sTitle, rFont); + itChar != maFavCharList.end() && itChar2 != maFavCharFontList.end() ) + { + maFavCharList.erase( itChar ); + maFavCharFontList.erase( itChar2); + } + + css::uno::Sequence< OUString > aFavCharList(maFavCharList.size()); + auto aFavCharListRange = asNonConstRange(aFavCharList); + css::uno::Sequence< OUString > aFavCharFontList(maFavCharFontList.size()); + auto aFavCharFontListRange = asNonConstRange(aFavCharFontList); + + for (size_t i = 0; i < maFavCharList.size(); ++i) + { + aFavCharListRange[i] = maFavCharList[i]; + aFavCharFontListRange[i] = maFavCharFontList[i]; + } + + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::FavoriteCharacters::FavoriteCharacterList::set(aFavCharList, batch); + officecfg::Office::Common::FavoriteCharacters::FavoriteCharacterFontList::set(aFavCharFontList, batch); + batch->commit(); +} + +void SvxCharacterMap::init() +{ + aFont = m_xVirDev->GetFont(); + aFont.SetTransparent( true ); + aFont.SetFamily( FAMILY_DONTKNOW ); + aFont.SetPitch( PITCH_DONTKNOW ); + aFont.SetCharSet( RTL_TEXTENCODING_DONTKNOW ); + + OUString aDefStr( aFont.GetFamilyName() ); + OUString aLastName; + int nCount = m_xVirDev->GetFontFaceCollectionCount(); + std::vector<weld::ComboBoxEntry> aEntries; + aEntries.reserve(nCount); + for (int i = 0; i < nCount; ++i) + { + OUString aFontName( m_xVirDev->GetFontMetricFromCollection( i ).GetFamilyName() ); + if (aFontName != aLastName) + { + aLastName = aFontName; + aEntries.emplace_back(aFontName, OUString::number(i)); + } + } + m_xFontLB->insert_vector(aEntries, true); + // the font may not be in the list => + // try to find a font name token in list and select found font, + // else select topmost entry + bool bFound = (m_xFontLB->find_text(aDefStr) != -1); + if (!bFound) + { + sal_Int32 nIndex = 0; + do + { + OUString aToken = aDefStr.getToken(0, ';', nIndex); + if (m_xFontLB->find_text(aToken) != -1) + { + aDefStr = aToken; + bFound = true; + break; + } + } + while ( nIndex >= 0 ); + } + + if (bFound) + m_xFontLB->set_active_text(aDefStr); + else if (m_xFontLB->get_count() ) + m_xFontLB->set_active(0); + FontSelectHdl(*m_xFontLB); + if (m_xSubsetLB->get_count()) + m_xSubsetLB->set_active(0); + + m_xFontLB->connect_changed(LINK( this, SvxCharacterMap, FontSelectHdl)); + m_xSubsetLB->connect_changed(LINK( this, SvxCharacterMap, SubsetSelectHdl)); + m_xOKBtn->connect_clicked(LINK(this, SvxCharacterMap, InsertClickHdl)); + m_xOKBtn->show(); + + m_xShowSet->SetDoubleClickHdl( LINK( this, SvxCharacterMap, CharDoubleClickHdl ) ); + m_xShowSet->SetSelectHdl( LINK( this, SvxCharacterMap, CharSelectHdl ) ); + m_xShowSet->SetHighlightHdl( LINK( this, SvxCharacterMap, CharHighlightHdl ) ); + m_xShowSet->SetPreSelectHdl( LINK( this, SvxCharacterMap, CharPreSelectHdl ) ); + m_xShowSet->SetFavClickHdl( LINK( this, SvxCharacterMap, FavClickHdl ) ); + + m_xSearchSet->SetDoubleClickHdl( LINK( this, SvxCharacterMap, SearchCharDoubleClickHdl ) ); + m_xSearchSet->SetSelectHdl( LINK( this, SvxCharacterMap, SearchCharSelectHdl ) ); + m_xSearchSet->SetHighlightHdl( LINK( this, SvxCharacterMap, SearchCharHighlightHdl ) ); + m_xSearchSet->SetPreSelectHdl( LINK( this, SvxCharacterMap, SearchCharPreSelectHdl ) ); + m_xSearchSet->SetFavClickHdl( LINK( this, SvxCharacterMap, FavClickHdl ) ); + + m_xDecimalCodeText->connect_changed( LINK( this, SvxCharacterMap, DecimalCodeChangeHdl ) ); + m_xHexCodeText->connect_changed( LINK( this, SvxCharacterMap, HexCodeChangeHdl ) ); + m_xFavouritesBtn->connect_clicked( LINK(this, SvxCharacterMap, FavSelectHdl)); + + // tdf#117038 set the buttons width to its max possible width so it doesn't + // make layout change when the label changes + m_xFavouritesBtn->set_label(CuiResId(RID_CUISTR_REMOVE_FAVORITES)); + auto nMaxWidth = m_xFavouritesBtn->get_preferred_size().Width(); + m_xFavouritesBtn->set_label(CuiResId(RID_CUISTR_ADD_FAVORITES)); + nMaxWidth = std::max(nMaxWidth, m_xFavouritesBtn->get_preferred_size().Width()); + m_xFavouritesBtn->set_size_request(nMaxWidth, -1); + + if( SvxShowCharSet::getSelectedChar() == ' ') + { + m_xOKBtn->set_sensitive(false); + } + else + { + sal_UCS4 cChar = m_xShowSet->GetSelectCharacter(); + // using the new UCS4 constructor + OUString aOUStr( &cChar, 1 ); + m_aShowChar.SetText(aOUStr); + + setFavButtonState(aOUStr, aDefStr); + m_xOKBtn->set_sensitive(true); + } + + getRecentCharacterList(); + updateRecentCharControl(); + + getFavCharacterList(); + updateFavCharControl(); + + bool bHasInsert = m_xFrame.is(); + + for(int i = 0; i < 16; i++) + { + m_aRecentCharView[i].SetHasInsert(bHasInsert); + m_aRecentCharView[i].setMouseClickHdl(LINK(this,SvxCharacterMap, CharClickHdl)); + m_aRecentCharView[i].setClearClickHdl(LINK(this,SvxCharacterMap, RecentClearClickHdl)); + m_aRecentCharView[i].setClearAllClickHdl(LINK(this,SvxCharacterMap, RecentClearAllClickHdl)); + m_aFavCharView[i].SetHasInsert(bHasInsert); + m_aFavCharView[i].setMouseClickHdl(LINK(this,SvxCharacterMap, CharClickHdl)); + m_aFavCharView[i].setClearClickHdl(LINK(this,SvxCharacterMap, FavClearClickHdl)); + m_aFavCharView[i].setClearAllClickHdl(LINK(this,SvxCharacterMap, FavClearAllClickHdl)); + } + + setCharName(90); + + m_xSearchText->connect_focus_in(LINK( this, SvxCharacterMap, SearchFieldGetFocusHdl )); + m_xSearchText->connect_changed(LINK(this, SvxCharacterMap, SearchUpdateHdl)); +} + +bool SvxCharacterMap::isFavChar(std::u16string_view sTitle, std::u16string_view rFont) +{ + const auto& [itChar, itFont] = getFavChar(sTitle, rFont); + return itChar != maFavCharList.end() && itFont != maFavCharFontList.end(); +} + + +void SvxCharacterMap::setFavButtonState(std::u16string_view sTitle, std::u16string_view rFont) +{ + if(sTitle.empty() || rFont.empty()) + { + m_xFavouritesBtn->set_sensitive(false); + return; + } + else + m_xFavouritesBtn->set_sensitive(true); + + if (isFavChar(sTitle, rFont)) + { + m_xFavouritesBtn->set_label(CuiResId(RID_CUISTR_REMOVE_FAVORITES)); + } + else + { + if(maFavCharList.size() == 16) + { + m_xFavouritesBtn->set_sensitive(false); + } + + m_xFavouritesBtn->set_label(CuiResId(RID_CUISTR_ADD_FAVORITES)); + } +} + + +void SvxCharacterMap::SetCharFont( const vcl::Font& rFont ) +{ + // first get the underlying info in order to get font names + // like "Times New Roman;Times" resolved + vcl::Font aTmp(m_xVirDev->GetFontMetric(rFont)); + + // tdf#56363 - search font family without the font feature after the colon + OUString sFontFamilyName = aTmp.GetFamilyName(); + if (const sal_Int32 nIndex = sFontFamilyName.indexOf(":"); nIndex != -1) + sFontFamilyName = sFontFamilyName.copy(0, nIndex); + if (sFontFamilyName == "StarSymbol" && m_xFontLB->find_text(sFontFamilyName) == -1) + { + //if for some reason, like font in an old document, StarSymbol is requested and it's not available, then + //try OpenSymbol instead + aTmp.SetFamilyName("OpenSymbol"); + } + + if (m_xFontLB->find_text(sFontFamilyName) == -1) + return; + + m_xFontLB->set_active_text(sFontFamilyName); + aFont = aTmp; + FontSelectHdl(*m_xFontLB); + if (m_xSubsetLB->get_count()) + m_xSubsetLB->set_active(0); +} + +void SvxCharacterMap::fillAllSubsets(weld::ComboBox& rListBox) +{ + SubsetMap aAll(nullptr); + std::vector<weld::ComboBoxEntry> aEntries; + for (auto & subset : aAll.GetSubsetMap()) + aEntries.emplace_back(subset.GetName()); + rListBox.insert_vector(aEntries, true); +} + +void SvxCharacterMap::insertCharToDoc(const OUString& sGlyph) +{ + if(sGlyph.isEmpty()) + return; + + if (m_xFrame.is()) { + uno::Sequence<beans::PropertyValue> aArgs{ + comphelper::makePropertyValue("Symbols", sGlyph), + comphelper::makePropertyValue("FontName", aFont.GetFamilyName()) + }; + comphelper::dispatchCommand(".uno:InsertSymbol", m_xFrame, aArgs); + + updateRecentCharacterList(sGlyph, aFont.GetFamilyName()); + + } else { + sal_UCS4 cChar = sGlyph.iterateCodePoints(&o3tl::temporary(sal_Int32(0))); + const SfxItemPool* pPool = m_xOutputSet->GetPool(); + m_xOutputSet->Put( SfxStringItem( SID_CHARMAP, sGlyph ) ); + m_xOutputSet->Put( SvxFontItem( aFont.GetFamilyType(), aFont.GetFamilyName(), + aFont.GetStyleName(), aFont.GetPitch(), aFont.GetCharSet(), pPool->GetWhich(SID_ATTR_CHAR_FONT) ) ); + m_xOutputSet->Put( SfxStringItem( SID_FONT_NAME, aFont.GetFamilyName() ) ); + m_xOutputSet->Put( SfxInt32Item( SID_ATTR_CHAR, cChar ) ); + } +} + +IMPL_LINK_NOARG(SvxCharacterMap, FontSelectHdl, weld::ComboBox&, void) +{ + const sal_uInt32 nFont = m_xFontLB->get_active_id().toUInt32(); + aFont = m_xVirDev->GetFontMetricFromCollection(nFont); + aFont.SetWeight( WEIGHT_DONTKNOW ); + aFont.SetItalic( ITALIC_NONE ); + aFont.SetWidthType( WIDTH_DONTKNOW ); + aFont.SetPitch( PITCH_DONTKNOW ); + aFont.SetFamily( FAMILY_DONTKNOW ); + + // notify children using this font + m_xShowSet->SetFont( aFont ); + m_xSearchSet->SetFont( aFont ); + m_aShowChar.SetFont( aFont ); + + // setup unicode subset listbar with font specific subsets, + // hide unicode subset listbar for symbol fonts + // TODO: get info from the Font once it provides it + pSubsetMap.reset(); + m_xSubsetLB->clear(); + + bool bNeedSubset = (aFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL); + if (bNeedSubset) + { + FontCharMapRef xFontCharMap = m_xShowSet->GetFontCharMap(); + pSubsetMap.reset(new SubsetMap( xFontCharMap )); + + // update subset listbox for new font's unicode subsets + for (auto const& subset : pSubsetMap->GetSubsetMap()) + { + m_xSubsetLB->append(weld::toId(&subset), subset.GetName()); + // NOTE: subset must live at least as long as the selected font + } + + if (m_xSubsetLB->get_count() <= 1) + bNeedSubset = false; + } + + m_xSubsetText->set_sensitive(bNeedSubset); + m_xSubsetLB->set_sensitive(bNeedSubset); + + if (isSearchMode) + { + // tdf#137294 do this after modifying m_xSubsetLB sensitivity to + // restore insensitive for the search case + SearchUpdateHdl(*m_xSearchText); + SearchCharHighlightHdl(m_xSearchSet.get()); + } + + // tdf#118304 reselect current glyph to see if it's still there in new font + selectCharByCode(Radix::hexadecimal); +} + +void SvxCharacterMap::toggleSearchView(bool state) +{ + isSearchMode = state; + m_xHexCodeText->set_editable(!state); + m_xDecimalCodeText->set_editable(!state); + m_xSubsetLB->set_sensitive(!state); + + if(state) + { + m_xSearchSet->Show(); + m_xShowSet->Hide(); + } + else + { + m_xSearchSet->Hide(); + m_xShowSet->Show(); + } +} + +void SvxCharacterMap::setCharName(sal_UCS4 nDecimalValue) +{ + /* get the character name */ + UErrorCode errorCode = U_ZERO_ERROR; + // icu has a private uprv_getMaxCharNameLength function which returns the max possible + // length of this property. Unicode 3.2 max char name length was 83 + char buffer[100]; + u_charName(nDecimalValue, U_UNICODE_CHAR_NAME, buffer, sizeof(buffer), &errorCode); + if (U_SUCCESS(errorCode)) + m_xCharName->set_label(OUString::createFromAscii(buffer)); +} + +IMPL_LINK_NOARG(SvxCharacterMap, SubsetSelectHdl, weld::ComboBox&, void) +{ + const sal_Int32 nPos = m_xSubsetLB->get_active(); + const Subset* pSubset = weld::fromId<const Subset*>(m_xSubsetLB->get_active_id()); + + if( pSubset && !isSearchMode) + { + sal_UCS4 cFirst = pSubset->GetRangeMin(); + m_xShowSet->SelectCharacter( cFirst ); + + setFavButtonState(OUString(&cFirst, 1), aFont.GetFamilyName()); + m_xSubsetLB->set_active(nPos); + } + else if( pSubset && isSearchMode) + { + m_xSearchSet->SelectCharacter( pSubset ); + + const Subset* curSubset = nullptr; + if( pSubsetMap ) + curSubset = pSubsetMap->GetSubsetByUnicode( m_xSearchSet->GetSelectCharacter() ); + if( curSubset ) + m_xSubsetLB->set_active_text(curSubset->GetName()); + else + m_xSubsetLB->set_active(-1); + + sal_UCS4 sChar = m_xSearchSet->GetSelectCharacter(); + setFavButtonState(OUString(&sChar, 1), aFont.GetFamilyName()); + } +} + +IMPL_LINK(SvxCharacterMap, RecentClearClickHdl, SvxCharView*, rView, void) +{ + const OUString& sTitle = rView->GetText(); + OUString sFont = rView->GetFont().GetFamilyName(); + + // if recent char to be added is already in list, remove it + if( const auto& [itChar, itChar2] = getRecentChar(sTitle, sFont); + itChar != maRecentCharList.end() && itChar2 != maRecentCharFontList.end() ) + { + maRecentCharList.erase( itChar ); + maRecentCharFontList.erase( itChar2); + } + + css::uno::Sequence< OUString > aRecentCharList(maRecentCharList.size()); + auto aRecentCharListRange = asNonConstRange(aRecentCharList); + css::uno::Sequence< OUString > aRecentCharFontList(maRecentCharFontList.size()); + auto aRecentCharFontListRange = asNonConstRange(aRecentCharFontList); + + for (size_t i = 0; i < maRecentCharList.size(); ++i) + { + aRecentCharListRange[i] = maRecentCharList[i]; + aRecentCharFontListRange[i] = maRecentCharFontList[i]; + } + + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::RecentCharacters::RecentCharacterList::set(aRecentCharList, batch); + officecfg::Office::Common::RecentCharacters::RecentCharacterFontList::set(aRecentCharFontList, batch); + batch->commit(); + + updateRecentCharControl(); +} + +IMPL_LINK_NOARG(SvxCharacterMap, RecentClearAllClickHdl, SvxCharView*, void) +{ + maRecentCharList.clear(); + maRecentCharFontList.clear(); + + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::RecentCharacters::RecentCharacterList::set({ }, batch); + officecfg::Office::Common::RecentCharacters::RecentCharacterFontList::set({ }, batch); + batch->commit(); + + updateRecentCharControl(); +} + +IMPL_LINK(SvxCharacterMap, FavClearClickHdl, SvxCharView*, rView, void) +{ + deleteFavCharacterFromList(rView->GetText(), rView->GetFont().GetFamilyName()); + updateFavCharControl(); +} + +IMPL_LINK_NOARG(SvxCharacterMap, FavClearAllClickHdl, SvxCharView*, void) +{ + maFavCharList.clear(); + maFavCharFontList.clear(); + + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::FavoriteCharacters::FavoriteCharacterList::set({ }, batch); + officecfg::Office::Common::FavoriteCharacters::FavoriteCharacterFontList::set({ }, batch); + batch->commit(); + + updateFavCharControl(); +} + +IMPL_LINK_NOARG(SvxCharacterMap, SearchFieldGetFocusHdl, weld::Widget&, void) +{ + m_xOKBtn->set_sensitive(false); +} + +IMPL_LINK_NOARG(SvxCharacterMap, SearchUpdateHdl, weld::Entry&, void) +{ + if (!m_xSearchText->get_text().isEmpty()) + { + m_xSearchSet->ClearPreviousData(); + OUString aKeyword = m_xSearchText->get_text(); + + toggleSearchView(true); + + FontCharMapRef xFontCharMap = m_xSearchSet->GetFontCharMap(); + + sal_UCS4 sChar = xFontCharMap->GetFirstChar(); + while(sChar != xFontCharMap->GetLastChar()) + { + UErrorCode errorCode = U_ZERO_ERROR; + char buffer[100]; + u_charName(sChar, U_UNICODE_CHAR_NAME, buffer, sizeof(buffer), &errorCode); + if (U_SUCCESS(errorCode)) + { + OUString sName = OUString::createFromAscii(buffer); + if(!sName.isEmpty() && sName.toAsciiLowerCase().indexOf(aKeyword.toAsciiLowerCase()) >= 0) + m_xSearchSet->AppendCharToList(sChar); + } + sChar = xFontCharMap->GetNextChar(sChar); + } + //for last char + UErrorCode errorCode = U_ZERO_ERROR; + char buffer[100]; + u_charName(sChar, U_UNICODE_CHAR_NAME, buffer, sizeof(buffer), &errorCode); + if (U_SUCCESS(errorCode)) + { + OUString sName = OUString::createFromAscii(buffer); + if(!sName.isEmpty() && sName.toAsciiLowerCase().indexOf(aKeyword.toAsciiLowerCase()) >= 0) + m_xSearchSet->AppendCharToList(sChar); + } + + m_xSearchSet->UpdateScrollRange(); + } + else + { + toggleSearchView(false); + } +} + + +IMPL_LINK(SvxCharacterMap, CharClickHdl, SvxCharView*, rView, void) +{ + rView->GrabFocus(); + + m_aShowChar.SetText( rView->GetText() ); + m_aShowChar.SetFont(rView->GetFont()); + m_aShowChar.Invalidate(); + + setFavButtonState(rView->GetText(), rView->GetFont().GetFamilyName());//check state + + // Get the hexadecimal code + OUString charValue = rView->GetText(); + sal_UCS4 cChar = charValue.iterateCodePoints(&o3tl::temporary(sal_Int32(1)), -1); + OUString aHexText = OUString::number(cChar, 16).toAsciiUpperCase(); + + // Get the decimal code + OUString aDecimalText = OUString::number(cChar); + + m_xHexCodeText->set_text(aHexText); + m_xDecimalCodeText->set_text(aDecimalText); + setCharName(cChar); + + rView->Invalidate(); + m_xOKBtn->set_sensitive(true); +} + +IMPL_LINK_NOARG(SvxCharacterMap, CharDoubleClickHdl, SvxShowCharSet*, void) +{ + sal_UCS4 cChar = m_xShowSet->GetSelectCharacter(); + // using the new UCS4 constructor + OUString aOUStr( &cChar, 1 ); + setFavButtonState(aOUStr, aFont.GetFamilyName()); + insertCharToDoc(aOUStr); +} + +IMPL_LINK_NOARG(SvxCharacterMap, SearchCharDoubleClickHdl, SvxShowCharSet*, void) +{ + sal_UCS4 cChar = m_xSearchSet->GetSelectCharacter(); + // using the new UCS4 constructor + OUString aOUStr( &cChar, 1 ); + setFavButtonState(aOUStr, aFont.GetFamilyName()); + insertCharToDoc(aOUStr); +} + +IMPL_LINK_NOARG(SvxCharacterMap, CharSelectHdl, SvxShowCharSet*, void) +{ + m_xOKBtn->set_sensitive(true); +} + +IMPL_LINK_NOARG(SvxCharacterMap, SearchCharSelectHdl, SvxShowCharSet*, void) +{ + m_xOKBtn->set_sensitive(true); +} + +IMPL_LINK_NOARG(SvxCharacterMap, InsertClickHdl, weld::Button&, void) +{ + OUString sChar = m_aShowChar.GetText(); + insertCharToDoc(sChar); + // Need to update recent character list, when OK button does not insert + if(!m_xFrame.is()) + updateRecentCharacterList(sChar, aFont.GetFamilyName()); + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG(SvxCharacterMap, FavSelectHdl, weld::Button&, void) +{ + if (m_xFavouritesBtn->get_label().match(CuiResId(RID_CUISTR_ADD_FAVORITES))) + { + updateFavCharacterList(m_aShowChar.GetText(), m_aShowChar.GetFont().GetFamilyName()); + setFavButtonState(m_aShowChar.GetText(), m_aShowChar.GetFont().GetFamilyName()); + } + else + { + deleteFavCharacterFromList(m_aShowChar.GetText(), m_aShowChar.GetFont().GetFamilyName()); + m_xFavouritesBtn->set_label(CuiResId(RID_CUISTR_ADD_FAVORITES)); + m_xFavouritesBtn->set_sensitive(false); + } + + updateFavCharControl(); +} + +IMPL_LINK_NOARG(SvxCharacterMap, FavClickHdl, SvxShowCharSet*, void) +{ + getFavCharacterList(); + updateFavCharControl(); +} + +IMPL_LINK_NOARG(SvxCharacterMap, CharHighlightHdl, SvxShowCharSet*, void) +{ + OUString aText; + sal_UCS4 cChar = m_xShowSet->GetSelectCharacter(); + bool bSelect = (cChar > 0); + + // show char sample + if ( bSelect ) + { + // using the new UCS4 constructor + aText = OUString( &cChar, 1 ); + // Get the hexadecimal code + OUString aHexText = OUString::number(cChar, 16).toAsciiUpperCase(); + // Get the decimal code + OUString aDecimalText = OUString::number(cChar); + setCharName(cChar); + + // Update the hex and decimal codes only if necessary + if (!m_xHexCodeText->get_text().equalsIgnoreAsciiCase(aHexText)) + m_xHexCodeText->set_text(aHexText); + if (m_xDecimalCodeText->get_text() != aDecimalText) + m_xDecimalCodeText->set_text( aDecimalText ); + + const Subset* pSubset = nullptr; + if( pSubsetMap ) + pSubset = pSubsetMap->GetSubsetByUnicode( cChar ); + if( pSubset ) + m_xSubsetLB->set_active_text(pSubset->GetName()); + else + m_xSubsetLB->set_active(-1); + } + + m_aShowChar.SetText( aText ); + m_aShowChar.SetFont( aFont ); + m_aShowChar.Invalidate(); + + setFavButtonState(aText, aFont.GetFamilyName()); +} + +IMPL_LINK_NOARG(SvxCharacterMap, SearchCharHighlightHdl, SvxShowCharSet*, void) +{ + OUString aText; + sal_UCS4 cChar = m_xSearchSet->GetSelectCharacter(); + bool bSelect = (cChar > 0); + + // show char sample + if ( bSelect ) + { + aText = OUString( &cChar, 1 ); + // Get the hexadecimal code + OUString aHexText = OUString::number(cChar, 16).toAsciiUpperCase(); + // Get the decimal code + OUString aDecimalText = OUString::number(cChar); + setCharName(cChar); + + // Update the hex and decimal codes only if necessary + if (!m_xHexCodeText->get_text().equalsIgnoreAsciiCase(aHexText)) + m_xHexCodeText->set_text(aHexText); + if (m_xDecimalCodeText->get_text() != aDecimalText) + m_xDecimalCodeText->set_text( aDecimalText ); + + const Subset* pSubset = nullptr; + if( pSubsetMap ) + pSubset = pSubsetMap->GetSubsetByUnicode( cChar ); + if( pSubset ) + m_xSubsetLB->set_active_text(pSubset->GetName()); + else + m_xSubsetLB->set_active(-1); + } + + if(m_xSearchSet->HasFocus()) + { + m_aShowChar.SetText( aText ); + m_aShowChar.SetFont( aFont ); + m_aShowChar.Invalidate(); + + setFavButtonState(aText, aFont.GetFamilyName()); + } +} + +void SvxCharacterMap::selectCharByCode(Radix radix) +{ + OUString aCodeString; + switch(radix) + { + case Radix::decimal: + aCodeString = m_xDecimalCodeText->get_text(); + break; + case Radix::hexadecimal: + aCodeString = m_xHexCodeText->get_text(); + break; + } + // Convert the code back to a character using the appropriate radix + sal_UCS4 cChar = aCodeString.toUInt32(static_cast<sal_Int16> (radix)); + // Use FontCharMap::HasChar(sal_UCS4 cChar) to see if the desired character is in the font + FontCharMapRef xFontCharMap = m_xShowSet->GetFontCharMap(); + if (xFontCharMap->HasChar(cChar)) + // Select the corresponding character + SetChar(cChar); + else { + m_xCharName->set_label(CuiResId(RID_CUISTR_MISSING_CHAR)); + m_aShowChar.SetText(" "); + switch(radix) + { + case Radix::decimal: + m_xHexCodeText->set_text(OUString::number(cChar, 16)); + break; + case Radix::hexadecimal: + m_xDecimalCodeText->set_text(OUString::number(cChar)); + break; + } + } +} + +IMPL_LINK_NOARG(SvxCharacterMap, DecimalCodeChangeHdl, weld::Entry&, void) +{ + selectCharByCode(Radix::decimal); +} + +IMPL_LINK_NOARG(SvxCharacterMap, HexCodeChangeHdl, weld::Entry&, void) +{ + selectCharByCode(Radix::hexadecimal); +} + +IMPL_LINK_NOARG(SvxCharacterMap, CharPreSelectHdl, SvxShowCharSet*, void) +{ + // adjust subset selection + if( pSubsetMap ) + { + sal_UCS4 cChar = m_xShowSet->GetSelectCharacter(); + + setFavButtonState(OUString(&cChar, 1), aFont.GetFamilyName()); + const Subset* pSubset = pSubsetMap->GetSubsetByUnicode( cChar ); + if( pSubset ) + m_xSubsetLB->set_active_text(pSubset->GetName()); + } + + m_xOKBtn->set_sensitive(true); +} + +IMPL_LINK_NOARG(SvxCharacterMap, SearchCharPreSelectHdl, SvxShowCharSet*, void) +{ + // adjust subset selection + if( pSubsetMap ) + { + sal_UCS4 cChar = m_xSearchSet->GetSelectCharacter(); + + setFavButtonState(OUString(&cChar, 1), aFont.GetFamilyName()); + const Subset* pSubset = pSubsetMap->GetSubsetByUnicode( cChar ); + if( pSubset ) + m_xSubsetLB->set_active_text(pSubset->GetName()); + } + + m_xOKBtn->set_sensitive(true); +} + +// class SvxShowText ===================================================== +SvxShowText::SvxShowText(const VclPtr<VirtualDevice>& rVirDev) + : m_xVirDev(rVirDev) + , mnY(0) + , mbCenter(false) +{ +} + +void SvxShowText::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + CustomWidgetController::SetDrawingArea(pDrawingArea); + vcl::Font aFont = m_xVirDev->GetFont(); + Size aFontSize(aFont.GetFontSize().Width() * 5, aFont.GetFontSize().Height() * 5); + aFont.SetFontSize(aFontSize); + m_xVirDev->Push(PUSH_ALLFONT); + m_xVirDev->SetFont(aFont); + pDrawingArea->set_size_request(m_xVirDev->approximate_digit_width() + 2 * 12, + m_xVirDev->LogicToPixel(aFontSize).Height() * 2); + m_xVirDev->Pop(); +} + +void SvxShowText::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + rRenderContext.SetFont(m_aFont); + + Color aTextCol = rRenderContext.GetTextColor(); + Color aFillCol = rRenderContext.GetFillColor(); + Color aLineCol = rRenderContext.GetLineColor(); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + const Color aWindowTextColor(rStyleSettings.GetDialogTextColor()); + const Color aWindowColor(rStyleSettings.GetWindowColor()); + const Color aShadowColor(rStyleSettings.GetShadowColor()); + rRenderContext.SetTextColor(aWindowTextColor); + rRenderContext.SetFillColor(aWindowColor); + + const OUString aText = GetText(); + + Size aSize(GetOutputSizePixel()); + tools::Long nAvailWidth = aSize.Width(); + tools::Long nWinHeight = aSize.Height(); + + bool bGotBoundary = true; + bool bShrankFont = false; + vcl::Font aOrigFont(rRenderContext.GetFont()); + Size aFontSize(aOrigFont.GetFontSize()); + ::tools::Rectangle aBoundRect; + + for (tools::Long nFontHeight = aFontSize.Height(); nFontHeight > 0; nFontHeight -= 5) + { + if (!rRenderContext.GetTextBoundRect( aBoundRect, aText ) || aBoundRect.IsEmpty()) + { + bGotBoundary = false; + break; + } + if (!mbCenter) + break; + //only shrink in the single glyph large view mode + tools::Long nTextWidth = aBoundRect.GetWidth(); + if (nAvailWidth > nTextWidth) + break; + vcl::Font aFont(aOrigFont); + aFontSize.setHeight( nFontHeight ); + aFont.SetFontSize(aFontSize); + rRenderContext.SetFont(aFont); + mnY = (nWinHeight - rRenderContext.GetTextHeight()) / 2; + bShrankFont = true; + } + + Point aPoint(2, mnY); + // adjust position using ink boundary if possible + if (!bGotBoundary) + aPoint.setX( (aSize.Width() - rRenderContext.GetTextWidth(aText)) / 2 ); + else + { + // adjust position before it gets out of bounds + aBoundRect += aPoint; + + // shift back vertically if needed + int nYLDelta = aBoundRect.Top(); + int nYHDelta = aSize.Height() - aBoundRect.Bottom(); + if( nYLDelta <= 0 ) + aPoint.AdjustY( -(nYLDelta - 1) ); + else if( nYHDelta <= 0 ) + aPoint.AdjustY(nYHDelta - 1 ); + + if (mbCenter) + { + // move glyph to middle of cell + aPoint.setX( -aBoundRect.Left() + (aSize.Width() - aBoundRect.GetWidth()) / 2 ); + } + else + { + // shift back horizontally if needed + int nXLDelta = aBoundRect.Left(); + int nXHDelta = aSize.Width() - aBoundRect.Right(); + if( nXLDelta <= 0 ) + aPoint.AdjustX( -(nXLDelta - 1) ); + else if( nXHDelta <= 0 ) + aPoint.AdjustX(nXHDelta - 1 ); + } + } + + rRenderContext.SetLineColor(aShadowColor); + rRenderContext.DrawRect(tools::Rectangle(Point(0, 0), aSize)); + rRenderContext.DrawText(aPoint, aText); + rRenderContext.SetTextColor(aTextCol); + rRenderContext.SetFillColor(aFillCol); + rRenderContext.SetLineColor(aLineCol); + if (bShrankFont) + rRenderContext.SetFont(aOrigFont); +} + +void SvxShowText::SetFont( const vcl::Font& rFont ) +{ + tools::Long nWinHeight = GetOutputSizePixel().Height(); + + m_aFont = rFont; + m_aFont.SetWeight(WEIGHT_NORMAL); + m_aFont.SetAlignment(ALIGN_TOP); + m_aFont.SetFontSize(m_xVirDev->PixelToLogic(Size(0, nWinHeight / 2))); + m_aFont.SetTransparent(true); + + m_xVirDev->Push(PUSH_ALLFONT); + m_xVirDev->SetFont(m_aFont); + mnY = (nWinHeight - m_xVirDev->GetTextHeight()) / 2; + m_xVirDev->Pop(); + + Invalidate(); +} + +void SvxShowText::Resize() +{ + SetFont(GetFont()); //force recalculation of size +} + +void SvxShowText::SetText(const OUString& rText) +{ + m_sText = rText; + Invalidate(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/cuifmsearch.cxx b/cui/source/dialogs/cuifmsearch.cxx new file mode 100644 index 000000000..f1af2838c --- /dev/null +++ b/cui/source/dialogs/cuifmsearch.cxx @@ -0,0 +1,759 @@ +/* -*- 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 <tools/debug.hxx> +#include <vcl/stdtext.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <dialmgr.hxx> +#include <sfx2/app.hxx> +#include <svx/fmsrccfg.hxx> +#include <svx/fmsrcimp.hxx> +#include <strings.hrc> +#include <cuifmsearch.hxx> +#include <svl/cjkoptions.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <svx/svxdlg.hxx> +#include <o3tl/string_view.hxx> + +using namespace css::uno; +using namespace css::i18n; +using namespace ::svxform; +using namespace css::sdbc; +using namespace css::util; + +#define MAX_HISTORY_ENTRIES 50 + +void FmSearchDialog::initCommon( const Reference< XResultSet >& _rxCursor ) +{ + // init the engine + DBG_ASSERT( m_pSearchEngine, "FmSearchDialog::initCommon: have no engine!" ); + m_pSearchEngine->SetProgressHandler(LINK(this, FmSearchDialog, OnSearchProgress)); + + // some layout changes according to available CJK options + if (!SvtCJKOptions::IsJapaneseFindEnabled()) + { + // hide the options for the japanese search + m_pSoundsLikeCJK->hide(); + m_pSoundsLikeCJKSettings->hide(); + } + + if (!SvtCJKOptions::IsCJKFontEnabled()) + { + m_pHalfFullFormsCJK->hide(); + + // never ignore the width (ignoring is expensive) if the option is not available at all + m_pSearchEngine->SetIgnoreWidthCJK( false ); + } + + // some initial record texts + m_pftRecord->set_label( OUString::number(_rxCursor->getRow()) ); + m_pbClose->set_tooltip_text(OUString()); +} + +FmSearchDialog::FmSearchDialog(weld::Window* pParent, const OUString& sInitialText, const std::vector< OUString >& _rContexts, sal_Int16 nInitialContext, + const Link<FmSearchContext&,sal_uInt32>& lnkContextSupplier) + : GenericDialogController(pParent, "cui/ui/fmsearchdialog.ui", "RecordSearchDialog") + , m_sCancel( GetStandardText( StandardButtonType::Cancel ) ) + , m_lnkContextSupplier(lnkContextSupplier) + , m_prbSearchForText(m_xBuilder->weld_radio_button("rbSearchForText")) + , m_prbSearchForNull(m_xBuilder->weld_radio_button("rbSearchForNull")) + , m_prbSearchForNotNull(m_xBuilder->weld_radio_button("rbSearchForNotNull")) + , m_pcmbSearchText(m_xBuilder->weld_combo_box("cmbSearchText")) + , m_pftForm(m_xBuilder->weld_label("ftForm")) + , m_plbForm(m_xBuilder->weld_combo_box("lbForm")) + , m_prbAllFields(m_xBuilder->weld_radio_button("rbAllFields")) + , m_prbSingleField(m_xBuilder->weld_radio_button("rbSingleField")) + , m_plbField(m_xBuilder->weld_combo_box("lbField")) + , m_pftPosition(m_xBuilder->weld_label("ftPosition")) + , m_plbPosition(m_xBuilder->weld_combo_box("lbPosition")) + , m_pcbUseFormat(m_xBuilder->weld_check_button("cbUseFormat")) + , m_pcbCase(m_xBuilder->weld_check_button("cbCase")) + , m_pcbBackwards(m_xBuilder->weld_check_button("cbBackwards")) + , m_pcbStartOver(m_xBuilder->weld_check_button("cbStartOver")) + , m_pcbWildCard(m_xBuilder->weld_check_button("cbWildCard")) + , m_pcbRegular(m_xBuilder->weld_check_button("cbRegular")) + , m_pcbApprox(m_xBuilder->weld_check_button("cbApprox")) + , m_ppbApproxSettings(m_xBuilder->weld_button("pbApproxSettings")) + , m_pHalfFullFormsCJK(m_xBuilder->weld_check_button("HalfFullFormsCJK")) + , m_pSoundsLikeCJK(m_xBuilder->weld_check_button("SoundsLikeCJK")) + , m_pSoundsLikeCJKSettings(m_xBuilder->weld_button("SoundsLikeCJKSettings")) + , m_pftRecord(m_xBuilder->weld_label("ftRecord")) + , m_pftHint(m_xBuilder->weld_label("ftHint")) + , m_pbSearchAgain(m_xBuilder->weld_button("pbSearchAgain")) + , m_pbClose(m_xBuilder->weld_button("close")) +{ + m_pcmbSearchText->set_size_request(m_pcmbSearchText->get_approximate_digit_width() * 38, -1); + m_plbForm->set_size_request(m_plbForm->get_approximate_digit_width() * 38, -1); + m_sSearch = m_pbSearchAgain->get_label(); + + DBG_ASSERT(m_lnkContextSupplier.IsSet(), "FmSearchDialog::FmSearchDialog : have no ContextSupplier !"); + + FmSearchContext fmscInitial; + fmscInitial.nContext = nInitialContext; + m_lnkContextSupplier.Call(fmscInitial); + DBG_ASSERT(fmscInitial.xCursor.is(), "FmSearchDialog::FmSearchDialog : invalid data supplied by ContextSupplier !"); + DBG_ASSERT(comphelper::string::getTokenCount(fmscInitial.strUsedFields, ';') == static_cast<sal_Int32>(fmscInitial.arrFields.size()), + "FmSearchDialog::FmSearchDialog : invalid data supplied by ContextSupplied !"); +#if (OSL_DEBUG_LEVEL > 1) || defined DBG_UTIL + for (const Reference<XInterface> & arrField : fmscInitial.arrFields) + { + DBG_ASSERT(arrField.is(), "FmSearchDialog::FmSearchDialog : invalid data supplied by ContextSupplier !"); + } +#endif // (OSL_DEBUG_LEVEL > 1) || DBG_UTIL + + for ( std::vector< OUString >::const_iterator context = _rContexts.begin(); + context != _rContexts.end(); + ++context + ) + { + m_arrContextFields.emplace_back(); + m_plbForm->append_text(*context); + } + m_plbForm->set_active(nInitialContext); + + m_plbForm->connect_changed(LINK(this, FmSearchDialog, OnContextSelection)); + + if (m_arrContextFields.size() == 1) + { + // hide dispensable controls + m_pftForm->hide(); + m_plbForm->hide(); + } + + m_pSearchEngine.reset( new FmSearchEngine( + ::comphelper::getProcessComponentContext(), fmscInitial.xCursor, fmscInitial.strUsedFields, fmscInitial.arrFields ) ); + initCommon( fmscInitial.xCursor ); + + if ( !fmscInitial.sFieldDisplayNames.isEmpty() ) + { // use the display names if supplied + DBG_ASSERT(comphelper::string::getTokenCount(fmscInitial.sFieldDisplayNames, ';') == comphelper::string::getTokenCount(fmscInitial.strUsedFields, ';'), + "FmSearchDialog::FmSearchDialog : invalid initial context description !"); + Init(fmscInitial.sFieldDisplayNames, sInitialText); + } + else + Init(fmscInitial.strUsedFields, sInitialText); +} + +FmSearchDialog::~FmSearchDialog() +{ + SaveParams(); + + m_pConfig.reset(); + m_pSearchEngine.reset(); +} + +void FmSearchDialog::Init(std::u16string_view strVisibleFields, const OUString& sInitialText) +{ + //the initialization of all the Controls + m_prbSearchForText->connect_toggled(LINK(this, FmSearchDialog, OnToggledSearchRadio)); + m_prbSearchForNull->connect_toggled(LINK(this, FmSearchDialog, OnToggledSearchRadio)); + m_prbSearchForNotNull->connect_toggled(LINK(this, FmSearchDialog, OnToggledSearchRadio)); + + m_prbAllFields->connect_toggled(LINK(this, FmSearchDialog, OnToggledFieldRadios)); + m_prbSingleField->connect_toggled(LINK(this, FmSearchDialog, OnToggledFieldRadios)); + + m_pbSearchAgain->connect_clicked(LINK(this, FmSearchDialog, OnClickedSearchAgain)); + m_ppbApproxSettings->connect_clicked(LINK(this, FmSearchDialog, OnClickedSpecialSettings)); + m_pSoundsLikeCJKSettings->connect_clicked(LINK(this, FmSearchDialog, OnClickedSpecialSettings)); + + m_plbPosition->connect_changed(LINK(this, FmSearchDialog, OnPositionSelected)); + m_plbField->connect_changed(LINK(this, FmSearchDialog, OnFieldSelected)); + + m_pcmbSearchText->connect_changed(LINK(this, FmSearchDialog, OnSearchTextModified)); + m_pcmbSearchText->set_entry_completion(false); + m_pcmbSearchText->connect_focus_in(LINK(this, FmSearchDialog, OnFocusGrabbed)); + + m_pcbUseFormat->connect_toggled(LINK(this, FmSearchDialog, OnCheckBoxToggled)); + m_pcbBackwards->connect_toggled(LINK(this, FmSearchDialog, OnCheckBoxToggled)); + m_pcbStartOver->connect_toggled(LINK(this, FmSearchDialog, OnCheckBoxToggled)); + m_pcbCase->connect_toggled(LINK(this, FmSearchDialog, OnCheckBoxToggled)); + m_pcbWildCard->connect_toggled(LINK(this, FmSearchDialog, OnCheckBoxToggled)); + m_pcbRegular->connect_toggled(LINK(this, FmSearchDialog, OnCheckBoxToggled)); + m_pcbApprox->connect_toggled(LINK(this, FmSearchDialog, OnCheckBoxToggled)); + m_pHalfFullFormsCJK->connect_toggled(LINK(this, FmSearchDialog, OnCheckBoxToggled)); + m_pSoundsLikeCJK->connect_toggled(LINK(this, FmSearchDialog, OnCheckBoxToggled)); + + // fill the listboxes + // method of field comparison + const TranslateId aResIds[] = { + RID_STR_SEARCH_ANYWHERE, + RID_STR_SEARCH_BEGINNING, + RID_STR_SEARCH_END, + RID_STR_SEARCH_WHOLE + }; + for (auto const & pResId : aResIds) + m_plbPosition->append_text(CuiResId(pResId)); + m_plbPosition->set_active(MATCHING_ANYWHERE); + + // the field listbox + if (!strVisibleFields.empty()) + { + sal_Int32 nPos {0}; + do { + m_plbField->append_text(OUString(o3tl::getToken(strVisibleFields, 0, ';', nPos))); + } while (nPos>=0); + } + + + m_pConfig.reset( new FmSearchConfigItem ); + LoadParams(); + + m_pcmbSearchText->set_entry_text(sInitialText); + // if the Edit-line has changed the text (e.g. because it contains + // control characters, as can be the case with memo fields), I use + // an empty OUString. + OUString sRealSetText = m_pcmbSearchText->get_active_text(); + if (sRealSetText != sInitialText) + m_pcmbSearchText->set_entry_text(OUString()); + OnSearchTextModified(*m_pcmbSearchText); + + // initial + EnableSearchUI(true); + + if ( m_prbSearchForText->get_active() ) + m_pcmbSearchText->grab_focus(); + +} + +short FmSearchDialog::run() +{ + short nRet = weld::GenericDialogController::run(); + m_pSearchEngine->CancelSearch(); + return nRet; +} + +IMPL_LINK(FmSearchDialog, OnToggledSearchRadio, weld::Toggleable&, rButton, void) +{ + if (!rButton.get_active()) + return; + EnableSearchForDependees(true); +} + +IMPL_LINK(FmSearchDialog, OnToggledFieldRadios, weld::Toggleable&, rButton, void) +{ + if (!rButton.get_active()) + return; + + // en- or disable field list box accordingly + if (m_prbSingleField->get_active()) + { + m_plbField->set_sensitive(true); + m_pSearchEngine->RebuildUsedFields(m_plbField->get_active()); + } + else + { + m_plbField->set_sensitive(false); + m_pSearchEngine->RebuildUsedFields(-1); + } +} + +IMPL_LINK_NOARG(FmSearchDialog, OnClickedSearchAgain, weld::Button&, void) +{ + if (m_pbClose->get_sensitive()) + { // the button has the function 'search' + OUString strThisRoundText = m_pcmbSearchText->get_active_text(); + // to history + m_pcmbSearchText->remove_text(strThisRoundText); + m_pcmbSearchText->insert_text(0, strThisRoundText); + // the remove/insert makes sure that a) the OUString does not appear twice and + // that b) the last searched strings are at the beginning and limit the list length + while (m_pcmbSearchText->get_count() > MAX_HISTORY_ENTRIES) + m_pcmbSearchText->remove(m_pcmbSearchText->get_count()-1); + + // take out the 'overflow' hint + m_pftHint->set_label(OUString()); + + if (m_pcbStartOver->get_active()) + { + m_pcbStartOver->set_active(false); + EnableSearchUI(false); + if (m_prbSearchForText->get_active()) + m_pSearchEngine->StartOver(strThisRoundText); + else + m_pSearchEngine->StartOverSpecial(m_prbSearchForNull->get_active()); + } + else + { + EnableSearchUI(false); + if (m_prbSearchForText->get_active()) + m_pSearchEngine->SearchNext(strThisRoundText); + else + m_pSearchEngine->SearchNextSpecial(m_prbSearchForNull->get_active()); + } + } + else + { // the button has the function 'cancel' + // the CancelButton is usually only disabled, when working in a thread or with reschedule + m_pSearchEngine->CancelSearch(); + // the ProgressHandler is called when it's really finished, here it's only a demand + } +} + +IMPL_LINK(FmSearchDialog, OnClickedSpecialSettings, weld::Button&, rButton, void) +{ + if (m_ppbApproxSettings.get() == &rButton) + { + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractSvxSearchSimilarityDialog> pDlg(pFact->CreateSvxSearchSimilarityDialog(m_xDialog.get(), m_pSearchEngine->GetLevRelaxed(), m_pSearchEngine->GetLevOther(), + m_pSearchEngine->GetLevShorter(), m_pSearchEngine->GetLevLonger() )); + if (pDlg->Execute() == RET_OK) + { + m_pSearchEngine->SetLevRelaxed( pDlg->IsRelaxed() ); + m_pSearchEngine->SetLevOther( pDlg->GetOther() ); + m_pSearchEngine->SetLevShorter(pDlg->GetShorter() ); + m_pSearchEngine->SetLevLonger( pDlg->GetLonger() ); + } + } + else if (m_pSoundsLikeCJKSettings.get() == &rButton) + { + SfxItemSet aSet( SfxGetpApp()->GetPool() ); + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractSvxJSearchOptionsDialog> aDlg(pFact->CreateSvxJSearchOptionsDialog(m_xDialog.get(), aSet, m_pSearchEngine->GetTransliterationFlags() )); + aDlg->Execute(); + + TransliterationFlags nFlags = aDlg->GetTransliterationFlags(); + m_pSearchEngine->SetTransliterationFlags(nFlags); + + m_pcbCase->set_active(m_pSearchEngine->GetCaseSensitive()); + OnCheckBoxToggled( *m_pcbCase ); + m_pHalfFullFormsCJK->set_active( !m_pSearchEngine->GetIgnoreWidthCJK() ); + OnCheckBoxToggled( *m_pHalfFullFormsCJK ); + } +} + +IMPL_LINK_NOARG(FmSearchDialog, OnSearchTextModified, weld::ComboBox&, void) +{ + if ((!m_pcmbSearchText->get_active_text().isEmpty()) || !m_prbSearchForText->get_active()) + m_pbSearchAgain->set_sensitive(true); + else + m_pbSearchAgain->set_sensitive(false); + + m_pSearchEngine->InvalidatePreviousLoc(); +} + +IMPL_LINK_NOARG(FmSearchDialog, OnFocusGrabbed, weld::Widget&, void) +{ + m_pcmbSearchText->select_entry_region(0, -1); +} + +IMPL_LINK_NOARG(FmSearchDialog, OnPositionSelected, weld::ComboBox&, void) +{ + m_pSearchEngine->SetPosition(m_plbPosition->get_active()); +} + +IMPL_LINK_NOARG(FmSearchDialog, OnFieldSelected, weld::ComboBox&, void) +{ + m_pSearchEngine->RebuildUsedFields(m_prbAllFields->get_active() ? -1 : m_plbField->get_active()); + // calls m_pSearchEngine->InvalidatePreviousLoc too + + int nCurrentContext = m_plbForm->get_active(); + if (nCurrentContext != -1) + m_arrContextFields[nCurrentContext] = m_plbField->get_active_text(); +} + +IMPL_LINK(FmSearchDialog, OnCheckBoxToggled, weld::Toggleable&, rBox, void) +{ + bool bChecked = rBox.get_active(); + + // formatter or case -> pass on to the engine + if (&rBox == m_pcbUseFormat.get()) + m_pSearchEngine->SetFormatterUsing(bChecked); + else if (&rBox == m_pcbCase.get()) + m_pSearchEngine->SetCaseSensitive(bChecked); + // direction -> pass on and reset the checkbox-text for StartOver + else if (&rBox == m_pcbBackwards.get()) + { + m_pcbStartOver->set_label( CuiResId( bChecked ? RID_STR_FROM_BOTTOM : RID_STR_FROM_TOP ) ); + m_pSearchEngine->SetDirection(!bChecked); + } + // similarity-search or regular expression + else if ((&rBox == m_pcbApprox.get()) || (&rBox == m_pcbRegular.get()) || (&rBox == m_pcbWildCard.get())) + { + weld::CheckButton* pBoxes[] = { m_pcbWildCard.get(), m_pcbRegular.get(), m_pcbApprox.get() }; + for (weld::CheckButton* pBoxe : pBoxes) + { + if (pBoxe != &rBox) + { + if (bChecked) + pBoxe->set_sensitive(false); + else + pBoxe->set_sensitive(true); + } + } + + // pass on to the engine + m_pSearchEngine->SetWildcard(m_pcbWildCard->get_sensitive() && m_pcbWildCard->get_active()); + m_pSearchEngine->SetRegular(m_pcbRegular->get_sensitive() && m_pcbRegular->get_active()); + m_pSearchEngine->SetLevenshtein(m_pcbApprox->get_sensitive() && m_pcbApprox->get_active()); + // (disabled boxes have to be passed to the engine as sal_False) + + // adjust the Position-Listbox (which is not allowed during Wildcard-search) + if (&rBox == m_pcbWildCard.get()) + { + if (bChecked) + { + m_pftPosition->set_sensitive(false); + m_plbPosition->set_sensitive(false); + } + else + { + m_pftPosition->set_sensitive(true); + m_plbPosition->set_sensitive(true); + } + } + + // and the button for similarity-search + if (&rBox == m_pcbApprox.get()) + { + if (bChecked) + m_ppbApproxSettings->set_sensitive(true); + else + m_ppbApproxSettings->set_sensitive(false); + } + } + else if (&rBox == m_pHalfFullFormsCJK.get()) + { + // forward to the search engine + m_pSearchEngine->SetIgnoreWidthCJK( !bChecked ); + } + else if (&rBox == m_pSoundsLikeCJK.get()) + { + m_pSoundsLikeCJKSettings->set_sensitive(bChecked); + + // two other buttons which depend on this one + bool bEnable = ( m_prbSearchForText->get_active() + && !m_pSoundsLikeCJK->get_active() + ) + || !SvtCJKOptions::IsJapaneseFindEnabled(); + m_pcbCase->set_sensitive(bEnable); + m_pHalfFullFormsCJK->set_sensitive(bEnable); + + // forward to the search engine + m_pSearchEngine->SetTransliteration( bChecked ); + } +} + +void FmSearchDialog::InitContext(sal_Int16 nContext) +{ + FmSearchContext fmscContext; + fmscContext.nContext = nContext; + + sal_uInt32 nResult = m_lnkContextSupplier.Call(fmscContext); + DBG_ASSERT(nResult > 0, "FmSearchDialog::InitContext : ContextSupplier didn't give me any controls !"); + + // put the field names into the respective listbox + m_plbField->clear(); + + if (!fmscContext.sFieldDisplayNames.isEmpty()) + { + // use the display names if supplied + DBG_ASSERT(comphelper::string::getTokenCount(fmscContext.sFieldDisplayNames, ';') == comphelper::string::getTokenCount(fmscContext.strUsedFields, ';'), + "FmSearchDialog::InitContext : invalid context description supplied !"); + sal_Int32 nPos {0}; + do { + m_plbField->append_text(fmscContext.sFieldDisplayNames.getToken(0, ';', nPos)); + } while (nPos>=0); + } + else if (!fmscContext.strUsedFields.isEmpty()) + { + // else use the field names + sal_Int32 nPos {0}; + do { + m_plbField->append_text(fmscContext.strUsedFields.getToken(0, ';', nPos)); + } while (nPos>=0); + } + + if (nContext < static_cast<sal_Int32>(m_arrContextFields.size()) && !m_arrContextFields[nContext].isEmpty()) + { + m_plbField->set_active_text(m_arrContextFields[nContext]); + } + else + { + m_plbField->set_active(0); + if (m_prbSingleField->get_active() && (m_plbField->get_count() > 1)) + m_plbField->grab_focus(); + } + + m_pSearchEngine->SwitchToContext(fmscContext.xCursor, fmscContext.strUsedFields, fmscContext.arrFields, + m_prbAllFields->get_active() ? -1 : 0); + + m_pftRecord->set_label(OUString::number(fmscContext.xCursor->getRow())); +} + +IMPL_LINK(FmSearchDialog, OnContextSelection, weld::ComboBox&, rBox, void) +{ + InitContext(rBox.get_active()); +} + +void FmSearchDialog::EnableSearchUI(bool bEnable) +{ + // the search button has two functions -> adjust its text accordingly + OUString sButtonText( bEnable ? m_sSearch : m_sCancel ); + m_pbSearchAgain->set_label(sButtonText); + + m_prbSearchForText->set_sensitive(bEnable); + m_prbSearchForNull->set_sensitive(bEnable); + m_prbSearchForNotNull->set_sensitive(bEnable); + m_plbForm->set_sensitive(bEnable); + m_prbAllFields->set_sensitive(bEnable); + m_prbSingleField->set_sensitive(bEnable); + m_plbField->set_sensitive(bEnable && m_prbSingleField->get_active()); + m_pcbBackwards->set_sensitive(bEnable); + m_pcbStartOver->set_sensitive(bEnable); + m_pbClose->set_sensitive(bEnable); + EnableSearchForDependees(bEnable); + + if ( !bEnable ) + { // this means we're preparing for starting a search + // In this case, EnableSearchForDependees disabled the search button + // But as we're about to use it for cancelling the search, we really need to enable it, again + m_pbSearchAgain->set_sensitive(true); + } +} + +void FmSearchDialog::EnableSearchForDependees(bool bEnable) +{ + bool bSearchingForText = m_prbSearchForText->get_active(); + m_pbSearchAgain->set_sensitive(bEnable && (!bSearchingForText || (!m_pcmbSearchText->get_active_text().isEmpty()))); + + bEnable = bEnable && bSearchingForText; + + bool bEnableRedundants = !m_pSoundsLikeCJK->get_active() || !SvtCJKOptions::IsJapaneseFindEnabled(); + + m_pcmbSearchText->set_sensitive(bEnable); + m_pftPosition->set_sensitive(bEnable && !m_pcbWildCard->get_active()); + m_pcbWildCard->set_sensitive(bEnable && !m_pcbRegular->get_active() && !m_pcbApprox->get_active()); + m_pcbRegular->set_sensitive(bEnable && !m_pcbWildCard->get_active() && !m_pcbApprox->get_active()); + m_pcbApprox->set_sensitive(bEnable && !m_pcbWildCard->get_active() && !m_pcbRegular->get_active()); + m_ppbApproxSettings->set_sensitive(bEnable && m_pcbApprox->get_active()); + m_pHalfFullFormsCJK->set_sensitive(bEnable && bEnableRedundants); + m_pSoundsLikeCJK->set_sensitive(bEnable); + m_pSoundsLikeCJKSettings->set_sensitive(bEnable && m_pSoundsLikeCJK->get_active()); + m_plbPosition->set_sensitive(bEnable && !m_pcbWildCard->get_active()); + m_pcbUseFormat->set_sensitive(bEnable); + m_pcbCase->set_sensitive(bEnable && bEnableRedundants); +} + +void FmSearchDialog::OnFound(const css::uno::Any& aCursorPos, sal_Int16 nFieldPos) +{ + FmFoundRecordInformation friInfo; + friInfo.nContext = m_plbForm->get_active(); + // if I don't do a search in a context, this has an invalid value - but then it doesn't matter anyway + friInfo.aPosition = aCursorPos; + if (m_prbAllFields->get_active()) + friInfo.nFieldPos = nFieldPos; + else + friInfo.nFieldPos = m_plbField->get_active(); + // this of course implies that I have really searched in the field that is selected in the listbox, + // which is made sure in RebuildUsedFields + + m_lnkFoundHandler.Call(friInfo); + + m_pcmbSearchText->grab_focus(); +} + +IMPL_LINK(FmSearchDialog, OnSearchProgress, const FmSearchProgress*, pProgress, void) +{ + SolarMutexGuard aGuard; + // make this single method thread-safe (it's an overkill to block the whole application for this, + // but we don't have another safety concept at the moment) + + switch (pProgress->aSearchState) + { + case FmSearchProgress::State::Progress: + if (pProgress->bOverflow) + { + OUString sHint( CuiResId( m_pcbBackwards->get_active() ? RID_STR_OVERFLOW_BACKWARD : RID_STR_OVERFLOW_FORWARD ) ); + m_pftHint->set_label( sHint ); + } + + m_pftRecord->set_label(OUString::number(1 + pProgress->nCurrentRecord)); + break; + + case FmSearchProgress::State::ProgressCounting: + m_pftHint->set_label(CuiResId(RID_STR_SEARCH_COUNTING)); + m_pftRecord->set_label(OUString::number(pProgress->nCurrentRecord)); + break; + + case FmSearchProgress::State::Successful: + OnFound(pProgress->aBookmark, static_cast<sal_Int16>(pProgress->nFieldIndex)); + EnableSearchUI(true); + break; + + case FmSearchProgress::State::Error: + case FmSearchProgress::State::NothingFound: + { + TranslateId pErrorId = (FmSearchProgress::State::Error == pProgress->aSearchState) + ? RID_STR_SEARCH_GENERAL_ERROR + : RID_STR_SEARCH_NORECORD; + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, CuiResId(pErrorId))); + xBox->run(); + [[fallthrough]]; + } + case FmSearchProgress::State::Canceled: + EnableSearchUI(true); + if (m_lnkCanceledNotFoundHdl.IsSet()) + { + FmFoundRecordInformation friInfo; + friInfo.nContext = m_plbForm->get_active(); + // if I don't do a search in a context, this has an invalid value - but then it doesn't matter anyway + friInfo.aPosition = pProgress->aBookmark; + m_lnkCanceledNotFoundHdl.Call(friInfo); + } + break; + } + + m_pftRecord->set_label(OUString::number(1 + pProgress->nCurrentRecord)); +} + +void FmSearchDialog::LoadParams() +{ + FmSearchParams aParams(m_pConfig->getParams()); + + const OUString* pHistory = aParams.aHistory.getConstArray(); + const OUString* pHistoryEnd = pHistory + aParams.aHistory.getLength(); + for (; pHistory != pHistoryEnd; ++pHistory) + m_pcmbSearchText->append_text( *pHistory ); + + // I do the settings at my UI-elements and then I simply call the respective change-handler, + // that way the data is handed on to the SearchEngine and all dependent settings are done + + // current field + int nInitialField = m_plbField->find_text( aParams.sSingleSearchField ); + if (nInitialField == -1) + nInitialField = 0; + m_plbField->set_active(nInitialField); + OnFieldSelected(*m_plbField); + // all fields/single field (AFTER selecting the field because OnToggledFieldRadios expects a valid value there) + if (aParams.bAllFields) + { + m_prbSingleField->set_active(false); + m_prbAllFields->set_active(true); + OnToggledFieldRadios(*m_prbAllFields); + // OnToggledFieldRadios also calls to RebuildUsedFields + } + else + { + m_prbAllFields->set_active(false); + m_prbSingleField->set_active(true); + OnToggledFieldRadios(*m_prbSingleField); + } + + m_plbPosition->set_active(aParams.nPosition); + OnPositionSelected(*m_plbPosition); + + // field formatting/case sensitivity/direction + m_pcbUseFormat->set_active(aParams.bUseFormatter); + m_pcbCase->set_active( aParams.isCaseSensitive() ); + m_pcbBackwards->set_active(aParams.bBackwards); + OnCheckBoxToggled(*m_pcbUseFormat); + OnCheckBoxToggled(*m_pcbCase); + OnCheckBoxToggled(*m_pcbBackwards); + + m_pHalfFullFormsCJK->set_active( !aParams.isIgnoreWidthCJK( ) ); // BEWARE: this checkbox has an inverse semantics! + m_pSoundsLikeCJK->set_active( aParams.bSoundsLikeCJK ); + OnCheckBoxToggled(*m_pHalfFullFormsCJK); + OnCheckBoxToggled(*m_pSoundsLikeCJK); + + m_pcbWildCard->set_active(false); + m_pcbRegular->set_active(false); + m_pcbApprox->set_active(false); + OnCheckBoxToggled(*m_pcbWildCard); + OnCheckBoxToggled(*m_pcbRegular); + OnCheckBoxToggled(*m_pcbApprox); + + weld::CheckButton* pToCheck = nullptr; + if (aParams.bWildcard) + pToCheck = m_pcbWildCard.get(); + if (aParams.bRegular) + pToCheck = m_pcbRegular.get(); + if (aParams.bApproxSearch) + pToCheck = m_pcbApprox.get(); + if (aParams.bSoundsLikeCJK) + pToCheck = m_pSoundsLikeCJK.get(); + if (pToCheck) + { + pToCheck->set_active(true); + OnCheckBoxToggled(*pToCheck); + } + + // set Levenshtein-parameters directly at the SearchEngine + m_pSearchEngine->SetLevRelaxed(aParams.bLevRelaxed); + m_pSearchEngine->SetLevOther(aParams.nLevOther); + m_pSearchEngine->SetLevShorter(aParams.nLevShorter); + m_pSearchEngine->SetLevLonger(aParams.nLevLonger); + + m_pSearchEngine->SetTransliterationFlags( aParams.getTransliterationFlags( ) ); + + m_prbSearchForText->set_active(false); + m_prbSearchForNull->set_active(false); + m_prbSearchForNotNull->set_active(false); + switch (aParams.nSearchForType) + { + case 1: m_prbSearchForNull->set_active(true); break; + case 2: m_prbSearchForNotNull->set_active(true); break; + default: m_prbSearchForText->set_active(true); break; + } + OnToggledFieldRadios(*m_prbSearchForText); +} + +void FmSearchDialog::SaveParams() const +{ + if (!m_pConfig) + return; + + FmSearchParams aCurrentSettings; + + int nCount = m_pcmbSearchText->get_count(); + aCurrentSettings.aHistory.realloc(nCount); + OUString* pHistory = aCurrentSettings.aHistory.getArray(); + for (int i = 0; i < nCount; ++i, ++pHistory) + *pHistory = m_pcmbSearchText->get_text(i); + + aCurrentSettings.sSingleSearchField = m_plbField->get_active_text(); + aCurrentSettings.bAllFields = m_prbAllFields->get_active(); + aCurrentSettings.nPosition = m_pSearchEngine->GetPosition(); + aCurrentSettings.bUseFormatter = m_pSearchEngine->GetFormatterUsing(); + aCurrentSettings.setCaseSensitive ( m_pSearchEngine->GetCaseSensitive() ); + aCurrentSettings.bBackwards = !m_pSearchEngine->GetDirection(); + aCurrentSettings.bWildcard = m_pSearchEngine->GetWildcard(); + aCurrentSettings.bRegular = m_pSearchEngine->GetRegular(); + aCurrentSettings.bApproxSearch = m_pSearchEngine->GetLevenshtein(); + aCurrentSettings.bLevRelaxed = m_pSearchEngine->GetLevRelaxed(); + aCurrentSettings.nLevOther = m_pSearchEngine->GetLevOther(); + aCurrentSettings.nLevShorter = m_pSearchEngine->GetLevShorter(); + aCurrentSettings.nLevLonger = m_pSearchEngine->GetLevLonger(); + + aCurrentSettings.bSoundsLikeCJK = m_pSearchEngine->GetTransliteration(); + aCurrentSettings.setTransliterationFlags ( m_pSearchEngine->GetTransliterationFlags() ); + + if (m_prbSearchForNull->get_active()) + aCurrentSettings.nSearchForType = 1; + else if (m_prbSearchForNotNull->get_active()) + aCurrentSettings.nSearchForType = 2; + else + aCurrentSettings.nSearchForType = 0; + + m_pConfig->setParams( aCurrentSettings ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/cuigaldlg.cxx b/cui/source/dialogs/cuigaldlg.cxx new file mode 100644 index 000000000..e432b4fce --- /dev/null +++ b/cui/source/dialogs/cuigaldlg.cxx @@ -0,0 +1,1009 @@ +/* -*- 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 <config_features.h> + +#include <sal/config.h> + +#include <algorithm> +#include <cassert> + +#include <utility> +#include <vcl/errinf.hxx> +#include <ucbhelper/content.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <avmedia/mediawindow.hxx> +#include <unotools/pathoptions.hxx> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/opengrf.hxx> +#include <vcl/graphicfilter.hxx> +#include <svx/gallery1.hxx> +#include <svx/galtheme.hxx> +#include <cuigaldlg.hxx> +#include <bitmaps.hlst> +#include <unotools/localedatawrapper.hxx> +#include <unotools/syslocale.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/ui/dialogs/XAsynchronousExecutableDialog.hpp> +#include <dialmgr.hxx> +#include <strings.hrc> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <osl/diagnose.h> +#include <o3tl/string_view.hxx> + +using namespace ::ucbhelper; +using namespace ::cppu; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::ui::dialogs; +using namespace ::com::sun::star::uno; + + +SearchThread::SearchThread(SearchProgress* pProgress, + TPGalleryThemeProperties* pBrowser, + INetURLObject aStartURL) + : Thread("cuiSearchThread") + , mpProgress(pProgress) + , mpBrowser(pBrowser) + , maStartURL(std::move(aStartURL)) +{ +} + +SearchThread::~SearchThread() +{ +} + +void SearchThread::execute() +{ + const OUString aFileType(mpBrowser->m_xCbbFileType->get_active_text()); + + if (!aFileType.isEmpty()) + { + const int nFileNumber = mpBrowser->m_xCbbFileType->find_text(aFileType); + sal_Int32 nBeginFormat, nEndFormat; + std::vector< OUString > aFormats; + + if( !nFileNumber || nFileNumber == -1) + { + nBeginFormat = 1; + nEndFormat = mpBrowser->m_xCbbFileType->get_count() - 1; + } + else + nBeginFormat = nEndFormat = nFileNumber; + + for (sal_Int32 i = nBeginFormat; i <= nEndFormat; ++i) + aFormats.push_back( mpBrowser->aFilterEntryList[ i ]->aFilterName.toAsciiLowerCase() ); + + ImplSearch( maStartURL, aFormats, mpBrowser->bSearchRecursive ); + } + + Application::PostUserEvent(LINK(mpProgress, SearchProgress, CleanUpHdl)); +} + + +void SearchThread::ImplSearch( const INetURLObject& rStartURL, + const std::vector< OUString >& rFormats, + bool bRecursive ) +{ + { + SolarMutexGuard aGuard; + + mpProgress->SetDirectory( rStartURL ); + } + + try + { + css::uno::Reference< XCommandEnvironment > xEnv; + Content aCnt( rStartURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext() ); + Sequence< OUString > aProps( 2 ); + + aProps.getArray()[ 0 ] = "IsFolder"; + aProps.getArray()[ 1 ] = "IsDocument"; + css::uno::Reference< XResultSet > xResultSet( + aCnt.createCursor( aProps ) ); + + if( xResultSet.is() ) + { + css::uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY_THROW ); + css::uno::Reference< XRow > xRow( xResultSet, UNO_QUERY_THROW ); + + while( xResultSet->next() && schedule() ) + { + INetURLObject aFoundURL( xContentAccess->queryContentIdentifierString() ); + DBG_ASSERT( aFoundURL.GetProtocol() != INetProtocol::NotValid, "invalid URL" ); + + bool bFolder = xRow->getBoolean( 1 ); // property "IsFolder" + if ( xRow->wasNull() ) + bFolder = false; + + if( bRecursive && bFolder ) + ImplSearch( aFoundURL, rFormats, true ); + else + { + bool bDocument = xRow->getBoolean( 2 ); // property "IsDocument" + if ( xRow->wasNull() ) + bDocument = false; + + if( bDocument ) + { + GraphicDescriptor aDesc( aFoundURL ); + + if( ( aDesc.Detect() && + std::find( rFormats.begin(), + rFormats.end(), + GraphicDescriptor::GetImportFormatShortName( + aDesc.GetFileFormat() ).toAsciiLowerCase() ) + != rFormats.end() ) || + std::find( rFormats.begin(), + rFormats.end(), + aFoundURL.GetFileExtension().toAsciiLowerCase()) + != rFormats.end() ) + { + SolarMutexGuard aGuard; + + mpBrowser->aFoundList.push_back( + aFoundURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) + ); + mpBrowser->m_xLbxFound->insert_text( + mpBrowser->aFoundList.size() - 1, + GetReducedString(aFoundURL, 50)); + } + } + } + } + } + } + catch (const ContentCreationException&) + { + } + catch (const css::uno::RuntimeException&) + { + } + catch (const css::uno::Exception&) + { + } +} + +SearchProgress::SearchProgress(weld::Window* pParent, TPGalleryThemeProperties* pTabPage, INetURLObject aStartURL) + : GenericDialogController(pParent, "cui/ui/gallerysearchprogress.ui", "GallerySearchProgress") + , startUrl_(std::move(aStartURL)) + , m_pTabPage(pTabPage) + , m_xFtSearchDir(m_xBuilder->weld_label("dir")) + , m_xFtSearchType(m_xBuilder->weld_label("file")) + , m_xBtnCancel(m_xBuilder->weld_button("cancel")) +{ + m_xFtSearchType->set_size_request(m_xFtSearchType->get_preferred_size().Width(), -1); + m_xBtnCancel->connect_clicked(LINK(this, SearchProgress, ClickCancelBtn)); +} + +SearchProgress::~SearchProgress() +{ +} + +IMPL_LINK_NOARG(SearchProgress, ClickCancelBtn, weld::Button&, void) +{ + if (m_aSearchThread.is()) + m_aSearchThread->terminate(); +} + +IMPL_LINK_NOARG(SearchProgress, CleanUpHdl, void*, void) +{ + if (m_aSearchThread.is()) + m_aSearchThread->join(); + + m_xDialog->response(RET_OK); +} + +void SearchProgress::LaunchThread() +{ + assert(!m_aSearchThread.is()); + m_aSearchThread = new SearchThread(this, m_pTabPage, startUrl_); + m_aSearchThread->launch(); +} + +TakeThread::TakeThread( + TakeProgress* pProgress, + TPGalleryThemeProperties* pBrowser, + TokenList_impl& rTakenList +) : + Thread ( "cuiTakeThread" ), + mpProgress ( pProgress ), + mpBrowser ( pBrowser ), + mrTakenList ( rTakenList ) +{ +} + + +TakeThread::~TakeThread() +{ +} + +void TakeThread::execute() +{ + sal_Int32 nEntries; + GalleryTheme* pThm = mpBrowser->GetXChgData()->pTheme; + std::unique_ptr<GalleryProgress> pStatusProgress; + + std::vector<int> aSelectedRows; + + { + SolarMutexGuard aGuard; + pStatusProgress.reset(new GalleryProgress); + if (mpBrowser->bTakeAll) + nEntries = mpBrowser->m_xLbxFound->n_children(); + else + { + aSelectedRows = mpBrowser->m_xLbxFound->get_selected_rows(); + nEntries = aSelectedRows.size(); + } + pThm->LockBroadcaster(); + } + + for( sal_Int32 i = 0; i < nEntries && schedule(); ++i ) + { + const sal_Int32 nPos = mpBrowser->bTakeAll ? i : aSelectedRows[i]; + const INetURLObject aURL( mpBrowser->aFoundList[ nPos ]); + + mrTakenList.push_back( nPos ); + + { + SolarMutexGuard aGuard; + + mpProgress->SetFile( aURL ); + pStatusProgress->Update( i, nEntries - 1 ); + pThm->InsertURL( aURL ); + } + } + + { + SolarMutexGuard aGuard; + + pThm->UnlockBroadcaster(); + pStatusProgress.reset(); + } + + Application::PostUserEvent(LINK(mpProgress, TakeProgress, CleanUpHdl)); +} + +TakeProgress::TakeProgress(weld::Window* pParent, TPGalleryThemeProperties* pTabPage) + : GenericDialogController(pParent, "cui/ui/galleryapplyprogress.ui", + "GalleryApplyProgress") + , m_pParent(pParent) + , m_pTabPage(pTabPage) + , m_xFtTakeFile(m_xBuilder->weld_label("file")) + , m_xBtnCancel(m_xBuilder->weld_button("cancel")) +{ + m_xBtnCancel->connect_clicked(LINK(this, TakeProgress, ClickCancelBtn)); +} + +TakeProgress::~TakeProgress() +{ +} + +IMPL_LINK_NOARG(TakeProgress, ClickCancelBtn, weld::Button&, void) +{ + if (maTakeThread.is()) + maTakeThread->terminate(); +} + + +IMPL_LINK_NOARG(TakeProgress, CleanUpHdl, void*, void) +{ + if (maTakeThread.is()) + maTakeThread->join(); + + std::vector<bool, std::allocator<bool> > aRemoveEntries(m_pTabPage->aFoundList.size(), false); + std::vector< OUString > aRemainingVector; + sal_uInt32 i, nCount; + + std::unique_ptr<weld::WaitObject> xWait(new weld::WaitObject(m_pParent)); + + m_pTabPage->m_xLbxFound->select(-1); + m_pTabPage->m_xLbxFound->freeze(); + + // mark all taken positions in aRemoveEntries + for( i = 0, nCount = maTakenList.size(); i < nCount; ++i ) + aRemoveEntries[ maTakenList[ i ] ] = true; + maTakenList.clear(); + + // refill found list + for( i = 0, nCount = aRemoveEntries.size(); i < nCount; ++i ) + if( !aRemoveEntries[ i ] ) + aRemainingVector.push_back( m_pTabPage->aFoundList[i] ); + + std::swap(m_pTabPage->aFoundList, aRemainingVector); + aRemainingVector.clear(); + + // refill list box + for( i = 0, nCount = aRemoveEntries.size(); i < nCount; ++i ) + if( !aRemoveEntries[ i ] ) + aRemainingVector.push_back(m_pTabPage->m_xLbxFound->get_text(i)); + + m_pTabPage->m_xLbxFound->clear(); + for( i = 0, nCount = aRemainingVector.size(); i < nCount; ++i ) + m_pTabPage->m_xLbxFound->append_text(aRemainingVector[i]); + aRemainingVector.clear(); + + m_pTabPage->m_xLbxFound->thaw(); + m_pTabPage->SelectFoundHdl( *m_pTabPage->m_xLbxFound ); + + xWait.reset(); + + m_xDialog->response(RET_OK); +} + +void TakeProgress::LaunchThread() +{ + assert(!maTakeThread.is()); + maTakeThread = new TakeThread(this, m_pTabPage, maTakenList); + maTakeThread->launch(); +} + +ActualizeProgress::ActualizeProgress(weld::Widget* pWindow, GalleryTheme* pThm) + : GenericDialogController(pWindow, "cui/ui/galleryupdateprogress.ui", + "GalleryUpdateProgress") + , pIdle(nullptr) + , pTheme(pThm) + , m_xFtActualizeFile(m_xBuilder->weld_label("file")) + , m_xBtnCancel(m_xBuilder->weld_button("cancel")) +{ + m_xBtnCancel->connect_clicked(LINK(this, ActualizeProgress, ClickCancelBtn)); +} + +ActualizeProgress::~ActualizeProgress() +{ +} + +short ActualizeProgress::run() +{ + pIdle = new Idle("ActualizeProgressTimeout"); + pIdle->SetInvokeHandler( LINK( this, ActualizeProgress, TimeoutHdl ) ); + pIdle->SetPriority( TaskPriority::LOWEST ); + pIdle->Start(); + + return GenericDialogController::run(); +} + +IMPL_LINK_NOARG(ActualizeProgress, ClickCancelBtn, weld::Button&, void) +{ + pTheme->AbortActualize(); + m_xDialog->response(RET_OK); +} + +IMPL_LINK( ActualizeProgress, TimeoutHdl, Timer*, _pTimer, void) +{ + if (_pTimer) + { + _pTimer->Stop(); + delete _pTimer; + } + + pTheme->Actualize(LINK(this, ActualizeProgress, ActualizeHdl), &aStatusProgress); + ClickCancelBtn(*m_xBtnCancel); +} + +IMPL_LINK( ActualizeProgress, ActualizeHdl, const INetURLObject&, rURL, void ) +{ + Application::Reschedule(true); + m_xFtActualizeFile->set_label(GetReducedString(rURL, 30)); +} + +TitleDialog::TitleDialog(weld::Widget* pParent, const OUString& rOldTitle) + : GenericDialogController(pParent, "cui/ui/gallerytitledialog.ui", "GalleryTitleDialog") + , m_xEdit(m_xBuilder->weld_entry("entry")) +{ + m_xEdit->set_text(rOldTitle); + m_xEdit->grab_focus(); +} + +TitleDialog::~TitleDialog() +{ +} + +GalleryIdDialog::GalleryIdDialog(weld::Widget* pParent, GalleryTheme* _pThm) + : GenericDialogController(pParent, "cui/ui/gallerythemeiddialog.ui", "GalleryThemeIDDialog") + , m_pThm(_pThm) + , m_xBtnOk(m_xBuilder->weld_button("ok")) + , m_xLbResName(m_xBuilder->weld_combo_box("entry")) +{ + m_xLbResName->append_text("!!! No Id !!!"); + + GalleryTheme::InsertAllThemes(*m_xLbResName); + + m_xLbResName->set_active(m_pThm->GetId()); + m_xLbResName->grab_focus(); + + m_xBtnOk->connect_clicked(LINK(this, GalleryIdDialog, ClickOkHdl)); +} + +GalleryIdDialog::~GalleryIdDialog() +{ +} + +IMPL_LINK_NOARG(GalleryIdDialog, ClickOkHdl, weld::Button&, void) +{ + Gallery* pGal = m_pThm->GetParent(); + const sal_uInt32 nId = GetId(); + bool bDifferentThemeExists = false; + + for( size_t i = 0, nCount = pGal->GetThemeCount(); i < nCount && !bDifferentThemeExists; i++ ) + { + const GalleryThemeEntry* pInfo = pGal->GetThemeInfo( i ); + + if ((pInfo->GetId() == nId) && (pInfo->GetThemeName() != m_pThm->GetName())) + { + OUString aStr = CuiResId( RID_CUISTR_GALLERY_ID_EXISTS ) + + " (" + pInfo->GetThemeName() + ")"; + + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Info, VclButtonsType::Ok, + aStr)); + xInfoBox->run(); + m_xLbResName->grab_focus(); + bDifferentThemeExists = true; + } + } + + if (!bDifferentThemeExists) + m_xDialog->response(RET_OK); +} + +GalleryThemeProperties::GalleryThemeProperties(weld::Widget* pParent, + ExchangeData* _pData, SfxItemSet const * pItemSet) + : SfxTabDialogController(pParent, "cui/ui/gallerythemedialog.ui", + "GalleryThemeDialog", pItemSet) + , pData(_pData) +{ + AddTabPage("general", TPGalleryThemeGeneral::Create, nullptr); + AddTabPage("files", TPGalleryThemeProperties::Create, nullptr); + if (pData->pTheme->IsReadOnly()) + RemoveTabPage("files"); + + OUString aText = m_xDialog->get_title().replaceFirst( "%1", pData->pTheme->GetName() ); + + if (pData->pTheme->IsReadOnly()) + aText += " " + CuiResId( RID_CUISTR_GALLERY_READONLY ); + + m_xDialog->set_title(aText); +} + +void GalleryThemeProperties::PageCreated(const OString& rId, SfxTabPage &rPage) +{ + if (rId == "general") + static_cast<TPGalleryThemeGeneral&>( rPage ).SetXChgData( pData ); + else + static_cast<TPGalleryThemeProperties&>( rPage ).SetXChgData( pData ); +} + +TPGalleryThemeGeneral::TPGalleryThemeGeneral(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet) + : SfxTabPage(pPage, pController, "cui/ui/gallerygeneralpage.ui", "GalleryGeneralPage", &rSet) + , pData(nullptr) + , m_xFiMSImage(m_xBuilder->weld_image("image")) + , m_xEdtMSName(m_xBuilder->weld_entry("name")) + , m_xFtMSShowType(m_xBuilder->weld_label("type")) + , m_xFtMSShowPath(m_xBuilder->weld_label("location")) + , m_xFtMSShowContent(m_xBuilder->weld_label("contents")) + , m_xFtMSShowChangeDate(m_xBuilder->weld_label("modified")) +{ +} + +void TPGalleryThemeGeneral::SetXChgData( ExchangeData* _pData ) +{ + pData = _pData; + + GalleryTheme* pThm = pData->pTheme; + OUString aOutStr( OUString::number(pThm->GetObjectCount()) ); + OUString aObjStr( CuiResId( RID_CUISTR_GALLERYPROPS_OBJECT ) ); + OUString aAccess; + OUString aType( SvxResId( RID_SVXSTR_GALLERYPROPS_GALTHEME ) ); + bool bReadOnly = pThm->IsReadOnly(); + + m_xEdtMSName->set_text(pThm->GetName()); + m_xEdtMSName->set_editable(!bReadOnly); + m_xEdtMSName->set_sensitive(!bReadOnly); + + if( pThm->IsReadOnly() ) + aType += CuiResId( RID_CUISTR_GALLERY_READONLY ); + + m_xFtMSShowType->set_label(aType); + m_xFtMSShowPath->set_label(pThm->getThemeURL().GetMainURL(INetURLObject::DecodeMechanism::Unambiguous)); + + // singular or plural? + if ( 1 == pThm->GetObjectCount() ) + aObjStr = aObjStr.getToken( 0, ';' ); + else + aObjStr = aObjStr.getToken( 1, ';' ); + + aOutStr += " " + aObjStr; + + m_xFtMSShowContent->set_label(aOutStr); + + // get locale wrapper (singleton) + const SvtSysLocale aSysLocale; + const LocaleDataWrapper& aLocaleData = aSysLocale.GetLocaleData(); + + // ChangeDate/Time + aAccess = aLocaleData.getDate( pData->aThemeChangeDate ) + ", " + aLocaleData.getTime( pData->aThemeChangeTime ); + m_xFtMSShowChangeDate->set_label(aAccess); + + // set image + OUString sId; + + if( pThm->IsReadOnly() ) + sId = RID_SVXBMP_THEME_READONLY_BIG; + else if( pThm->IsDefault() ) + sId = RID_SVXBMP_THEME_DEFAULT_BIG; + else + sId = RID_SVXBMP_THEME_NORMAL_BIG; + + m_xFiMSImage->set_from_icon_name(sId); +} + +bool TPGalleryThemeGeneral::FillItemSet( SfxItemSet* /*rSet*/ ) +{ + pData->aEditedTitle = m_xEdtMSName->get_text(); + return true; +} + +std::unique_ptr<SfxTabPage> TPGalleryThemeGeneral::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rSet) +{ + return std::make_unique<TPGalleryThemeGeneral>(pPage, pController, *rSet); +} + +TPGalleryThemeProperties::TPGalleryThemeProperties(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet) + : SfxTabPage(pPage, pController, "cui/ui/galleryfilespage.ui", "GalleryFilesPage", &rSet) + , pData(nullptr) + , aPreviewTimer("cui TPGalleryThemeProperties aPreviewTimer") + , bEntriesFound(false) + , bInputAllowed(true) + , bTakeAll(false) + , bSearchRecursive(false) + , xDialogListener(new ::svt::DialogClosedListener()) + , m_xCbbFileType(m_xBuilder->weld_combo_box("filetype")) + , m_xLbxFound(m_xBuilder->weld_tree_view("files")) + , m_xBtnSearch(m_xBuilder->weld_button("findfiles")) + , m_xBtnTake(m_xBuilder->weld_button("add")) + , m_xBtnTakeAll(m_xBuilder->weld_button("addall")) + , m_xCbxPreview(m_xBuilder->weld_check_button("preview")) + , m_xWndPreview(new weld::CustomWeld(*m_xBuilder, "image", m_aWndPreview)) +{ + m_xLbxFound->set_size_request(m_xLbxFound->get_approximate_digit_width() * 35, + m_xLbxFound->get_height_rows(15)); + m_xLbxFound->set_selection_mode(SelectionMode::Multiple); + xDialogListener->SetDialogClosedLink( LINK( this, TPGalleryThemeProperties, DialogClosedHdl ) ); +} + +void TPGalleryThemeProperties::SetXChgData( ExchangeData* _pData ) +{ + pData = _pData; + + aPreviewTimer.SetInvokeHandler( LINK( this, TPGalleryThemeProperties, PreviewTimerHdl ) ); + aPreviewTimer.SetTimeout( 500 ); + m_xBtnSearch->connect_clicked(LINK(this, TPGalleryThemeProperties, ClickSearchHdl)); + m_xBtnTake->connect_clicked(LINK(this, TPGalleryThemeProperties, ClickTakeHdl)); + m_xBtnTakeAll->connect_clicked(LINK(this, TPGalleryThemeProperties, ClickTakeAllHdl)); + m_xCbxPreview->connect_toggled(LINK(this, TPGalleryThemeProperties, ClickPreviewHdl)); + m_xCbbFileType->connect_changed(LINK(this, TPGalleryThemeProperties, SelectFileTypeHdl)); + m_xLbxFound->connect_row_activated(LINK(this, TPGalleryThemeProperties, DClickFoundHdl)); + m_xLbxFound->connect_changed(LINK(this, TPGalleryThemeProperties, SelectFoundHdl)); + m_xLbxFound->append_text(CuiResId(RID_CUISTR_GALLERY_NOFILES)); + m_xLbxFound->show(); + + FillFilterList(); + + m_xBtnTake->set_sensitive(true); + m_xBtnTakeAll->set_sensitive(false); + m_xCbxPreview->set_sensitive(false); +} + +void TPGalleryThemeProperties::StartSearchFiles( std::u16string_view _rFolderURL, short _nDlgResult ) +{ + if ( RET_OK == _nDlgResult ) + { + aURL = INetURLObject( _rFolderURL ); + bSearchRecursive = true; // UI choice no longer possible, windows file picker allows no user controls + SearchFiles(); + } +} + +TPGalleryThemeProperties::~TPGalleryThemeProperties() +{ + xMediaPlayer.clear(); + xDialogListener.clear(); + aFilterEntryList.clear(); +} + +std::unique_ptr<SfxTabPage> TPGalleryThemeProperties::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rSet) +{ + return std::make_unique<TPGalleryThemeProperties>(pPage, pController, *rSet); +} + +OUString TPGalleryThemeProperties::addExtension( const OUString& _rDisplayText, std::u16string_view _rExtension ) +{ + OUString sRet = _rDisplayText; + if ( sRet.indexOf( "(*.*)" ) == -1 ) + { + sRet += OUString::Concat(" (") + _rExtension + ")"; + } + return sRet; +} + +void TPGalleryThemeProperties::FillFilterList() +{ + GraphicFilter &rFilter = GraphicFilter::GetGraphicFilter(); + OUString aExt; + OUString aName; + sal_uInt16 i, nKeyCount; + + // graphic filters + for( i = 0, nKeyCount = rFilter.GetImportFormatCount(); i < nKeyCount; i++ ) + { + aExt = rFilter.GetImportFormatShortName( i ); + aName = rFilter.GetImportFormatName( i ); + size_t entryIndex = 0; + FilterEntry* pTestEntry = aFilterEntryList.empty() ? nullptr : aFilterEntryList[ entryIndex ].get(); + bool bInList = false; + + OUString aExtensions; + int j = 0; + OUString sWildcard; + while( true ) + { + sWildcard = rFilter.GetImportWildcard( i, j++ ); + if ( sWildcard.isEmpty() ) + break; + if ( aExtensions.indexOf( sWildcard ) == -1 ) + { + if ( !aExtensions.isEmpty() ) + aExtensions += ";"; + aExtensions += sWildcard; + } + } + aName = addExtension( aName, aExtensions ); + + while( pTestEntry ) + { + if ( pTestEntry->aFilterName == aExt ) + { + bInList = true; + break; + } + pTestEntry = ( ++entryIndex < aFilterEntryList.size() ) + ? aFilterEntryList[ entryIndex ].get() : nullptr; + } + if ( !bInList ) + { + std::unique_ptr<FilterEntry> pFilterEntry(new FilterEntry); + pFilterEntry->aFilterName = aExt; + m_xCbbFileType->append_text(aName); + aFilterEntryList.push_back(std::move(pFilterEntry)); + } + } + +#if HAVE_FEATURE_AVMEDIA + // media filters + static constexpr OUStringLiteral aWildcard = u"*."; + ::avmedia::FilterNameVector aFilters= ::avmedia::MediaWindow::getMediaFilters(); + + for(const std::pair<OUString,OUString> & aFilter : aFilters) + { + for( sal_Int32 nIndex = 0; nIndex >= 0; ) + { + OUString aFilterWildcard( aWildcard ); + + std::unique_ptr<FilterEntry> pFilterEntry(new FilterEntry); + pFilterEntry->aFilterName = aFilter.second.getToken( 0, ';', nIndex ); + aFilterWildcard += pFilterEntry->aFilterName; + m_xCbbFileType->append_text(addExtension(aFilter.first, aFilterWildcard)); + aFilterEntryList.push_back( std::move(pFilterEntry) ); + } + } +#endif + + // 'All' filters + OUString aExtensions; + + // graphic filters + for ( i = 0; i < nKeyCount; ++i ) + { + int j = 0; + OUString sWildcard; + while( true ) + { + sWildcard = rFilter.GetImportWildcard( i, j++ ); + if ( sWildcard.isEmpty() ) + break; + if ( aExtensions.indexOf( sWildcard ) == -1 ) + { + if ( !aExtensions.isEmpty() ) + aExtensions += ";"; + + aExtensions += sWildcard; + } + } + } + +#if HAVE_FEATURE_AVMEDIA + // media filters + for(const std::pair<OUString,OUString> & aFilter : aFilters) + { + for( sal_Int32 nIndex = 0; nIndex >= 0; ) + { + if ( !aExtensions.isEmpty() ) + aExtensions += ";"; + aExtensions += OUString::Concat(aWildcard) + o3tl::getToken(aFilter.second, 0, ';', nIndex ); + } + } +#endif + +#if defined(_WIN32) + if (aExtensions.getLength() > 240) + aExtensions = "*.*"; +#endif + + std::unique_ptr<FilterEntry> pFilterEntry(new FilterEntry); + pFilterEntry->aFilterName = CuiResId(RID_CUISTR_GALLERY_ALLFILES); + pFilterEntry->aFilterName = addExtension(pFilterEntry->aFilterName, aExtensions); + m_xCbbFileType->insert_text(0, pFilterEntry->aFilterName); + m_xCbbFileType->set_active(0); + aFilterEntryList.insert(aFilterEntryList.begin(), std::move(pFilterEntry)); +} + +IMPL_LINK_NOARG(TPGalleryThemeProperties, SelectFileTypeHdl, weld::ComboBox&, void) +{ + OUString aText(m_xCbbFileType->get_active_text()); + + if( bInputAllowed && ( aLastFilterName != aText ) ) + { + aLastFilterName = aText; + + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(GetFrameWeld(), "cui/ui/queryupdategalleryfilelistdialog.ui")); + std::unique_ptr<weld::MessageDialog> xQuery(xBuilder->weld_message_dialog("QueryUpdateFileListDialog")); + if (xQuery->run() == RET_YES) + SearchFiles(); + } +} + +void TPGalleryThemeProperties::SearchFiles() +{ + auto xProgress = std::make_shared<SearchProgress>(GetFrameWeld(), this, aURL); + + aFoundList.clear(); + m_xLbxFound->clear(); + + xProgress->SetFileType( m_xCbbFileType->get_active_text() ); + xProgress->SetDirectory( INetURLObject() ); + + xProgress->LaunchThread(); + weld::DialogController::runAsync(xProgress, [this](sal_Int32 nResult) { + EndSearchProgressHdl(nResult); + }); +} + +IMPL_LINK_NOARG(TPGalleryThemeProperties, ClickSearchHdl, weld::Button&, void) +{ + if( !bInputAllowed ) + return; + + try + { + // setup folder picker + css::uno::Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + xFolderPicker = sfx2::createFolderPicker(xContext, GetFrameWeld()); + + OUString aDlgPathName( SvtPathOptions().GetGraphicPath() ); + xFolderPicker->setDisplayDirectory(aDlgPathName); + + aPreviewTimer.Stop(); + + css::uno::Reference< XAsynchronousExecutableDialog > xAsyncDlg( xFolderPicker, UNO_QUERY ); + if ( xAsyncDlg.is() ) + xAsyncDlg->startExecuteModal( xDialogListener ); + else + { + if( xFolderPicker->execute() == RET_OK ) + { + aURL = INetURLObject( xFolderPicker->getDirectory() ); + bSearchRecursive = true; // UI choice no longer possible, windows file picker allows no user controls + SearchFiles(); + } + } + } + catch (const IllegalArgumentException&) + { + OSL_FAIL( "Folder picker failed with illegal arguments" ); + } +} + +void TPGalleryThemeProperties::TakeFiles() +{ + if (m_xLbxFound->count_selected_rows() || (bTakeAll && bEntriesFound)) + { + auto xTakeProgress = std::make_shared<TakeProgress>(GetFrameWeld(), this); + xTakeProgress->LaunchThread(); + weld::DialogController::runAsync(xTakeProgress, [](sal_Int32 /*nResult*/) { + /* no postprocessing needed, pTakeProgress + will be disposed in TakeProgress::CleanupHdl */ + }); + + } +} + +IMPL_LINK_NOARG(TPGalleryThemeProperties, ClickPreviewHdl, weld::Toggleable&, void) +{ + if ( !bInputAllowed ) + return; + + aPreviewTimer.Stop(); + aPreviewString.clear(); + + if (!m_xCbxPreview->get_active()) + { + xMediaPlayer.clear(); + m_aWndPreview.SetGraphic(Graphic()); + m_aWndPreview.Invalidate(); + } + else + DoPreview(); +} + +void TPGalleryThemeProperties::DoPreview() +{ + int nIndex = m_xLbxFound->get_selected_index(); + OUString aString(m_xLbxFound->get_text(nIndex)); + + if (aString == aPreviewString) + return; + + INetURLObject _aURL(aFoundList[nIndex]); + bInputAllowed = false; + + if (!m_aWndPreview.SetGraphic(_aURL)) + { + weld::WaitObject aWaitObject(GetFrameWeld()); + ErrorHandler::HandleError(ERRCODE_IO_NOTEXISTSPATH, GetFrameWeld()); + } +#if HAVE_FEATURE_AVMEDIA + else if( ::avmedia::MediaWindow::isMediaURL( _aURL.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous ), "" ) ) + { + xMediaPlayer = ::avmedia::MediaWindow::createPlayer( _aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), "" ); + if( xMediaPlayer.is() ) + xMediaPlayer->start(); + } +#endif + bInputAllowed = true; + aPreviewString = aString; +} + +IMPL_LINK_NOARG(TPGalleryThemeProperties, ClickTakeHdl, weld::Button&, void) +{ + if( !bInputAllowed ) + return; + + aPreviewTimer.Stop(); + + if (!m_xLbxFound->count_selected_rows() || !bEntriesFound) + { + SvxOpenGraphicDialog aDlg(CuiResId(RID_CUISTR_KEY_GALLERY_DIR), GetFrameWeld()); + aDlg.EnableLink(false); + aDlg.AsLink(false); + + if( !aDlg.Execute() ) + pData->pTheme->InsertURL( INetURLObject( aDlg.GetPath() ) ); + } + else + { + bTakeAll = false; + TakeFiles(); + } +} + +IMPL_LINK_NOARG(TPGalleryThemeProperties, ClickTakeAllHdl, weld::Button&, void) +{ + if( bInputAllowed ) + { + aPreviewTimer.Stop(); + bTakeAll = true; + TakeFiles(); + } +} + +IMPL_LINK_NOARG(TPGalleryThemeProperties, SelectFoundHdl, weld::TreeView&, void) +{ + if (!bInputAllowed) + return; + + bool bPreviewPossible = false; + + aPreviewTimer.Stop(); + + if( bEntriesFound ) + { + if (m_xLbxFound->count_selected_rows() == 1) + { + m_xCbxPreview->set_sensitive(true); + bPreviewPossible = true; + } + else + m_xCbxPreview->set_sensitive(false); + + if( !aFoundList.empty() ) + m_xBtnTakeAll->set_sensitive(true); + else + m_xBtnTakeAll->set_sensitive(false); + } + + if (bPreviewPossible && m_xCbxPreview->get_active()) + aPreviewTimer.Start(); +} + +IMPL_LINK_NOARG(TPGalleryThemeProperties, DClickFoundHdl, weld::TreeView&, bool) +{ + if( bInputAllowed ) + { + aPreviewTimer.Stop(); + + if (m_xLbxFound->count_selected_rows() == 1 && bEntriesFound) + ClickTakeHdl(*m_xBtnTake); + } + return true; +} + +IMPL_LINK_NOARG(TPGalleryThemeProperties, PreviewTimerHdl, Timer *, void) +{ + aPreviewTimer.Stop(); + DoPreview(); +} + +void TPGalleryThemeProperties::EndSearchProgressHdl(sal_Int32 /*nResult*/) +{ + if( !aFoundList.empty() ) + { + m_xLbxFound->select(0); + m_xBtnTakeAll->set_sensitive(true); + m_xCbxPreview->set_sensitive(true); + bEntriesFound = true; + } + else + { + m_xLbxFound->append_text(CuiResId(RID_CUISTR_GALLERY_NOFILES)); + m_xBtnTakeAll->set_sensitive(false); + m_xCbxPreview->set_sensitive(false); + bEntriesFound = false; + } +} + +IMPL_LINK( TPGalleryThemeProperties, DialogClosedHdl, css::ui::dialogs::DialogClosedEvent*, pEvt, void ) +{ + DBG_ASSERT( xFolderPicker.is(), "TPGalleryThemeProperties::DialogClosedHdl(): no folder picker" ); + + OUString sURL = xFolderPicker->getDirectory(); + StartSearchFiles( sURL, pEvt->DialogResult ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/cuigrfflt.cxx b/cui/source/dialogs/cuigrfflt.cxx new file mode 100644 index 000000000..c67ee92d1 --- /dev/null +++ b/cui/source/dialogs/cuigrfflt.cxx @@ -0,0 +1,469 @@ +/* -*- 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 <vcl/BitmapMosaicFilter.hxx> +#include <vcl/BitmapSharpenFilter.hxx> +#include <vcl/BitmapEmbossGreyFilter.hxx> +#include <vcl/BitmapSepiaFilter.hxx> +#include <vcl/BitmapSmoothenFilter.hxx> +#include <vcl/BitmapSolarizeFilter.hxx> +#include <vcl/BitmapColorQuantizationFilter.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <osl/diagnose.h> +#include <tools/helpers.hxx> +#include <cuigrfflt.hxx> + +CuiGraphicPreviewWindow::CuiGraphicPreviewWindow() + : mpOrigGraphic(nullptr) + , mfScaleX(0.0) + , mfScaleY(0.0) +{ +} + +void CuiGraphicPreviewWindow::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + CustomWidgetController::SetDrawingArea(pDrawingArea); + OutputDevice &rDevice = pDrawingArea->get_ref_device(); + maOutputSizePixel = rDevice.LogicToPixel(Size(81, 73), MapMode(MapUnit::MapAppFont)); + pDrawingArea->set_size_request(maOutputSizePixel.Width(), maOutputSizePixel.Height()); +} + +void CuiGraphicPreviewWindow::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle&) +{ + rRenderContext.SetBackground(Wallpaper(Application::GetSettings().GetStyleSettings().GetDialogColor())); + rRenderContext.Erase(); + + const Size aOutputSize(GetOutputSizePixel()); + + if (maPreview.IsAnimated()) + { + const Size aGraphicSize(rRenderContext.LogicToPixel(maPreview.GetPrefSize(), maPreview.GetPrefMapMode())); + const Point aGraphicPosition((aOutputSize.Width() - aGraphicSize.Width() ) >> 1, + (aOutputSize.Height() - aGraphicSize.Height() ) >> 1); + maPreview.StartAnimation(rRenderContext, aGraphicPosition, aGraphicSize); + } + else + { + const Size aGraphicSize(maPreview.GetSizePixel()); + const Point aGraphicPosition((aOutputSize.Width() - aGraphicSize.Width()) >> 1, + (aOutputSize.Height() - aGraphicSize.Height()) >> 1); + maPreview.Draw(rRenderContext, aGraphicPosition, aGraphicSize); + } +} + +void CuiGraphicPreviewWindow::SetPreview(const Graphic& rGraphic) +{ + maPreview = rGraphic; + Invalidate(); +} + +void CuiGraphicPreviewWindow::ScaleImageToFit() +{ + if (!mpOrigGraphic) + return; + + maScaledOrig = *mpOrigGraphic; + + const Size aPreviewSize(GetOutputSizePixel()); + Size aGrfSize(maOrigGraphicSizePixel); + + if( mpOrigGraphic->GetType() == GraphicType::Bitmap && + aPreviewSize.Width() && aPreviewSize.Height() && + aGrfSize.Width() && aGrfSize.Height() ) + { + const double fGrfWH = static_cast<double>(aGrfSize.Width()) / aGrfSize.Height(); + const double fPreWH = static_cast<double>(aPreviewSize.Width()) / aPreviewSize.Height(); + + if( fGrfWH < fPreWH ) + { + aGrfSize.setWidth( static_cast<tools::Long>( aPreviewSize.Height() * fGrfWH ) ); + aGrfSize.setHeight( aPreviewSize.Height() ); + } + else + { + aGrfSize.setWidth( aPreviewSize.Width() ); + aGrfSize.setHeight( static_cast<tools::Long>( aPreviewSize.Width() / fGrfWH ) ); + } + + mfScaleX = static_cast<double>(aGrfSize.Width()) / maOrigGraphicSizePixel.Width(); + mfScaleY = static_cast<double>(aGrfSize.Height()) / maOrigGraphicSizePixel.Height(); + + if( !mpOrigGraphic->IsAnimated() ) + { + BitmapEx aBmpEx( mpOrigGraphic->GetBitmapEx() ); + + if( aBmpEx.Scale( aGrfSize ) ) + maScaledOrig = aBmpEx; + } + } + + maModifyHdl.Call(nullptr); +} + +void CuiGraphicPreviewWindow::Resize() +{ + maOutputSizePixel = GetOutputSizePixel(); + ScaleImageToFit(); +} + +GraphicFilterDialog::GraphicFilterDialog(weld::Window* pParent, + const OUString& rUIXMLDescription, const OString& rID, + const Graphic& rGraphic) + : GenericDialogController(pParent, rUIXMLDescription, rID) + , maTimer("cui GraphicFilterDialog maTimer") + , maModifyHdl(LINK(this, GraphicFilterDialog, ImplModifyHdl)) + , mxPreview(new weld::CustomWeld(*m_xBuilder, "preview", maPreview)) +{ + bIsBitmap = rGraphic.GetType() == GraphicType::Bitmap; + + maTimer.SetInvokeHandler(LINK(this, GraphicFilterDialog, ImplPreviewTimeoutHdl)); + maTimer.SetTimeout(5); + + maPreview.init(&rGraphic, maModifyHdl); +} + +IMPL_LINK_NOARG(GraphicFilterDialog, ImplPreviewTimeoutHdl, Timer *, void) +{ + maTimer.Stop(); + maPreview.SetPreview(GetFilteredGraphic(maPreview.GetScaledOriginal(), + maPreview.GetScaleX(), maPreview.GetScaleY())); +} + +IMPL_LINK_NOARG(GraphicFilterDialog, ImplModifyHdl, LinkParamNone*, void) +{ + if (bIsBitmap) + { + maTimer.Stop(); + maTimer.Start(); + } +} + +GraphicFilterMosaic::GraphicFilterMosaic(weld::Window* pParent, const Graphic& rGraphic, + sal_uInt16 nTileWidth, sal_uInt16 nTileHeight, bool bEnhanceEdges) + : GraphicFilterDialog(pParent, "cui/ui/mosaicdialog.ui", "MosaicDialog", rGraphic) + , mxMtrWidth(m_xBuilder->weld_metric_spin_button("width", FieldUnit::PIXEL)) + , mxMtrHeight(m_xBuilder->weld_metric_spin_button("height", FieldUnit::PIXEL)) + , mxCbxEdges(m_xBuilder->weld_check_button("edges")) +{ + mxMtrWidth->set_value(nTileWidth, FieldUnit::PIXEL); + mxMtrWidth->set_max(GetGraphicSizePixel().Width(), FieldUnit::PIXEL); + mxMtrWidth->connect_value_changed(LINK(this, GraphicFilterMosaic, EditModifyHdl)); + + mxMtrHeight->set_value(nTileHeight, FieldUnit::PIXEL); + mxMtrHeight->set_max(GetGraphicSizePixel().Height(), FieldUnit::PIXEL); + mxMtrHeight->connect_value_changed(LINK(this, GraphicFilterMosaic, EditModifyHdl)); + + mxCbxEdges->set_active(bEnhanceEdges); + mxCbxEdges->connect_toggled(LINK(this, GraphicFilterMosaic, CheckBoxModifyHdl)); + + mxMtrWidth->grab_focus(); +} + +IMPL_LINK_NOARG(GraphicFilterMosaic, CheckBoxModifyHdl, weld::Toggleable&, void) +{ + GetModifyHdl().Call(nullptr); +} + +IMPL_LINK_NOARG(GraphicFilterMosaic, EditModifyHdl, weld::MetricSpinButton&, void) +{ + GetModifyHdl().Call(nullptr); +} + +Graphic GraphicFilterMosaic::GetFilteredGraphic( const Graphic& rGraphic, + double fScaleX, double fScaleY ) +{ + Graphic aRet; + tools::Long nTileWidth = static_cast<tools::Long>(mxMtrWidth->get_value(FieldUnit::PIXEL)); + tools::Long nTileHeight = static_cast<tools::Long>(mxMtrHeight->get_value(FieldUnit::PIXEL)); + const Size aSize( std::max( FRound( nTileWidth * fScaleX ), tools::Long(1) ), + std::max( FRound( nTileHeight * fScaleY ), tools::Long(1) ) ); + + if( rGraphic.IsAnimated() ) + { + Animation aAnim( rGraphic.GetAnimation() ); + + if (BitmapFilter::Filter(aAnim, BitmapMosaicFilter(aSize.getWidth(), aSize.getHeight()))) + { + if( IsEnhanceEdges() ) + (void)BitmapFilter::Filter(aAnim, BitmapSharpenFilter()); + + aRet = aAnim; + } + } + else + { + BitmapEx aBmpEx( rGraphic.GetBitmapEx() ); + + if (BitmapFilter::Filter(aBmpEx, BitmapMosaicFilter(aSize.getWidth(), aSize.getHeight()))) + { + if( IsEnhanceEdges() ) + BitmapFilter::Filter(aBmpEx, BitmapSharpenFilter()); + + aRet = aBmpEx; + } + } + + return aRet; +} + +GraphicFilterSmooth::GraphicFilterSmooth(weld::Window* pParent, const Graphic& rGraphic, double nRadius) + : GraphicFilterDialog(pParent, "cui/ui/smoothdialog.ui", "SmoothDialog", rGraphic) + , mxMtrRadius(m_xBuilder->weld_spin_button("radius")) +{ + mxMtrRadius->set_value(nRadius * 10); + mxMtrRadius->connect_value_changed(LINK(this, GraphicFilterSmooth, EditModifyHdl)); + mxMtrRadius->grab_focus(); +} + +IMPL_LINK_NOARG(GraphicFilterSmooth, EditModifyHdl, weld::SpinButton&, void) +{ + GetModifyHdl().Call(nullptr); +} + +Graphic GraphicFilterSmooth::GetFilteredGraphic( const Graphic& rGraphic, double, double ) +{ + Graphic aRet; + double nRadius = mxMtrRadius->get_value() / 10.0; + + if( rGraphic.IsAnimated() ) + { + Animation aAnim( rGraphic.GetAnimation() ); + + if (BitmapFilter::Filter(aAnim, BitmapSmoothenFilter(nRadius))) + { + aRet = aAnim; + } + } + else + { + BitmapEx aBmpEx( rGraphic.GetBitmapEx() ); + + if (BitmapFilter::Filter(aBmpEx, BitmapSmoothenFilter(nRadius))) + { + aRet = aBmpEx; + } + } + + return aRet; +} + +GraphicFilterSolarize::GraphicFilterSolarize(weld::Window* pParent, const Graphic& rGraphic, + sal_uInt8 cGreyThreshold, bool bInvert) + : GraphicFilterDialog(pParent, "cui/ui/solarizedialog.ui", "SolarizeDialog", rGraphic) + , mxMtrThreshold(m_xBuilder->weld_metric_spin_button("value", FieldUnit::PERCENT)) + , mxCbxInvert(m_xBuilder->weld_check_button("invert")) +{ + mxMtrThreshold->set_value(FRound(cGreyThreshold / 2.55), FieldUnit::PERCENT); + mxMtrThreshold->connect_value_changed(LINK(this, GraphicFilterSolarize, EditModifyHdl)); + + mxCbxInvert->set_active(bInvert); + mxCbxInvert->connect_toggled(LINK(this, GraphicFilterSolarize, CheckBoxModifyHdl)); +} + +IMPL_LINK_NOARG(GraphicFilterSolarize, CheckBoxModifyHdl, weld::Toggleable&, void) +{ + GetModifyHdl().Call(nullptr); +} + +IMPL_LINK_NOARG(GraphicFilterSolarize, EditModifyHdl, weld::MetricSpinButton&, void) +{ + GetModifyHdl().Call(nullptr); +} + +Graphic GraphicFilterSolarize::GetFilteredGraphic( const Graphic& rGraphic, double, double ) +{ + Graphic aRet; + sal_uInt8 nGreyThreshold = static_cast<sal_uInt8>(FRound(mxMtrThreshold->get_value(FieldUnit::PERCENT) * 2.55)); + + if( rGraphic.IsAnimated() ) + { + Animation aAnim( rGraphic.GetAnimation() ); + + if (BitmapFilter::Filter(aAnim, BitmapSolarizeFilter(nGreyThreshold))) + { + if( IsInvert() ) + aAnim.Invert(); + + aRet = aAnim; + } + } + else + { + BitmapEx aBmpEx( rGraphic.GetBitmapEx() ); + + if (BitmapFilter::Filter(aBmpEx, BitmapSolarizeFilter(nGreyThreshold))) + { + if( IsInvert() ) + aBmpEx.Invert(); + + aRet = aBmpEx; + } + } + + return aRet; +} + +GraphicFilterSepia::GraphicFilterSepia(weld::Window* pParent, const Graphic& rGraphic, + sal_uInt16 nSepiaPercent) + : GraphicFilterDialog(pParent, "cui/ui/agingdialog.ui", "AgingDialog", rGraphic) + , mxMtrSepia(m_xBuilder->weld_metric_spin_button("value", FieldUnit::PERCENT)) +{ + mxMtrSepia->set_value(nSepiaPercent, FieldUnit::PERCENT); + mxMtrSepia->connect_value_changed(LINK(this, GraphicFilterSepia, EditModifyHdl)); +} + +IMPL_LINK_NOARG(GraphicFilterSepia, EditModifyHdl, weld::MetricSpinButton&, void) +{ + GetModifyHdl().Call(nullptr); +} + +Graphic GraphicFilterSepia::GetFilteredGraphic( const Graphic& rGraphic, double, double ) +{ + Graphic aRet; + sal_uInt16 nSepiaPct = sal::static_int_cast< sal_uInt16 >(mxMtrSepia->get_value(FieldUnit::PERCENT)); + + if( rGraphic.IsAnimated() ) + { + Animation aAnim( rGraphic.GetAnimation() ); + + if (BitmapFilter::Filter(aAnim, BitmapSepiaFilter(nSepiaPct))) + aRet = aAnim; + } + else + { + BitmapEx aBmpEx( rGraphic.GetBitmapEx() ); + + if (BitmapFilter::Filter(aBmpEx, BitmapSepiaFilter(nSepiaPct))) + aRet = aBmpEx; + } + + return aRet; +} + +GraphicFilterPoster::GraphicFilterPoster(weld::Window* pParent, const Graphic& rGraphic, + sal_uInt16 nPosterCount) + : GraphicFilterDialog(pParent, "cui/ui/posterdialog.ui", "PosterDialog", rGraphic) + , mxNumPoster(m_xBuilder->weld_spin_button("value")) +{ + mxNumPoster->set_range(2, vcl::pixelFormatBitCount(rGraphic.GetBitmapEx().getPixelFormat())); + mxNumPoster->set_value(nPosterCount); + mxNumPoster->connect_value_changed(LINK(this, GraphicFilterPoster, EditModifyHdl)); +} + +IMPL_LINK_NOARG(GraphicFilterPoster, EditModifyHdl, weld::SpinButton&, void) +{ + GetModifyHdl().Call(nullptr); +} + +Graphic GraphicFilterPoster::GetFilteredGraphic( const Graphic& rGraphic, double, double ) +{ + Graphic aRet; + const sal_uInt16 nPosterCount = static_cast<sal_uInt16>(mxNumPoster->get_value()); + + if( rGraphic.IsAnimated() ) + { + Animation aAnim( rGraphic.GetAnimation() ); + + if( aAnim.ReduceColors( nPosterCount ) ) + aRet = aAnim; + } + else + { + BitmapEx aBmpEx( rGraphic.GetBitmapEx() ); + + if (BitmapFilter::Filter(aBmpEx, BitmapColorQuantizationFilter(nPosterCount))) + aRet = aBmpEx; + } + + return aRet; +} + +bool EmbossControl::MouseButtonDown( const MouseEvent& rEvt ) +{ + const RectPoint eOldRP = GetActualRP(); + + SvxRectCtl::MouseButtonDown( rEvt ); + + if( GetActualRP() != eOldRP ) + maModifyHdl.Call( nullptr ); + + return true; +} + +void EmbossControl::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + SvxRectCtl::SetDrawingArea(pDrawingArea); + Size aSize(pDrawingArea->get_ref_device().LogicToPixel(Size(77, 60), MapMode(MapUnit::MapAppFont))); + pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); +} + +GraphicFilterEmboss::GraphicFilterEmboss(weld::Window* pParent, + const Graphic& rGraphic, RectPoint eLightSource) + : GraphicFilterDialog(pParent, "cui/ui/embossdialog.ui", "EmbossDialog", rGraphic) + , mxCtlLight(new weld::CustomWeld(*m_xBuilder, "lightsource", maCtlLight)) +{ + maCtlLight.SetActualRP(eLightSource); + maCtlLight.SetModifyHdl( GetModifyHdl() ); + maCtlLight.GrabFocus(); +} + +GraphicFilterEmboss::~GraphicFilterEmboss() +{ +} + +Graphic GraphicFilterEmboss::GetFilteredGraphic( const Graphic& rGraphic, double, double ) +{ + Graphic aRet; + sal_uInt16 nAzim, nElev; + + switch (maCtlLight.GetActualRP()) + { + default: OSL_FAIL("svx::GraphicFilterEmboss::GetFilteredGraphic(), unknown Reference Point!" ); + [[fallthrough]]; + case RectPoint::LT: nAzim = 4500; nElev = 4500; break; + case RectPoint::MT: nAzim = 9000; nElev = 4500; break; + case RectPoint::RT: nAzim = 13500; nElev = 4500; break; + case RectPoint::LM: nAzim = 0; nElev = 4500; break; + case RectPoint::MM: nAzim = 0; nElev = 9000; break; + case RectPoint::RM: nAzim = 18000; nElev = 4500; break; + case RectPoint::LB: nAzim = 31500; nElev = 4500; break; + case RectPoint::MB: nAzim = 27000; nElev = 4500; break; + case RectPoint::RB: nAzim = 22500; nElev = 4500; break; + } + + if( rGraphic.IsAnimated() ) + { + Animation aAnim( rGraphic.GetAnimation() ); + + if (BitmapFilter::Filter(aAnim, BitmapEmbossGreyFilter(nAzim, nElev))) + aRet = aAnim; + } + else + { + BitmapEx aBmpEx( rGraphic.GetBitmapEx() ); + + if (BitmapFilter::Filter(aBmpEx, BitmapEmbossGreyFilter(nAzim, nElev))) + aRet = aBmpEx; + } + + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/cuihyperdlg.cxx b/cui/source/dialogs/cuihyperdlg.cxx new file mode 100644 index 000000000..20c1b1e26 --- /dev/null +++ b/cui/source/dialogs/cuihyperdlg.cxx @@ -0,0 +1,294 @@ +/* -*- 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 <sal/config.h> + +#include <osl/diagnose.h> +#include <comphelper/lok.hxx> +#include <unotools/viewoptions.hxx> +#include <cuihyperdlg.hxx> +#include <hlinettp.hxx> +#include <hlmailtp.hxx> +#include <hldoctp.hxx> +#include <hldocntp.hxx> +#include <sfx2/app.hxx> +#include <sfx2/viewfrm.hxx> +#include <svl/eitem.hxx> +#include <svx/svxids.hrc> +#include <dialmgr.hxx> +#include <strings.hrc> +#include <vector> + +using ::com::sun::star::uno::Reference; +using ::com::sun::star::frame::XFrame; + + +//# # +//# Childwindow-Wrapper-Class # +//# # + + +SvxHlinkCtrl::SvxHlinkCtrl( sal_uInt16 _nId, SfxBindings & rBindings, SvxHpLinkDlg* pDlg ) + : SfxControllerItem ( _nId, rBindings ) + , aRdOnlyForwarder ( SID_READONLY_MODE, *this ) +{ + pParent = pDlg; +} + +void SvxHlinkCtrl::dispose() +{ + pParent = nullptr; + aRdOnlyForwarder.dispose(); + ::SfxControllerItem::dispose(); +} + +void SvxHlinkCtrl::StateChangedAtToolBoxControl( sal_uInt16 nSID, SfxItemState eState, + const SfxPoolItem* pState ) +{ + if (!(eState == SfxItemState::DEFAULT && pParent)) + return; + + switch ( nSID ) + { + case SID_HYPERLINK_GETLINK : + { + pParent->SetPage( static_cast<const SvxHyperlinkItem*>(pState) ); + } + break; + case SID_READONLY_MODE : + { + pParent->SetReadOnlyMode( static_cast<const SfxBoolItem*>(pState)->GetValue() ); + } + break; + } +} + +//# # +//# Hyperlink - Dialog # +//# # +SvxHpLinkDlg::SvxHpLinkDlg(SfxBindings* pBindings, SfxChildWindow* pChild, weld::Window* pParent) + : SfxModelessDialogController(pBindings, pChild, pParent, "cui/ui/hyperlinkdialog.ui", "HyperlinkDialog") + , pSet ( nullptr ) + , maCtrl ( SID_HYPERLINK_GETLINK, *pBindings, this ) + , mbIsHTMLDoc ( false ) + , m_xIconCtrl(m_xBuilder->weld_notebook("tabcontrol")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) + , m_xApplyBtn(m_xBuilder->weld_button("apply")) + , m_xCancelBtn(m_xBuilder->weld_button("cancel")) + , m_xHelpBtn(m_xBuilder->weld_button("help")) + , m_xResetBtn(m_xBuilder->weld_button("reset")) +{ + m_xIconCtrl->connect_enter_page( LINK ( this, SvxHpLinkDlg, ChosePageHdl_Impl ) ); + m_xIconCtrl->show(); + + // ItemSet + if ( pSet ) + { + pExampleSet.reset(new SfxItemSet( *pSet )); + pOutSet.reset(new SfxItemSet( *pSet->GetPool(), pSet->GetRanges() )); + } + + // Buttons + m_xOKBtn->show(); + m_xApplyBtn->show(); + m_xCancelBtn->show(); + m_xHelpBtn->show(); + m_xResetBtn->show(); + + mbGrabFocus = true; + + // set OK/Cancel - button + m_xCancelBtn->set_label(CuiResId(RID_CUISTR_HYPDLG_CLOSEBUT)); + + // create itemset for tabpages + mpItemSet = std::make_unique<SfxItemSetFixed<SID_HYPERLINK_GETLINK, + SID_HYPERLINK_SETLINK>>( SfxGetpApp()->GetPool()); + + SvxHyperlinkItem aItem(SID_HYPERLINK_GETLINK); + mpItemSet->Put(aItem); + + SetInputSet (mpItemSet.get()); + + // insert pages + AddTabPage("internet", SvxHyperlinkInternetTp::Create); + AddTabPage("mail", SvxHyperlinkMailTp::Create); + if (!comphelper::LibreOfficeKit::isActive()) + { + AddTabPage("document", SvxHyperlinkDocTp::Create); + AddTabPage("newdocument", SvxHyperlinkNewDocTp::Create); + } + + SetCurPageId("internet"); + + // Init Dialog + Start(); + + GetBindings().Update(SID_HYPERLINK_GETLINK); + GetBindings().Update(SID_READONLY_MODE); + + m_xResetBtn->connect_clicked( LINK( this, SvxHpLinkDlg, ResetHdl ) ); + m_xOKBtn->connect_clicked( LINK ( this, SvxHpLinkDlg, ClickOkHdl_Impl ) ); + m_xApplyBtn->connect_clicked ( LINK ( this, SvxHpLinkDlg, ClickApplyHdl_Impl ) ); +} + +SvxHpLinkDlg::~SvxHpLinkDlg() +{ + mbGrabFocus = false; // don't do any grab if tear-down moves focus around during destruction + + // delete config item, so the base class (SfxModelessDialogController) can not load it on the next start + SvtViewOptions aViewOpt( EViewType::TabDialog, OUString::number(SID_HYPERLINK_DIALOG) ); + aViewOpt.Delete(); + + mpItemSet.reset(); + + maCtrl.dispose(); + + maPageList.clear(); + + pRanges.reset(); + pOutSet.reset(); +} + +void SvxHpLinkDlg::Activate() { + if (mbGrabFocus) { + static_cast<SvxHyperlinkTabPageBase *>(GetTabPage(GetCurPageId()))->SetInitFocus(); + mbGrabFocus = false; + } + SfxModelessDialogController::Activate(); +} + +void SvxHpLinkDlg::Close() +{ + if (IsClosing()) + return; + SfxViewFrame* pViewFrame = SfxViewFrame::Current(); + if (pViewFrame) + pViewFrame->ToggleChildWindow(SID_HYPERLINK_DIALOG); +} + +void SvxHpLinkDlg::Apply() +{ + SfxItemSetFixed<SID_HYPERLINK_GETLINK, SID_HYPERLINK_SETLINK> aItemSet( SfxGetpApp()->GetPool() ); + + SvxHyperlinkTabPageBase* pCurrentPage = static_cast<SvxHyperlinkTabPageBase*>( + GetTabPage( GetCurPageId() ) ); + + pCurrentPage->FillItemSet( &aItemSet ); + + const SvxHyperlinkItem *aItem = aItemSet.GetItem(SID_HYPERLINK_SETLINK); + if ( !aItem->GetURL().isEmpty() ) + GetDispatcher()->ExecuteList(SID_HYPERLINK_SETLINK, + SfxCallMode::ASYNCHRON | SfxCallMode::RECORD, { aItem }); + + static_cast<SvxHyperlinkTabPageBase*>( GetTabPage( GetCurPageId() ) )->DoApply(); +} + +/// Click on OK button +IMPL_LINK_NOARG(SvxHpLinkDlg, ClickOkHdl_Impl, weld::Button&, void) +{ + Apply(); + m_xDialog->response(RET_OK); +} + +/************************************************************************* +|* +|* Click on Apply-button +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHpLinkDlg, ClickApplyHdl_Impl, weld::Button&, void) +{ + Apply(); +} + +/************************************************************************* +|* +|* Set Page +|* +|************************************************************************/ +void SvxHpLinkDlg::SetPage ( SvxHyperlinkItem const * pItem ) +{ + OString sPageId("internet"); + + OUString aStrURL(pItem->GetURL()); + INetURLObject aURL(aStrURL); + INetProtocol eProtocolTyp = aURL.GetProtocol(); + + switch ( eProtocolTyp ) + { + case INetProtocol::Http : + case INetProtocol::Ftp : + sPageId = "internet"; + break; + case INetProtocol::File : + sPageId = "document"; + break; + case INetProtocol::Mailto : + sPageId = "mail"; + break; + default : + if (aStrURL.startsWith("#")) + sPageId = "document"; + else + { + // not valid + sPageId = GetCurPageId(); + } + break; + } + + ShowPage (sPageId); + + SvxHyperlinkTabPageBase* pCurrentPage = static_cast<SvxHyperlinkTabPageBase*>(GetTabPage( sPageId )); + + mbIsHTMLDoc = (pItem->GetInsertMode() & HLINK_HTMLMODE) != 0; + + IconChoicePage* pPage = GetTabPage (sPageId); + if(pPage) + { + SfxItemSet& aPageSet = const_cast<SfxItemSet&>(pPage->GetItemSet ()); + aPageSet.Put ( *pItem ); + + pCurrentPage->Reset( aPageSet ); + } +} + +/************************************************************************* +|* +|* Enable/Disable ReadOnly mode +|* +|************************************************************************/ +void SvxHpLinkDlg::SetReadOnlyMode( bool bRdOnly ) +{ + m_xOKBtn->set_sensitive(!bRdOnly); +} + +/************************************************************************* +|* +|* late-initialization of newly created pages +|* +|************************************************************************/ +void SvxHpLinkDlg::PageCreated(IconChoicePage& rPage) +{ + SvxHyperlinkTabPageBase& rHyperlinkPage = dynamic_cast< SvxHyperlinkTabPageBase& >( rPage ); + Reference< XFrame > xDocumentFrame = GetBindings().GetActiveFrame(); + OSL_ENSURE( xDocumentFrame.is(), "SvxHpLinkDlg::PageCreated: macro assignment functionality won't work with a proper frame!" ); + rHyperlinkPage.SetDocumentFrame( xDocumentFrame ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/cuiimapwnd.cxx b/cui/source/dialogs/cuiimapwnd.cxx new file mode 100644 index 000000000..d613e1a80 --- /dev/null +++ b/cui/source/dialogs/cuiimapwnd.cxx @@ -0,0 +1,57 @@ +/* -*- 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 <cuiimapwnd.hxx> + +/************************************************************************* +|* +|* URLDlg +|* +\************************************************************************/ + +URLDlg::URLDlg(weld::Widget* pWindow, const OUString& rURL, const OUString& rAlternativeText, + const OUString& rDescription, const OUString& rTarget, const OUString& rName, + TargetList& rTargetList) + : GenericDialogController(pWindow, "cui/ui/cuiimapdlg.ui", "IMapDialog") + , m_xEdtURL(m_xBuilder->weld_entry("urlentry")) + , m_xCbbTargets(m_xBuilder->weld_combo_box("frameCB")) + , m_xEdtName(m_xBuilder->weld_entry("nameentry")) + , m_xEdtAlternativeText(m_xBuilder->weld_entry("textentry")) + , m_xEdtDescription(m_xBuilder->weld_text_view("descTV")) +{ + m_xEdtDescription->set_size_request(m_xEdtDescription->get_approximate_digit_width() * 51, + m_xEdtDescription->get_height_rows(5)); + + m_xEdtURL->set_text(rURL); + m_xEdtAlternativeText->set_text(rAlternativeText); + m_xEdtDescription->set_text(rDescription); + m_xEdtName->set_text(rName); + + for (const OUString& a : rTargetList) + m_xCbbTargets->append_text(a); + + if (rTarget.isEmpty()) + m_xCbbTargets->set_entry_text("_self"); + else + m_xCbbTargets->set_entry_text(rTarget); +} + +URLDlg::~URLDlg() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/cuitbxform.cxx b/cui/source/dialogs/cuitbxform.cxx new file mode 100644 index 000000000..55d21325d --- /dev/null +++ b/cui/source/dialogs/cuitbxform.cxx @@ -0,0 +1,31 @@ +/* -*- 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 <cuitbxform.hxx> + +FmInputRecordNoDialog::FmInputRecordNoDialog(weld::Window* pParent) + : GenericDialogController(pParent, "cui/ui/recordnumberdialog.ui", "RecordNumberDialog") + , m_xRecordNo(m_xBuilder->weld_spin_button("entry")) +{ + m_xRecordNo->set_range(1, 0x7FFFFFFF); +} + +FmInputRecordNoDialog::~FmInputRecordNoDialog() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/dlgname.cxx b/cui/source/dialogs/dlgname.cxx new file mode 100644 index 000000000..ffa540bbc --- /dev/null +++ b/cui/source/dialogs/dlgname.cxx @@ -0,0 +1,105 @@ +/* -*- 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 <dlgname.hxx> + +/************************************************************************* +|* +|* Dialog for editing a name +|* +\************************************************************************/ + +SvxNameDialog::SvxNameDialog(weld::Window* pParent, const OUString& rName, const OUString& rDesc) + : GenericDialogController(pParent, "cui/ui/namedialog.ui", "NameDialog") + , m_xEdtName(m_xBuilder->weld_entry("name_entry")) + , m_xFtDescription(m_xBuilder->weld_label("description_label")) + , m_xBtnOK(m_xBuilder->weld_button("ok")) +{ + m_xFtDescription->set_label(rDesc); + m_xEdtName->set_text(rName); + m_xEdtName->select_region(0, -1); + ModifyHdl(*m_xEdtName); + m_xEdtName->connect_changed(LINK(this, SvxNameDialog, ModifyHdl)); +} + +IMPL_LINK_NOARG(SvxNameDialog, ModifyHdl, weld::Entry&, void) +{ + // Do not allow empty names + bool bEnable; + if (m_aCheckNameHdl.IsSet()) + bEnable = !m_xEdtName->get_text().isEmpty() && m_aCheckNameHdl.Call(*this); + else + bEnable = !m_xEdtName->get_text().isEmpty(); + m_xBtnOK->set_sensitive(bEnable); + // tdf#129032: feedback on reason to disabled controls + m_xEdtName->set_message_type(bEnable ? weld::EntryMessageType::Normal + : weld::EntryMessageType::Error); + OUString rTip = ""; + if (!bEnable && m_aCheckNameTooltipHdl.IsSet()) + rTip = m_aCheckNameTooltipHdl.Call(*this); + m_xBtnOK->set_tooltip_text(rTip); + m_xEdtName->set_tooltip_text(rTip); +} + +// #i68101# +// Dialog for editing Object Name +// plus uniqueness-callback-linkHandler + +SvxObjectNameDialog::SvxObjectNameDialog(weld::Window* pParent, const OUString& rName) + : GenericDialogController(pParent, "cui/ui/objectnamedialog.ui", "ObjectNameDialog") + , m_xEdtName(m_xBuilder->weld_entry("object_name_entry")) + , m_xBtnOK(m_xBuilder->weld_button("ok")) +{ + // set name + m_xEdtName->set_text(rName); + m_xEdtName->select_region(0, -1); + + // activate name + ModifyHdl(*m_xEdtName); + m_xEdtName->connect_changed(LINK(this, SvxObjectNameDialog, ModifyHdl)); +} + +IMPL_LINK_NOARG(SvxObjectNameDialog, ModifyHdl, weld::Entry&, void) +{ + if (aCheckNameHdl.IsSet()) + { + m_xBtnOK->set_sensitive(aCheckNameHdl.Call(*this)); + } +} + +// #i68101# +// Dialog for editing Object Title and Description + +SvxObjectTitleDescDialog::SvxObjectTitleDescDialog(weld::Window* pParent, const OUString& rTitle, + const OUString& rDescription) + : GenericDialogController(pParent, "cui/ui/objecttitledescdialog.ui", "ObjectTitleDescDialog") + , m_xEdtTitle(m_xBuilder->weld_entry("object_title_entry")) + , m_xEdtDescription(m_xBuilder->weld_text_view("desc_entry")) +{ + //lock height to initial height + m_xEdtDescription->set_size_request(-1, m_xEdtDescription->get_text_height() * 5); + // set title & desc + m_xEdtTitle->set_text(rTitle); + m_xEdtDescription->set_text(rDescription); + + // activate title + m_xEdtTitle->select_region(0, -1); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/fileextcheckdlg.cxx b/cui/source/dialogs/fileextcheckdlg.cxx new file mode 100644 index 000000000..732f83674 --- /dev/null +++ b/cui/source/dialogs/fileextcheckdlg.cxx @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 <sal/config.h> + +#include <officecfg/Office/Common.hxx> +#include <vcl/fileregistration.hxx> + +#include <fileextcheckdlg.hxx> + +FileExtCheckDialog::FileExtCheckDialog(weld::Window* pParent, const OUString& sTitle, + const OUString& sMsg) + : GenericDialogController(pParent, "cui/ui/fileextcheckdialog.ui", "FileExtCheckDialog") + , m_pText(m_xBuilder->weld_label("lbText")) + , m_pPerformCheck(m_xBuilder->weld_check_button("cbPerformCheck")) + , m_pOk(m_xBuilder->weld_button("btnOk")) +{ + m_pPerformCheck->set_active(true); + m_pOk->connect_clicked(LINK(this, FileExtCheckDialog, OnOkClick)); + m_xDialog->set_title(sTitle); + m_pText->set_label(sMsg); +} + +FileExtCheckDialog::~FileExtCheckDialog() +{ + std::shared_ptr<comphelper::ConfigurationChanges> xChanges( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Misc::PerformFileExtCheck::set(m_pPerformCheck->get_active(), + xChanges); + xChanges->commit(); +} + +IMPL_LINK_NOARG(FileExtCheckDialog, OnOkClick, weld::Button&, void) +{ + vcl::fileregistration::LaunchRegistrationUI(); + FileExtCheckDialog::response(RET_OK); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/cui/source/dialogs/hangulhanjadlg.cxx b/cui/source/dialogs/hangulhanjadlg.cxx new file mode 100644 index 000000000..fb25df938 --- /dev/null +++ b/cui/source/dialogs/hangulhanjadlg.cxx @@ -0,0 +1,1508 @@ +/* -*- 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 <hangulhanjadlg.hxx> +#include <dialmgr.hxx> + +#include <helpids.h> +#include <strings.hrc> + +#include <algorithm> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/debug.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weldutils.hxx> +#include <unotools/lingucfg.hxx> +#include <unotools/linguprops.hxx> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/linguistic2/ConversionDictionaryType.hpp> +#include <com/sun/star/linguistic2/ConversionDirection.hpp> +#include <com/sun/star/linguistic2/ConversionDictionaryList.hpp> +#include <com/sun/star/i18n/TextConversionOption.hpp> +#include <com/sun/star/util/XFlushable.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> + +#define HHC editeng::HangulHanjaConversion +#define LINE_CNT static_cast< sal_uInt16 >(2) +#define MAXNUM_SUGGESTIONS 50 + + +namespace svx +{ + + using namespace ::com::sun::star; + using namespace css::uno; + using namespace css::linguistic2; + using namespace css::lang; + using namespace css::container; + + + namespace + { + class FontSwitch + { + private: + OutputDevice& m_rDev; + + public: + FontSwitch( OutputDevice& _rDev, const vcl::Font& _rTemporaryFont ) + :m_rDev( _rDev ) + { + m_rDev.Push( vcl::PushFlags::FONT ); + m_rDev.SetFont( _rTemporaryFont ); + } + ~FontSwitch() COVERITY_NOEXCEPT_FALSE + { + m_rDev.Pop(); + } + }; + + /** a class which allows to draw two texts in a pseudo-ruby way (which basically + means one text above or below the other, and a little bit smaller) + */ + class PseudoRubyText + { + public: + enum RubyPosition + { + eAbove, eBelow + }; + + protected: + OUString m_sPrimaryText; + OUString m_sSecondaryText; + RubyPosition m_ePosition; + + public: + PseudoRubyText(); + void init( const OUString& rPrimaryText, const OUString& rSecondaryText, const RubyPosition& rPosition ); + const OUString& getPrimaryText() const { return m_sPrimaryText; } + const OUString& getSecondaryText() const { return m_sSecondaryText; } + + public: + void Paint( vcl::RenderContext& _rDevice, const ::tools::Rectangle& _rRect, + ::tools::Rectangle* _pPrimaryLocation, ::tools::Rectangle* _pSecondaryLocation ); + }; + + } + + PseudoRubyText::PseudoRubyText() + : m_ePosition(eAbove) + { + } + + void PseudoRubyText::init( const OUString& rPrimaryText, const OUString& rSecondaryText, const RubyPosition& rPosition ) + { + m_sPrimaryText = rPrimaryText; + m_sSecondaryText = rSecondaryText; + m_ePosition = rPosition; + } + + + void PseudoRubyText::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& _rRect, + ::tools::Rectangle* _pPrimaryLocation, ::tools::Rectangle* _pSecondaryLocation ) + { + // calculate the text flags for the painting + constexpr DrawTextFlags nTextStyle = DrawTextFlags::Mnemonic | + DrawTextFlags::Left | + DrawTextFlags::VCenter; + + Size aPlaygroundSize(_rRect.GetSize()); + + // the font for the secondary text: + vcl::Font aSmallerFont(rRenderContext.GetFont()); + // heuristic: 80% of the original size + aSmallerFont.SetFontHeight( static_cast<tools::Long>( 0.8 * aSmallerFont.GetFontHeight() ) ); + + // let's calculate the size of our two texts + ::tools::Rectangle aPrimaryRect = rRenderContext.GetTextRect( _rRect, m_sPrimaryText, nTextStyle ); + ::tools::Rectangle aSecondaryRect; + { + FontSwitch aFontRestore(rRenderContext, aSmallerFont); + aSecondaryRect = rRenderContext.GetTextRect(_rRect, m_sSecondaryText, nTextStyle); + } + + // position these rectangles properly + // x-axis: + sal_Int32 nCombinedWidth = std::max( aSecondaryRect.GetWidth(), aPrimaryRect.GetWidth() ); + // the rectangle where both texts will reside is as high as possible, and as wide as the + // widest of both text rects + aPrimaryRect.SetLeft( _rRect.Left() ); + aSecondaryRect.SetLeft( aPrimaryRect.Left() ); + aPrimaryRect.SetRight( _rRect.Left() + nCombinedWidth ); + aSecondaryRect.SetRight( aPrimaryRect.Right() ); + + // y-axis: + sal_Int32 nCombinedHeight = aPrimaryRect.GetHeight() + aSecondaryRect.GetHeight(); + // align to the top, for the moment + aPrimaryRect.Move( 0, _rRect.Top() - aPrimaryRect.Top() ); + aSecondaryRect.Move( 0, aPrimaryRect.Top() + aPrimaryRect.GetHeight() - aSecondaryRect.Top() ); + // move the rects to the bottom + aPrimaryRect.Move( 0, ( aPlaygroundSize.Height() - nCombinedHeight ) / 2 ); + aSecondaryRect.Move( 0, ( aPlaygroundSize.Height() - nCombinedHeight ) / 2 ); + + // 'til here, everything we did assumes that the secondary text is painted _below_ the primary + // text. If this isn't the case, we need to correct the rectangles + if (eAbove == m_ePosition) + { + sal_Int32 nVertDistance = aSecondaryRect.Top() - aPrimaryRect.Top(); + aSecondaryRect.Move( 0, -nVertDistance ); + aPrimaryRect.Move( 0, nCombinedHeight - nVertDistance ); + } + + // now draw the texts + // as we already calculated the precise rectangles for the texts, we don't want to + // use the alignment flags given - within its rect, every text is centered + DrawTextFlags nDrawTextStyle( nTextStyle ); + nDrawTextStyle &= ~DrawTextFlags( DrawTextFlags::Right | DrawTextFlags::Left | DrawTextFlags::Bottom | DrawTextFlags::Top ); + nDrawTextStyle |= DrawTextFlags::Center | DrawTextFlags::VCenter; + + rRenderContext.DrawText( aPrimaryRect, m_sPrimaryText, nDrawTextStyle ); + { + FontSwitch aFontRestore(rRenderContext, aSmallerFont); + rRenderContext.DrawText( aSecondaryRect, m_sSecondaryText, nDrawTextStyle ); + } + + // outta here + if (_pPrimaryLocation) + *_pPrimaryLocation = aPrimaryRect; + if (_pSecondaryLocation) + *_pSecondaryLocation = aSecondaryRect; + } + + class RubyRadioButton + { + public: + RubyRadioButton(std::unique_ptr<weld::RadioButton> xControl, std::unique_ptr<weld::Image> xImage); + void init(const OUString& rPrimaryText, const OUString& rSecondaryText, const PseudoRubyText::RubyPosition& rPosition); + + void set_sensitive(bool sensitive) + { + m_xControl->set_sensitive(sensitive); + m_xImage->set_sensitive(sensitive); + } + void set_active(bool active) { m_xControl->set_active(active); } + bool get_active() const { return m_xControl->get_active(); } + + void connect_toggled(const Link<weld::Toggleable&, void>& rLink) { m_xControl->connect_toggled(rLink); } + + private: + Size GetOptimalSize() const; + void Paint(vcl::RenderContext& rRenderContext); + + ScopedVclPtr<VirtualDevice> m_xVirDev; + std::unique_ptr<weld::RadioButton> m_xControl; + std::unique_ptr<weld::Image> m_xImage; + PseudoRubyText m_aRubyText; + }; + + RubyRadioButton::RubyRadioButton(std::unique_ptr<weld::RadioButton> xControl, std::unique_ptr<weld::Image> xImage) + : m_xVirDev(xControl->create_virtual_device()) + , m_xControl(std::move(xControl)) + , m_xImage(std::move(xImage)) + { + // expand the point size of the desired font to the equivalent pixel size + weld::SetPointFont(*m_xVirDev, m_xControl->get_font()); + } + + void RubyRadioButton::init( const OUString& rPrimaryText, const OUString& rSecondaryText, const PseudoRubyText::RubyPosition& rPosition ) + { + m_aRubyText.init(rPrimaryText, rSecondaryText, rPosition); + + m_xVirDev->SetOutputSizePixel(GetOptimalSize()); + + Paint(*m_xVirDev); + + m_xImage->set_image(m_xVirDev.get()); + } + + void RubyRadioButton::Paint(vcl::RenderContext& rRenderContext) + { + ::tools::Rectangle aOverallRect(Point(0, 0), rRenderContext.GetOutputSizePixel()); + // inflate the rect a little bit (because the VCL radio button does the same) + ::tools::Rectangle aTextRect( aOverallRect ); + aTextRect.AdjustLeft( 1 ); aTextRect.AdjustRight( -1 ); + aTextRect.AdjustTop( 1 ); aTextRect.AdjustBottom( -1 ); + + // paint the ruby text + ::tools::Rectangle aPrimaryTextLocation; + ::tools::Rectangle aSecondaryTextLocation; + + m_aRubyText.Paint(rRenderContext, aTextRect, &aPrimaryTextLocation, &aSecondaryTextLocation); + } + + Size RubyRadioButton::GetOptimalSize() const + { + vcl::Font aSmallerFont(m_xVirDev->GetFont()); + aSmallerFont.SetFontHeight( static_cast<tools::Long>( 0.8 * aSmallerFont.GetFontHeight() ) ); + ::tools::Rectangle rect( Point(), Size( SAL_MAX_INT32, SAL_MAX_INT32 ) ); + + Size aPrimarySize = m_xVirDev->GetTextRect( rect, m_aRubyText.getPrimaryText() ).GetSize(); + Size aSecondarySize; + { + FontSwitch aFontRestore(*m_xVirDev, aSmallerFont); + aSecondarySize = m_xVirDev->GetTextRect( rect, m_aRubyText.getSecondaryText() ).GetSize(); + } + + Size minimumSize; + minimumSize.setHeight( aPrimarySize.Height() + aSecondarySize.Height() + 5 ); + minimumSize.setWidth(std::max(aPrimarySize.Width(), aSecondarySize.Width()) + 5 ); + return minimumSize; + } + + SuggestionSet::SuggestionSet(std::unique_ptr<weld::ScrolledWindow> xScrolledWindow) + : ValueSet(std::move(xScrolledWindow)) + + { + } + + void SuggestionSet::UserDraw( const UserDrawEvent& rUDEvt ) + { + vcl::RenderContext* pDev = rUDEvt.GetRenderContext(); + ::tools::Rectangle aRect = rUDEvt.GetRect(); + sal_uInt16 nItemId = rUDEvt.GetItemId(); + + OUString sText = *static_cast< OUString* >( GetItemData( nItemId ) ); + pDev->DrawText( aRect, sText, DrawTextFlags::Center | DrawTextFlags::VCenter ); + } + + SuggestionDisplay::SuggestionDisplay(weld::Builder& rBuilder) + : m_bDisplayListBox( true ) + , m_bInSelectionUpdate( false ) + , m_xValueSet(new SuggestionSet(rBuilder.weld_scrolled_window("scrollwin", true))) + , m_xValueSetWin(new weld::CustomWeld(rBuilder, "valueset", *m_xValueSet)) + , m_xListBox(rBuilder.weld_tree_view("listbox")) + { + m_xValueSet->SetSelectHdl( LINK( this, SuggestionDisplay, SelectSuggestionValueSetHdl ) ); + m_xListBox->connect_changed( LINK( this, SuggestionDisplay, SelectSuggestionListBoxHdl ) ); + + m_xValueSet->SetLineCount( LINE_CNT ); + m_xValueSet->SetStyle( m_xValueSet->GetStyle() | WB_ITEMBORDER | WB_VSCROLL ); + + auto nItemWidth = 2 * m_xListBox->get_pixel_size("AU").Width(); + m_xValueSet->SetItemWidth( nItemWidth ); + + Size aSize(m_xListBox->get_approximate_digit_width() * 42, m_xListBox->get_text_height() * 5); + m_xValueSet->set_size_request(aSize.Width(), aSize.Height()); + m_xListBox->set_size_request(aSize.Width(), aSize.Height()); + + implUpdateDisplay(); + } + + void SuggestionDisplay::implUpdateDisplay() + { + m_xListBox->set_visible(m_bDisplayListBox); + if (!m_bDisplayListBox) + m_xValueSetWin->show(); + else + m_xValueSetWin->hide(); + } + + weld::Widget& SuggestionDisplay::implGetCurrentControl() + { + if (m_bDisplayListBox) + return *m_xListBox; + return *m_xValueSet->GetDrawingArea(); + } + + void SuggestionDisplay::DisplayListBox( bool bDisplayListBox ) + { + if( m_bDisplayListBox == bDisplayListBox ) + return; + + weld::Widget& rOldControl = implGetCurrentControl(); + bool bHasFocus = rOldControl.has_focus(); + + m_bDisplayListBox = bDisplayListBox; + + if( bHasFocus ) + { + weld::Widget& rNewControl = implGetCurrentControl(); + rNewControl.grab_focus(); + } + + implUpdateDisplay(); + } + + IMPL_LINK_NOARG(SuggestionDisplay, SelectSuggestionValueSetHdl, ValueSet*, void) + { + SelectSuggestionHdl(false); + } + + IMPL_LINK_NOARG(SuggestionDisplay, SelectSuggestionListBoxHdl, weld::TreeView&, void) + { + SelectSuggestionHdl(true); + } + + void SuggestionDisplay::SelectSuggestionHdl(bool bListBox) + { + if( m_bInSelectionUpdate ) + return; + + m_bInSelectionUpdate = true; + if (bListBox) + { + sal_uInt16 nPos = m_xListBox->get_selected_index(); + m_xValueSet->SelectItem( nPos+1 ); //itemid == pos+1 (id 0 has special meaning) + } + else + { + sal_uInt16 nPos = m_xValueSet->GetSelectedItemId()-1; //itemid == pos+1 (id 0 has special meaning) + m_xListBox->select(nPos); + } + m_bInSelectionUpdate = false; + m_aSelectLink.Call( *this ); + } + + void SuggestionDisplay::SetSelectHdl( const Link<SuggestionDisplay&,void>& rLink ) + { + m_aSelectLink = rLink; + } + + void SuggestionDisplay::Clear() + { + m_xListBox->clear(); + m_xValueSet->Clear(); + } + + void SuggestionDisplay::InsertEntry( const OUString& rStr ) + { + m_xListBox->append_text(rStr); + sal_uInt16 nItemId = m_xListBox->n_children(); //itemid == pos+1 (id 0 has special meaning) + m_xValueSet->InsertItem( nItemId ); + OUString* pItemData = new OUString( rStr ); + m_xValueSet->SetItemData( nItemId, pItemData ); + } + + void SuggestionDisplay::SelectEntryPos( sal_uInt16 nPos ) + { + m_xListBox->select(nPos); + m_xValueSet->SelectItem( nPos+1 ); //itemid == pos+1 (id 0 has special meaning) + } + + sal_uInt16 SuggestionDisplay::GetEntryCount() const + { + return m_xListBox->n_children(); + } + + OUString SuggestionDisplay::GetEntry( sal_uInt16 nPos ) const + { + return m_xListBox->get_text( nPos ); + } + + OUString SuggestionDisplay::GetSelectedEntry() const + { + return m_xListBox->get_selected_text(); + } + + void SuggestionDisplay::SetHelpIds() + { + m_xValueSet->SetHelpId(HID_HANGULDLG_SUGGESTIONS_GRID); + m_xListBox->set_help_id(HID_HANGULDLG_SUGGESTIONS_LIST); + } + + HangulHanjaConversionDialog::HangulHanjaConversionDialog(weld::Widget* pParent) + : GenericDialogController(pParent, "cui/ui/hangulhanjaconversiondialog.ui", "HangulHanjaConversionDialog") + , m_bDocumentMode( true ) + , m_xFind(m_xBuilder->weld_button("find")) + , m_xIgnore(m_xBuilder->weld_button("ignore")) + , m_xIgnoreAll(m_xBuilder->weld_button("ignoreall")) + , m_xReplace(m_xBuilder->weld_button("replace")) + , m_xReplaceAll(m_xBuilder->weld_button("replaceall")) + , m_xOptions(m_xBuilder->weld_button("options")) + , m_xSuggestions(new SuggestionDisplay(*m_xBuilder)) + , m_xSimpleConversion(m_xBuilder->weld_radio_button("simpleconversion")) + , m_xHangulBracketed(m_xBuilder->weld_radio_button("hangulbracket")) + , m_xHanjaBracketed(m_xBuilder->weld_radio_button("hanjabracket")) + , m_xWordInput(m_xBuilder->weld_entry("wordinput")) + , m_xOriginalWord(m_xBuilder->weld_label("originalword")) + , m_xHanjaAbove(new RubyRadioButton(m_xBuilder->weld_radio_button("hanja_above"), + m_xBuilder->weld_image("hanja_above_img"))) + , m_xHanjaBelow(new RubyRadioButton(m_xBuilder->weld_radio_button("hanja_below"), + m_xBuilder->weld_image("hanja_below_img"))) + , m_xHangulAbove(new RubyRadioButton(m_xBuilder->weld_radio_button("hangul_above"), + m_xBuilder->weld_image("hangul_above_img"))) + , m_xHangulBelow(new RubyRadioButton(m_xBuilder->weld_radio_button("hangul_below"), + m_xBuilder->weld_image("hangul_below_img"))) + , m_xHangulOnly(m_xBuilder->weld_check_button("hangulonly")) + , m_xHanjaOnly(m_xBuilder->weld_check_button("hanjaonly")) + , m_xReplaceByChar(m_xBuilder->weld_check_button("replacebychar")) + { + m_xSuggestions->set_size_request(m_xOriginalWord->get_approximate_digit_width() * 42, + m_xOriginalWord->get_text_height() * 5); + + const OUString sHangul(CuiResId(RID_CUISTR_HANGUL)); + const OUString sHanja(CuiResId(RID_CUISTR_HANJA)); + m_xHanjaAbove->init( sHangul, sHanja, PseudoRubyText::eAbove ); + m_xHanjaBelow->init( sHangul, sHanja, PseudoRubyText::eBelow ); + m_xHangulAbove->init( sHanja, sHangul, PseudoRubyText::eAbove ); + m_xHangulBelow->init( sHanja, sHangul, PseudoRubyText::eBelow ); + + m_xWordInput->connect_changed( LINK( this, HangulHanjaConversionDialog, OnSuggestionModified ) ); + m_xSuggestions->SetSelectHdl( LINK( this, HangulHanjaConversionDialog, OnSuggestionSelected ) ); + m_xReplaceByChar->connect_toggled( LINK( this, HangulHanjaConversionDialog, ClickByCharacterHdl ) ); + m_xHangulOnly->connect_toggled( LINK( this, HangulHanjaConversionDialog, OnConversionDirectionClicked ) ); + m_xHanjaOnly->connect_toggled( LINK( this, HangulHanjaConversionDialog, OnConversionDirectionClicked ) ); + m_xOptions->connect_clicked(LINK(this, HangulHanjaConversionDialog, OnOption)); + + // initial focus + FocusSuggestion( ); + + // initial control values + m_xSimpleConversion->set_active(true); + + m_xSuggestions->SetHelpIds(); + } + + HangulHanjaConversionDialog::~HangulHanjaConversionDialog() + { + } + + void HangulHanjaConversionDialog::FillSuggestions( const css::uno::Sequence< OUString >& _rSuggestions ) + { + m_xSuggestions->Clear(); + for ( auto const & suggestion : _rSuggestions ) + m_xSuggestions->InsertEntry( suggestion ); + + // select the first suggestion, and fill in the suggestion edit field + OUString sFirstSuggestion; + if ( m_xSuggestions->GetEntryCount() ) + { + sFirstSuggestion = m_xSuggestions->GetEntry( 0 ); + m_xSuggestions->SelectEntryPos( 0 ); + } + m_xWordInput->set_text( sFirstSuggestion ); + m_xWordInput->save_value(); + OnSuggestionModified( *m_xWordInput ); + } + + void HangulHanjaConversionDialog::SetOptionsChangedHdl(const Link<LinkParamNone*,void>& rHdl) + { + m_aOptionsChangedLink = rHdl; + } + + void HangulHanjaConversionDialog::SetIgnoreHdl(const Link<weld::Button&,void>& rHdl) + { + m_xIgnore->connect_clicked(rHdl); + } + + void HangulHanjaConversionDialog::SetIgnoreAllHdl(const Link<weld::Button&,void>& rHdl) + { + m_xIgnoreAll->connect_clicked(rHdl); + } + + void HangulHanjaConversionDialog::SetChangeHdl(const Link<weld::Button&,void>& rHdl ) + { + m_xReplace->connect_clicked(rHdl); + } + + void HangulHanjaConversionDialog::SetChangeAllHdl(const Link<weld::Button&,void>& rHdl) + { + m_xReplaceAll->connect_clicked(rHdl); + } + + void HangulHanjaConversionDialog::SetFindHdl(const Link<weld::Button&,void>& rHdl) + { + m_xFind->connect_clicked(rHdl); + } + + void HangulHanjaConversionDialog::SetConversionFormatChangedHdl( const Link<weld::Toggleable&,void>& rHdl ) + { + m_xSimpleConversion->connect_toggled( rHdl ); + m_xHangulBracketed->connect_toggled( rHdl ); + m_xHanjaBracketed->connect_toggled( rHdl ); + m_xHanjaAbove->connect_toggled( rHdl ); + m_xHanjaBelow->connect_toggled( rHdl ); + m_xHangulAbove->connect_toggled( rHdl ); + m_xHangulBelow->connect_toggled( rHdl ); + } + + void HangulHanjaConversionDialog::SetClickByCharacterHdl( const Link<weld::Toggleable&,void>& _rHdl ) + { + m_aClickByCharacterLink = _rHdl; + } + + IMPL_LINK_NOARG( HangulHanjaConversionDialog, OnSuggestionSelected, SuggestionDisplay&, void ) + { + m_xWordInput->set_text(m_xSuggestions->GetSelectedEntry()); + OnSuggestionModified( *m_xWordInput ); + } + + IMPL_LINK_NOARG( HangulHanjaConversionDialog, OnSuggestionModified, weld::Entry&, void ) + { + m_xFind->set_sensitive(m_xWordInput->get_value_changed_from_saved()); + + bool bSameLen = m_xWordInput->get_text().getLength() == m_xOriginalWord->get_label().getLength(); + m_xReplace->set_sensitive( m_bDocumentMode && bSameLen ); + m_xReplaceAll->set_sensitive( m_bDocumentMode && bSameLen ); + } + + IMPL_LINK(HangulHanjaConversionDialog, ClickByCharacterHdl, weld::Toggleable&, rBox, void) + { + m_aClickByCharacterLink.Call(rBox); + bool bByCharacter = rBox.get_active(); + m_xSuggestions->DisplayListBox( !bByCharacter ); + } + + IMPL_LINK(HangulHanjaConversionDialog, OnConversionDirectionClicked, weld::Toggleable&, rBox, void) + { + weld::CheckButton* pOtherBox = nullptr; + if (&rBox == m_xHangulOnly.get()) + pOtherBox = m_xHanjaOnly.get(); + else + pOtherBox = m_xHangulOnly.get(); + bool bBoxChecked = rBox.get_active(); + if (bBoxChecked) + pOtherBox->set_active(false); + pOtherBox->set_sensitive(!bBoxChecked); + } + + IMPL_LINK_NOARG(HangulHanjaConversionDialog, OnOption, weld::Button&, void) + { + HangulHanjaOptionsDialog aOptDlg(m_xDialog.get()); + aOptDlg.run(); + m_aOptionsChangedLink.Call( nullptr ); + } + + OUString HangulHanjaConversionDialog::GetCurrentString( ) const + { + return m_xOriginalWord->get_label(); + } + + void HangulHanjaConversionDialog::FocusSuggestion( ) + { + m_xWordInput->grab_focus(); + } + + void HangulHanjaConversionDialog::SetCurrentString( const OUString& _rNewString, + const Sequence< OUString >& _rSuggestions, bool _bOriginatesFromDocument ) + { + m_xOriginalWord->set_label(_rNewString); + + bool bOldDocumentMode = m_bDocumentMode; + m_bDocumentMode = _bOriginatesFromDocument; // before FillSuggestions! + FillSuggestions( _rSuggestions ); + + m_xIgnoreAll->set_sensitive( m_bDocumentMode ); + + // switch the def button depending if we're working for document text + if (bOldDocumentMode == m_bDocumentMode) + return; + + weld::Widget* pOldDefButton = nullptr; + weld::Widget* pNewDefButton = nullptr; + if (m_bDocumentMode) + { + pOldDefButton = m_xFind.get(); + pNewDefButton = m_xReplace.get(); + } + else + { + pOldDefButton = m_xReplace.get(); + pNewDefButton = m_xFind.get(); + } + + m_xDialog->change_default_widget(pOldDefButton, pNewDefButton); + } + + OUString HangulHanjaConversionDialog::GetCurrentSuggestion( ) const + { + return m_xWordInput->get_text(); + } + + void HangulHanjaConversionDialog::SetByCharacter( bool _bByCharacter ) + { + m_xReplaceByChar->set_active( _bByCharacter ); + m_xSuggestions->DisplayListBox( !_bByCharacter ); + } + + void HangulHanjaConversionDialog::SetConversionDirectionState( + bool _bTryBothDirections, + HHC::ConversionDirection ePrimaryConversionDirection ) + { + // default state: try both direction + m_xHangulOnly->set_active( false ); + m_xHangulOnly->set_sensitive(true); + m_xHanjaOnly->set_active( false ); + m_xHanjaOnly->set_sensitive(true); + + if (!_bTryBothDirections) + { + weld::CheckButton* pBox = ePrimaryConversionDirection == HHC::eHangulToHanja ? + m_xHangulOnly.get() : m_xHanjaOnly.get(); + pBox->set_active(true); + OnConversionDirectionClicked(*pBox); + } + } + + bool HangulHanjaConversionDialog::GetUseBothDirections( ) const + { + return !m_xHangulOnly->get_active() && !m_xHanjaOnly->get_active(); + } + + HHC::ConversionDirection HangulHanjaConversionDialog::GetDirection( + HHC::ConversionDirection eDefaultDirection ) const + { + HHC::ConversionDirection eDirection = eDefaultDirection; + if (m_xHangulOnly->get_active() && !m_xHanjaOnly->get_active()) + eDirection = HHC::eHangulToHanja; + else if (!m_xHangulOnly->get_active() && m_xHanjaOnly->get_active()) + eDirection = HHC::eHanjaToHangul; + return eDirection; + } + + void HangulHanjaConversionDialog::SetConversionFormat( HHC::ConversionFormat _eType ) + { + switch ( _eType ) + { + case HHC::eSimpleConversion: m_xSimpleConversion->set_active(true); break; + case HHC::eHangulBracketed: m_xHangulBracketed->set_active(true); break; + case HHC::eHanjaBracketed: m_xHanjaBracketed->set_active(true); break; + case HHC::eRubyHanjaAbove: m_xHanjaAbove->set_active(true); break; + case HHC::eRubyHanjaBelow: m_xHanjaBelow->set_active(true); break; + case HHC::eRubyHangulAbove: m_xHangulAbove->set_active(true); break; + case HHC::eRubyHangulBelow: m_xHangulBelow->set_active(true); break; + default: + OSL_FAIL( "HangulHanjaConversionDialog::SetConversionFormat: unknown type!" ); + } + } + + HHC::ConversionFormat HangulHanjaConversionDialog::GetConversionFormat( ) const + { + if ( m_xSimpleConversion->get_active() ) + return HHC::eSimpleConversion; + if ( m_xHangulBracketed->get_active() ) + return HHC::eHangulBracketed; + if ( m_xHanjaBracketed->get_active() ) + return HHC::eHanjaBracketed; + if ( m_xHanjaAbove->get_active() ) + return HHC::eRubyHanjaAbove; + if ( m_xHanjaBelow->get_active() ) + return HHC::eRubyHanjaBelow; + if ( m_xHangulAbove->get_active() ) + return HHC::eRubyHangulAbove; + if ( m_xHangulBelow->get_active() ) + return HHC::eRubyHangulBelow; + + OSL_FAIL( "HangulHanjaConversionDialog::GetConversionFormat: no radio checked?" ); + return HHC::eSimpleConversion; + } + + void HangulHanjaConversionDialog::EnableRubySupport( bool bVal ) + { + m_xHanjaAbove->set_sensitive( bVal ); + m_xHanjaBelow->set_sensitive( bVal ); + m_xHangulAbove->set_sensitive( bVal ); + m_xHangulBelow->set_sensitive( bVal ); + } + + void HangulHanjaOptionsDialog::Init() + { + if( !m_xConversionDictionaryList.is() ) + { + m_xConversionDictionaryList = ConversionDictionaryList::create( ::comphelper::getProcessComponentContext() ); + } + + m_aDictList.clear(); + m_xDictsLB->clear(); + + Reference< XNameContainer > xNameCont = m_xConversionDictionaryList->getDictionaryContainer(); + if( xNameCont.is() ) + { + Sequence< OUString > aDictNames( xNameCont->getElementNames() ); + + const OUString* pDic = aDictNames.getConstArray(); + sal_Int32 nCount = aDictNames.getLength(); + + sal_Int32 i; + for( i = 0 ; i < nCount ; ++i ) + { + Any aAny( xNameCont->getByName( pDic[ i ] ) ); + Reference< XConversionDictionary > xDic; + if( ( aAny >>= xDic ) && xDic.is() ) + { + if( LANGUAGE_KOREAN == LanguageTag( xDic->getLocale() ).getLanguageType() ) + { + m_aDictList.push_back( xDic ); + AddDict( xDic->getName(), xDic->isActive() ); + } + } + } + } + if (m_xDictsLB->n_children()) + m_xDictsLB->select(0); + } + + IMPL_LINK_NOARG(HangulHanjaOptionsDialog, OkHdl, weld::Button&, void) + { + sal_uInt32 nCnt = m_aDictList.size(); + sal_uInt32 n = 0; + sal_uInt32 nActiveDics = 0; + Sequence< OUString > aActiveDics; + + aActiveDics.realloc( nCnt ); + OUString* pActActiveDic = aActiveDics.getArray(); + + while( nCnt ) + { + Reference< XConversionDictionary > xDict = m_aDictList[ n ]; + + DBG_ASSERT( xDict.is(), "-HangulHanjaOptionsDialog::OkHdl(): someone is evaporated..." ); + + bool bActive = m_xDictsLB->get_toggle(n) == TRISTATE_TRUE; + xDict->setActive( bActive ); + Reference< util::XFlushable > xFlush( xDict, uno::UNO_QUERY ); + if( xFlush.is() ) + xFlush->flush(); + + if( bActive ) + { + pActActiveDic[ nActiveDics ] = xDict->getName(); + ++nActiveDics; + } + + ++n; + --nCnt; + } + + // save configuration + aActiveDics.realloc( nActiveDics ); + Any aTmp; + SvtLinguConfig aLngCfg; + aTmp <<= aActiveDics; + aLngCfg.SetProperty( UPH_ACTIVE_CONVERSION_DICTIONARIES, aTmp ); + + aTmp <<= m_xIgnorepostCB->get_active(); + aLngCfg.SetProperty( UPH_IS_IGNORE_POST_POSITIONAL_WORD, aTmp ); + + aTmp <<= m_xShowrecentlyfirstCB->get_active(); + aLngCfg.SetProperty( UPH_IS_SHOW_ENTRIES_RECENTLY_USED_FIRST, aTmp ); + + aTmp <<= m_xAutoreplaceuniqueCB->get_active(); + aLngCfg.SetProperty( UPH_IS_AUTO_REPLACE_UNIQUE_ENTRIES, aTmp ); + + m_xDialog->response(RET_OK); + } + + IMPL_LINK_NOARG(HangulHanjaOptionsDialog, DictsLB_SelectHdl, weld::TreeView&, void) + { + bool bSel = m_xDictsLB->get_selected_index() != -1; + + m_xEditPB->set_sensitive(bSel); + m_xDeletePB->set_sensitive(bSel); + } + + IMPL_LINK_NOARG(HangulHanjaOptionsDialog, NewDictHdl, weld::Button&, void) + { + OUString aName; + HangulHanjaNewDictDialog aNewDlg(m_xDialog.get()); + aNewDlg.run(); + if (!aNewDlg.GetName(aName)) + return; + + if( !m_xConversionDictionaryList.is() ) + return; + + try + { + Reference< XConversionDictionary > xDic = + m_xConversionDictionaryList->addNewDictionary( aName, LanguageTag::convertToLocale( LANGUAGE_KOREAN ), ConversionDictionaryType::HANGUL_HANJA ); + + if( xDic.is() ) + { + //adapt local caches: + m_aDictList.push_back( xDic ); + AddDict( xDic->getName(), xDic->isActive() ); + } + } + catch( const ElementExistException& ) + { + } + catch( const NoSupportException& ) + { + } + } + + IMPL_LINK_NOARG(HangulHanjaOptionsDialog, EditDictHdl, weld::Button&, void) + { + int nEntry = m_xDictsLB->get_selected_index(); + DBG_ASSERT(nEntry != -1, "+HangulHanjaEditDictDialog::EditDictHdl(): call of edit should not be possible with no selection!"); + if (nEntry != -1) + { + HangulHanjaEditDictDialog aEdDlg(m_xDialog.get(), m_aDictList, nEntry); + aEdDlg.run(); + } + } + + IMPL_LINK_NOARG(HangulHanjaOptionsDialog, DeleteDictHdl, weld::Button&, void) + { + int nSelPos = m_xDictsLB->get_selected_index(); + if (nSelPos == -1) + return; + + Reference< XConversionDictionary > xDic( m_aDictList[ nSelPos ] ); + if( !(m_xConversionDictionaryList.is() && xDic.is()) ) + return; + + Reference< XNameContainer > xNameCont = m_xConversionDictionaryList->getDictionaryContainer(); + if( !xNameCont.is() ) + return; + + try + { + xNameCont->removeByName( xDic->getName() ); + + //adapt local caches: + m_aDictList.erase(m_aDictList.begin()+nSelPos ); + m_xDictsLB->remove(nSelPos); + } + catch( const ElementExistException& ) + { + } + catch( const NoSupportException& ) + { + } + } + + HangulHanjaOptionsDialog::HangulHanjaOptionsDialog(weld::Window* pParent) + : GenericDialogController(pParent, "cui/ui/hangulhanjaoptdialog.ui", "HangulHanjaOptDialog") + , m_xDictsLB(m_xBuilder->weld_tree_view("dicts")) + , m_xIgnorepostCB(m_xBuilder->weld_check_button("ignorepost")) + , m_xShowrecentlyfirstCB(m_xBuilder->weld_check_button("showrecentfirst")) + , m_xAutoreplaceuniqueCB(m_xBuilder->weld_check_button("autoreplaceunique")) + , m_xNewPB(m_xBuilder->weld_button("new")) + , m_xEditPB(m_xBuilder->weld_button("edit")) + , m_xDeletePB(m_xBuilder->weld_button("delete")) + , m_xOkPB(m_xBuilder->weld_button("ok")) + { + m_xDictsLB->set_size_request(m_xDictsLB->get_approximate_digit_width() * 32, + m_xDictsLB->get_height_rows(5)); + + m_xDictsLB->enable_toggle_buttons(weld::ColumnToggleType::Check); + + m_xDictsLB->connect_changed( LINK( this, HangulHanjaOptionsDialog, DictsLB_SelectHdl ) ); + + m_xOkPB->connect_clicked( LINK( this, HangulHanjaOptionsDialog, OkHdl ) ); + m_xNewPB->connect_clicked( LINK( this, HangulHanjaOptionsDialog, NewDictHdl ) ); + m_xEditPB->connect_clicked( LINK( this, HangulHanjaOptionsDialog, EditDictHdl ) ); + m_xDeletePB->connect_clicked( LINK( this, HangulHanjaOptionsDialog, DeleteDictHdl ) ); + + SvtLinguConfig aLngCfg; + Any aTmp; + bool bVal = bool(); + aTmp = aLngCfg.GetProperty( UPH_IS_IGNORE_POST_POSITIONAL_WORD ); + if( aTmp >>= bVal ) + m_xIgnorepostCB->set_active( bVal ); + + aTmp = aLngCfg.GetProperty( UPH_IS_SHOW_ENTRIES_RECENTLY_USED_FIRST ); + if( aTmp >>= bVal ) + m_xShowrecentlyfirstCB->set_active( bVal ); + + aTmp = aLngCfg.GetProperty( UPH_IS_AUTO_REPLACE_UNIQUE_ENTRIES ); + if( aTmp >>= bVal ) + m_xAutoreplaceuniqueCB->set_active( bVal ); + + Init(); + } + + HangulHanjaOptionsDialog::~HangulHanjaOptionsDialog() + { + } + + void HangulHanjaOptionsDialog::AddDict(const OUString& rName, bool bChecked) + { + m_xDictsLB->append(); + int nRow = m_xDictsLB->n_children() - 1; + m_xDictsLB->set_toggle(nRow, bChecked ? TRISTATE_TRUE : TRISTATE_FALSE); + m_xDictsLB->set_text(nRow, rName, 0); + m_xDictsLB->set_id(nRow, rName); + } + + IMPL_LINK_NOARG(HangulHanjaNewDictDialog, OKHdl, weld::Button&, void) + { + OUString aName(comphelper::string::stripEnd(m_xDictNameED->get_text(), ' ')); + + m_bEntered = !aName.isEmpty(); + if (m_bEntered) + m_xDictNameED->set_text(aName); // do this in case of trailing chars have been deleted + + m_xDialog->response(RET_OK); + } + + IMPL_LINK_NOARG(HangulHanjaNewDictDialog, ModifyHdl, weld::Entry&, void) + { + OUString aName(comphelper::string::stripEnd(m_xDictNameED->get_text(), ' ')); + + m_xOkBtn->set_sensitive(!aName.isEmpty()); + } + + HangulHanjaNewDictDialog::HangulHanjaNewDictDialog(weld::Window* pParent) + : GenericDialogController(pParent, "cui/ui/hangulhanjaadddialog.ui", "HangulHanjaAddDialog") + , m_bEntered(false) + , m_xOkBtn(m_xBuilder->weld_button("ok")) + , m_xDictNameED(m_xBuilder->weld_entry("entry")) + { + m_xOkBtn->connect_clicked( LINK( this, HangulHanjaNewDictDialog, OKHdl ) ); + m_xDictNameED->connect_changed( LINK( this, HangulHanjaNewDictDialog, ModifyHdl ) ); + } + + HangulHanjaNewDictDialog::~HangulHanjaNewDictDialog() + { + } + + bool HangulHanjaNewDictDialog::GetName( OUString& _rRetName ) const + { + if( m_bEntered ) + _rRetName = comphelper::string::stripEnd(m_xDictNameED->get_text(), ' '); + + return m_bEntered; + } + + class SuggestionList + { + private: + protected: + std::vector<OUString> m_vElements; + sal_uInt16 m_nNumOfEntries; + // index of the internal iterator, used for First() and Next() methods + sal_uInt16 m_nAct; + + const OUString* Next_(); + public: + SuggestionList(); + ~SuggestionList(); + + void Set( const OUString& _rElement, sal_uInt16 _nNumOfElement ); + void Reset( sal_uInt16 _nNumOfElement ); + const OUString & Get( sal_uInt16 _nNumOfElement ) const; + void Clear(); + + const OUString* First(); + const OUString* Next(); + + sal_uInt16 GetCount() const { return m_nNumOfEntries; } + }; + + SuggestionList::SuggestionList() : + m_vElements(MAXNUM_SUGGESTIONS) + { + m_nAct = m_nNumOfEntries = 0; + } + + SuggestionList::~SuggestionList() + { + Clear(); + } + + void SuggestionList::Set( const OUString& _rElement, sal_uInt16 _nNumOfElement ) + { + m_vElements[_nNumOfElement] = _rElement; + ++m_nNumOfEntries; + } + + void SuggestionList::Reset( sal_uInt16 _nNumOfElement ) + { + m_vElements[_nNumOfElement].clear(); + --m_nNumOfEntries; + } + + const OUString& SuggestionList::Get( sal_uInt16 _nNumOfElement ) const + { + return m_vElements[_nNumOfElement]; + } + + void SuggestionList::Clear() + { + if( m_nNumOfEntries ) + { + for (auto & vElement : m_vElements) + vElement.clear(); + m_nNumOfEntries = m_nAct = 0; + } + } + + const OUString* SuggestionList::Next_() + { + while( m_nAct < m_vElements.size() ) + { + auto & s = m_vElements[ m_nAct ]; + if (!s.isEmpty()) + return &s; + ++m_nAct; + } + + return nullptr; + } + + const OUString* SuggestionList::First() + { + m_nAct = 0; + return Next_(); + } + + const OUString* SuggestionList::Next() + { + const OUString* pRet; + + if( m_nAct < m_nNumOfEntries ) + { + ++m_nAct; + pRet = Next_(); + } + else + pRet = nullptr; + + return pRet; + } + + + bool SuggestionEdit::ShouldScroll( bool _bUp ) const + { + bool bRet = false; + + if( _bUp ) + { + if( !m_pPrev ) + bRet = m_pScrollBar->vadjustment_get_value() > m_pScrollBar->vadjustment_get_lower(); + } + else + { + if( !m_pNext ) + bRet = m_pScrollBar->vadjustment_get_value() < ( m_pScrollBar->vadjustment_get_upper() - 4 ); + } + + return bRet; + } + + void SuggestionEdit::DoJump( bool _bUp ) + { + m_pScrollBar->vadjustment_set_value( m_pScrollBar->vadjustment_get_value() + ( _bUp? -1 : 1 ) ); + m_pParent->UpdateScrollbar(); + } + + SuggestionEdit::SuggestionEdit(std::unique_ptr<weld::Entry> xEntry, HangulHanjaEditDictDialog* pParent) + : m_pParent(pParent) + , m_pPrev(nullptr) + , m_pNext(nullptr) + , m_pScrollBar(nullptr) + , m_xEntry(std::move(xEntry)) + { + m_xEntry->connect_key_press(LINK(this, SuggestionEdit, KeyInputHdl)); + } + + IMPL_LINK(SuggestionEdit, KeyInputHdl, const KeyEvent&, rKEvt, bool) + { + bool bHandled = false; + + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + sal_uInt16 nMod = rKeyCode.GetModifier(); + sal_uInt16 nCode = rKeyCode.GetCode(); + if( nCode == KEY_TAB && ( !nMod || KEY_SHIFT == nMod ) ) + { + bool bUp = KEY_SHIFT == nMod; + if( ShouldScroll( bUp ) ) + { + DoJump( bUp ); + m_xEntry->select_region(0, -1); + // Tab-travel doesn't really happen, so emulate it by setting a selection manually + bHandled = true; + } + } + else if( KEY_UP == nCode || KEY_DOWN == nCode ) + { + bool bUp = KEY_UP == nCode; + if( ShouldScroll( bUp ) ) + { + DoJump( bUp ); + bHandled = true; + } + else if( bUp ) + { + if( m_pPrev ) + { + m_pPrev->grab_focus(); + bHandled = true; + } + } + else if( m_pNext ) + { + m_pNext->grab_focus(); + bHandled = true; + } + } + + return bHandled; + } + + void SuggestionEdit::init(weld::ScrolledWindow* pScrollBar, SuggestionEdit* pPrev, SuggestionEdit* pNext) + { + m_pScrollBar = pScrollBar; + m_pPrev = pPrev; + m_pNext = pNext; + } + + namespace + { + bool GetConversions( const Reference< XConversionDictionary >& _xDict, + const OUString& _rOrg, + Sequence< OUString >& _rEntries ) + { + bool bRet = false; + if( _xDict.is() && !_rOrg.isEmpty() ) + { + try + { + _rEntries = _xDict->getConversions( _rOrg, + 0, + _rOrg.getLength(), + ConversionDirection_FROM_LEFT, + css::i18n::TextConversionOption::NONE ); + bRet = _rEntries.hasElements(); + } + catch( const IllegalArgumentException& ) + { + } + } + + return bRet; + } + } + + IMPL_LINK_NOARG( HangulHanjaEditDictDialog, ScrollHdl, weld::ScrolledWindow&, void ) + { + UpdateScrollbar(); + } + + IMPL_LINK_NOARG( HangulHanjaEditDictDialog, OriginalModifyHdl, weld::ComboBox&, void ) + { + m_bModifiedOriginal = true; + m_aOriginal = comphelper::string::stripEnd( m_xOriginalLB->get_active_text(), ' ' ); + + UpdateSuggestions(); + UpdateButtonStates(); + } + + IMPL_LINK( HangulHanjaEditDictDialog, EditModifyHdl1, weld::Entry&, rEdit, void ) + { + EditModify( &rEdit, 0 ); + } + + IMPL_LINK( HangulHanjaEditDictDialog, EditModifyHdl2, weld::Entry&, rEdit, void ) + { + EditModify( &rEdit, 1 ); + } + + IMPL_LINK( HangulHanjaEditDictDialog, EditModifyHdl3, weld::Entry&, rEdit, void ) + { + EditModify( &rEdit, 2 ); + } + + IMPL_LINK( HangulHanjaEditDictDialog, EditModifyHdl4, weld::Entry&, rEdit, void ) + { + EditModify( &rEdit, 3 ); + } + + IMPL_LINK_NOARG( HangulHanjaEditDictDialog, BookLBSelectHdl, weld::ComboBox&, void ) + { + InitEditDictDialog( m_xBookLB->get_active() ); + } + + IMPL_LINK_NOARG( HangulHanjaEditDictDialog, NewPBPushHdl, weld::Button&, void ) + { + DBG_ASSERT( m_xSuggestions, "-HangulHanjaEditDictDialog::NewPBPushHdl(): no suggestions... search in hell..." ); + Reference< XConversionDictionary > xDict = m_rDictList[ m_nCurrentDict ]; + if( xDict.is() && m_xSuggestions ) + { + //delete old entry + bool bRemovedSomething = DeleteEntryFromDictionary( xDict ); + + OUString aLeft( m_aOriginal ); + const OUString* pRight = m_xSuggestions->First(); + bool bAddedSomething = false; + while( pRight ) + { + try + { + //add new entry + xDict->addEntry( aLeft, *pRight ); + bAddedSomething = true; + } + catch( const IllegalArgumentException& ) + { + } + catch( const ElementExistException& ) + { + } + + pRight = m_xSuggestions->Next(); + } + + if( bAddedSomething || bRemovedSomething ) + InitEditDictDialog( m_nCurrentDict ); + } + else + { + SAL_INFO( "cui.dialogs", "dictionary faded away..." ); + } + } + + bool HangulHanjaEditDictDialog::DeleteEntryFromDictionary( const Reference< XConversionDictionary >& xDict ) + { + bool bRemovedSomething = false; + if( xDict.is() ) + { + OUString aOrg( m_aOriginal ); + Sequence< OUString > aEntries; + GetConversions( xDict, m_aOriginal, aEntries ); + + sal_uInt32 n = aEntries.getLength(); + OUString* pEntry = aEntries.getArray(); + while( n ) + { + try + { + xDict->removeEntry( aOrg, *pEntry ); + bRemovedSomething = true; + } + catch( const NoSuchElementException& ) + { // can not be... + } + + ++pEntry; + --n; + } + } + return bRemovedSomething; + } + + IMPL_LINK_NOARG( HangulHanjaEditDictDialog, DeletePBPushHdl, weld::Button&, void ) + { + if( DeleteEntryFromDictionary( m_rDictList[ m_nCurrentDict ] ) ) + { + m_aOriginal.clear(); + m_bModifiedOriginal = true; + InitEditDictDialog( m_nCurrentDict ); + } + } + + void HangulHanjaEditDictDialog::InitEditDictDialog( sal_uInt32 nSelDict ) + { + if( m_xSuggestions ) + m_xSuggestions->Clear(); + + if( m_nCurrentDict != nSelDict ) + { + m_nCurrentDict = nSelDict; + m_aOriginal.clear(); + m_bModifiedOriginal = true; + } + + UpdateOriginalLB(); + + m_xOriginalLB->set_entry_text( !m_aOriginal.isEmpty() ? m_aOriginal : m_aEditHintText); + m_xOriginalLB->select_entry_region(0, -1); + m_xOriginalLB->grab_focus(); + + UpdateSuggestions(); + UpdateButtonStates(); + } + + void HangulHanjaEditDictDialog::UpdateOriginalLB() + { + m_xOriginalLB->clear(); + Reference< XConversionDictionary > xDict = m_rDictList[ m_nCurrentDict ]; + if( xDict.is() ) + { + Sequence< OUString > aEntries = xDict->getConversionEntries( ConversionDirection_FROM_LEFT ); + sal_uInt32 n = aEntries.getLength(); + OUString* pEntry = aEntries.getArray(); + while( n ) + { + m_xOriginalLB->append_text( *pEntry ); + + ++pEntry; + --n; + } + } + else + { + SAL_INFO( "cui.dialogs", "dictionary faded away..." ); + } + } + + void HangulHanjaEditDictDialog::UpdateButtonStates() + { + bool bHaveValidOriginalString = !m_aOriginal.isEmpty() && m_aOriginal != m_aEditHintText; + bool bNew = bHaveValidOriginalString && m_xSuggestions && m_xSuggestions->GetCount() > 0; + bNew = bNew && ( m_bModifiedSuggestions || m_bModifiedOriginal ); + + m_xNewPB->set_sensitive( bNew ); + m_xDeletePB->set_sensitive(!m_bModifiedOriginal && bHaveValidOriginalString); + } + + void HangulHanjaEditDictDialog::UpdateSuggestions() + { + Sequence< OUString > aEntries; + bool bFound = GetConversions( m_rDictList[ m_nCurrentDict ], m_aOriginal, aEntries ); + if( bFound ) + { + m_bModifiedOriginal = false; + + if( m_xSuggestions ) + m_xSuggestions->Clear(); + + //fill found entries into boxes + sal_uInt32 nCnt = aEntries.getLength(); + if( nCnt ) + { + if( !m_xSuggestions ) + m_xSuggestions.reset(new SuggestionList); + + const OUString* pSugg = aEntries.getConstArray(); + sal_uInt32 n = 0; + while( nCnt ) + { + m_xSuggestions->Set( pSugg[ n ], sal_uInt16( n ) ); + ++n; + --nCnt; + } + } + m_bModifiedSuggestions = false; + } + + m_xScrollSB->vadjustment_set_value( 0 ); + UpdateScrollbar(); // will force edits to be filled new + } + + void HangulHanjaEditDictDialog::SetEditText(SuggestionEdit& rEdit, sal_uInt16 nEntryNum) + { + OUString aStr; + if( m_xSuggestions ) + { + aStr = m_xSuggestions->Get(nEntryNum); + } + + rEdit.set_text(aStr); + } + + void HangulHanjaEditDictDialog::EditModify(const weld::Entry* pEdit, sal_uInt8 _nEntryOffset) + { + m_bModifiedSuggestions = true; + + OUString aTxt( pEdit->get_text() ); + sal_uInt16 nEntryNum = m_nTopPos + _nEntryOffset; + if( aTxt.isEmpty() ) + { + //reset suggestion + if( m_xSuggestions ) + m_xSuggestions->Reset( nEntryNum ); + } + else + { + //set suggestion + if( !m_xSuggestions ) + m_xSuggestions.reset(new SuggestionList); + m_xSuggestions->Set( aTxt, nEntryNum ); + } + + UpdateButtonStates(); + } + + HangulHanjaEditDictDialog::HangulHanjaEditDictDialog(weld::Window* pParent, HHDictList& _rDictList, sal_uInt32 nSelDict) + : GenericDialogController(pParent, "cui/ui/hangulhanjaeditdictdialog.ui", "HangulHanjaEditDictDialog") + , m_aEditHintText ( CuiResId(RID_CUISTR_EDITHINT) ) + , m_rDictList ( _rDictList ) + , m_nCurrentDict ( 0xFFFFFFFF ) + , m_nTopPos ( 0 ) + , m_bModifiedSuggestions ( false ) + , m_bModifiedOriginal ( false ) + , m_xBookLB(m_xBuilder->weld_combo_box("book")) + , m_xOriginalLB(m_xBuilder->weld_combo_box("original")) + , m_xEdit1(new SuggestionEdit(m_xBuilder->weld_entry("edit1"), this)) + , m_xEdit2(new SuggestionEdit(m_xBuilder->weld_entry("edit2"), this)) + , m_xEdit3(new SuggestionEdit(m_xBuilder->weld_entry("edit3"), this)) + , m_xEdit4(new SuggestionEdit(m_xBuilder->weld_entry("edit4"), this)) + , m_xContents(m_xBuilder->weld_widget("box")) + , m_xScrollSB(m_xBuilder->weld_scrolled_window("scrollbar", true)) + , m_xNewPB(m_xBuilder->weld_button("new")) + , m_xDeletePB(m_xBuilder->weld_button("delete")) + { + Size aSize(m_xContents->get_preferred_size()); + m_xScrollSB->set_size_request(-1, aSize.Height()); + + m_xEdit1->init( m_xScrollSB.get(), nullptr, m_xEdit2.get() ); + m_xEdit2->init( m_xScrollSB.get(), m_xEdit1.get(), m_xEdit3.get() ); + m_xEdit3->init( m_xScrollSB.get(), m_xEdit2.get(), m_xEdit4.get() ); + m_xEdit4->init( m_xScrollSB.get(), m_xEdit3.get(), nullptr ); + + m_xOriginalLB->connect_changed( LINK( this, HangulHanjaEditDictDialog, OriginalModifyHdl ) ); + + m_xNewPB->connect_clicked( LINK( this, HangulHanjaEditDictDialog, NewPBPushHdl ) ); + m_xNewPB->set_sensitive( false ); + + m_xDeletePB->connect_clicked( LINK( this, HangulHanjaEditDictDialog, DeletePBPushHdl ) ); + m_xDeletePB->set_sensitive( false ); + + static_assert(MAXNUM_SUGGESTIONS >= 5, "number of suggestions should not under-run the value of 5"); + + // 4 here, because we have 4 edits / page + m_xScrollSB->vadjustment_configure(0, 0, MAXNUM_SUGGESTIONS, 1, 4, 4); + m_xScrollSB->connect_vadjustment_changed(LINK(this, HangulHanjaEditDictDialog, ScrollHdl)); + + m_xEdit1->connect_changed( LINK( this, HangulHanjaEditDictDialog, EditModifyHdl1 ) ); + m_xEdit2->connect_changed( LINK( this, HangulHanjaEditDictDialog, EditModifyHdl2 ) ); + m_xEdit3->connect_changed( LINK( this, HangulHanjaEditDictDialog, EditModifyHdl3 ) ); + m_xEdit4->connect_changed( LINK( this, HangulHanjaEditDictDialog, EditModifyHdl4 ) ); + + m_xBookLB->connect_changed( LINK( this, HangulHanjaEditDictDialog, BookLBSelectHdl ) ); + sal_uInt32 nDictCnt = m_rDictList.size(); + for( sal_uInt32 n = 0 ; n < nDictCnt ; ++n ) + { + Reference< XConversionDictionary > xDic( m_rDictList[n] ); + OUString aName; + if( xDic.is() ) + aName = xDic->getName(); + m_xBookLB->append_text( aName ); + } + m_xBookLB->set_active(nSelDict); + + InitEditDictDialog(nSelDict); + } + + HangulHanjaEditDictDialog::~HangulHanjaEditDictDialog() + { + } + + void HangulHanjaEditDictDialog::UpdateScrollbar() + { + sal_uInt16 nPos = m_xScrollSB->vadjustment_get_value(); + m_nTopPos = nPos; + + SetEditText( *m_xEdit1, nPos++ ); + SetEditText( *m_xEdit2, nPos++ ); + SetEditText( *m_xEdit3, nPos++ ); + SetEditText( *m_xEdit4, nPos ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/hldocntp.cxx b/cui/source/dialogs/hldocntp.cxx new file mode 100644 index 000000000..1c3b850b4 --- /dev/null +++ b/cui/source/dialogs/hldocntp.cxx @@ -0,0 +1,450 @@ +/* -*- 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 <hldocntp.hxx> +#include <osl/file.hxx> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/docfilt.hxx> +#include <svl/stritem.hxx> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/Exception.hpp> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <tools/urlobj.hxx> +#include <unotools/pathoptions.hxx> +#include <unotools/dynamicmenuoptions.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <unotools/ucbhelper.hxx> + +#include <comphelper/processfactory.hxx> +#include <com/sun/star/ui/dialogs/XFolderPicker2.hpp> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> + +#include <cuihyperdlg.hxx> +#include <dialmgr.hxx> +#include <strings.hrc> + +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::ui::dialogs; +using namespace ::com::sun::star::uno; + +using namespace ::com::sun::star; + +/************************************************************************* +|* +|* Data-struct for documenttypes in listbox +|* +|************************************************************************/ + +namespace { + +struct DocumentTypeData +{ + OUString aStrURL; + OUString aStrExt; + DocumentTypeData (OUString aURL, OUString aExt) : aStrURL(std::move(aURL)), aStrExt(std::move(aExt)) + {} +}; + +} + +bool SvxHyperlinkNewDocTp::ImplGetURLObject( const OUString& rPath, std::u16string_view rBase, INetURLObject& aURLObject ) const +{ + bool bIsValidURL = !rPath.isEmpty(); + if ( bIsValidURL ) + { + aURLObject.SetURL( rPath ); + if ( aURLObject.GetProtocol() == INetProtocol::NotValid ) // test if the source is already a valid url + { // if not we have to create a url from a physical file name + bool wasAbs; + INetURLObject base(rBase); + base.setFinalSlash(); + aURLObject = base.smartRel2Abs( + rPath, wasAbs, true, INetURLObject::EncodeMechanism::All, + RTL_TEXTENCODING_UTF8, true); + } + bIsValidURL = aURLObject.GetProtocol() != INetProtocol::NotValid; + if ( bIsValidURL ) + { + OUString aBase( aURLObject.getName( INetURLObject::LAST_SEGMENT, false ) ); + if ( aBase.isEmpty() || ( aBase[0] == '.' ) ) + bIsValidURL = false; + } + if ( bIsValidURL ) + { + sal_Int32 nPos = m_xLbDocTypes->get_selected_index(); + if (nPos != -1) + aURLObject.SetExtension(weld::fromId<DocumentTypeData*>(m_xLbDocTypes->get_id(nPos))->aStrExt); + } + + } + return bIsValidURL; +} + +/************************************************************************* +|* +|* Constructor / Destructor +|* +|************************************************************************/ + +SvxHyperlinkNewDocTp::SvxHyperlinkNewDocTp(weld::Container* pParent, SvxHpLinkDlg* pDlg, const SfxItemSet* pItemSet) + : SvxHyperlinkTabPageBase(pParent, pDlg, "cui/ui/hyperlinknewdocpage.ui", "HyperlinkNewDocPage", pItemSet) + , m_xRbtEditNow(xBuilder->weld_radio_button("editnow")) + , m_xRbtEditLater(xBuilder->weld_radio_button("editlater")) + , m_xCbbPath(new SvxHyperURLBox(xBuilder->weld_combo_box("path"))) + , m_xBtCreate(xBuilder->weld_button("create")) + , m_xLbDocTypes(xBuilder->weld_tree_view("types")) +{ + m_xCbbPath->SetSmartProtocol(INetProtocol::File); + m_xLbDocTypes->set_size_request(-1, m_xLbDocTypes->get_height_rows(5)); + + InitStdControls(); + + SetExchangeSupport (); + + m_xCbbPath->show(); + m_xCbbPath->SetBaseURL(SvtPathOptions().GetWorkPath()); + + // set defaults + m_xRbtEditNow->set_active(true); + + m_xBtCreate->connect_clicked(LINK(this, SvxHyperlinkNewDocTp, ClickNewHdl_Impl)); + + FillDocumentList (); +} + +SvxHyperlinkNewDocTp::~SvxHyperlinkNewDocTp () +{ + if (m_xLbDocTypes) + { + for (sal_Int32 n = 0, nEntryCount = m_xLbDocTypes->n_children(); n < nEntryCount; ++n) + delete weld::fromId<DocumentTypeData*>(m_xLbDocTypes->get_id(n)); + m_xLbDocTypes = nullptr; + } +} + +/************************************************************************* +|* +|* Fill the all dialog-controls except controls in groupbox "more..." +|* +|************************************************************************/ + + +void SvxHyperlinkNewDocTp::FillDlgFields(const OUString& /*rStrURL*/) +{ +} + +void SvxHyperlinkNewDocTp::FillDocumentList() +{ + weld::WaitObject aWaitObj(mpDialog->getDialog()); + + std::vector<SvtDynMenuEntry> aDynamicMenuEntries( SvtDynamicMenuOptions::GetMenu( EDynamicMenuType::NewMenu ) ); + + for ( const SvtDynMenuEntry & rDynamicMenuEntry : aDynamicMenuEntries ) + { + OUString aDocumentUrl = rDynamicMenuEntry.sURL; + OUString aTitle = rDynamicMenuEntry.sTitle; + + //#i96822# business cards, labels and database should not be inserted here + if( aDocumentUrl == "private:factory/swriter?slot=21051" || + aDocumentUrl == "private:factory/swriter?slot=21052" || + aDocumentUrl == "private:factory/sdatabase?Interactive" ) + continue; + + // Insert into listbox + if ( !aDocumentUrl.isEmpty() ) + { + if ( aDocumentUrl == "private:factory/simpress?slot=6686" ) // SJ: #106216# do not start + aDocumentUrl = "private:factory/simpress"; // the AutoPilot for impress + + // insert private-url and default-extension as user-data + std::shared_ptr<const SfxFilter> pFilter = SfxFilter::GetDefaultFilterFromFactory( aDocumentUrl ); + if ( pFilter ) + { + // insert doc-name and image + OUString aTitleName = aTitle.replaceFirst( "~", "" ); + + OUString aStrDefExt(pFilter->GetDefaultExtension()); + DocumentTypeData *pTypeData = new DocumentTypeData(aDocumentUrl, aStrDefExt.copy(2)); + OUString sId(weld::toId(pTypeData)); + m_xLbDocTypes->append(sId, aTitleName); + } + } + } + m_xLbDocTypes->select(0); +} + +/************************************************************************* +|* +|* retrieve and prepare data from dialog-fields +|* +|************************************************************************/ + +void SvxHyperlinkNewDocTp::GetCurentItemData ( OUString& rStrURL, OUString& aStrName, + OUString& aStrIntName, OUString& aStrFrame, + SvxLinkInsertMode& eMode ) +{ + // get data from dialog-controls + rStrURL = m_xCbbPath->get_active_text(); + INetURLObject aURL; + if ( ImplGetURLObject( rStrURL, m_xCbbPath->GetBaseURL(), aURL ) ) + { + rStrURL = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + + GetDataFromCommonFields( aStrName, aStrIntName, aStrFrame, eMode ); +} + +/************************************************************************* +|* +|* static method to create Tabpage +|* +|************************************************************************/ + +std::unique_ptr<IconChoicePage> SvxHyperlinkNewDocTp::Create(weld::Container* pWindow, SvxHpLinkDlg* pDlg, const SfxItemSet* pItemSet) +{ + return std::make_unique<SvxHyperlinkNewDocTp>(pWindow, pDlg, pItemSet); +} + +/************************************************************************* +|* +|* Set initial focus +|* +|************************************************************************/ +void SvxHyperlinkNewDocTp::SetInitFocus() +{ + m_xCbbPath->grab_focus(); +} + +namespace +{ + struct ExecuteInfo + { + bool bRbtEditLater; + bool bRbtEditNow; + INetURLObject aURL; + OUString aStrDocName; + // current document + css::uno::Reference<css::frame::XFrame> xFrame; + SfxDispatcher* pDispatcher; + }; +} + +IMPL_STATIC_LINK(SvxHyperlinkNewDocTp, DispatchDocument, void*, p, void) +{ + std::unique_ptr<ExecuteInfo> xExecuteInfo(static_cast<ExecuteInfo*>(p)); + if (!xExecuteInfo->xFrame.is()) + return; + try + { + //if it throws dispatcher is invalid + css::uno::Reference<css::awt::XTopWindow>(xExecuteInfo->xFrame->getContainerWindow(), css::uno::UNO_QUERY_THROW); + + SfxViewFrame *pViewFrame = nullptr; + + // create items + SfxStringItem aName( SID_FILE_NAME, xExecuteInfo->aStrDocName ); + SfxStringItem aReferer( SID_REFERER, "private:user" ); + SfxStringItem aFrame( SID_TARGETNAME, "_blank"); + + OUString aStrFlags('S'); + if (xExecuteInfo->bRbtEditLater) + { + aStrFlags += "H"; + } + SfxStringItem aFlags (SID_OPTIONS, aStrFlags); + + // open url + const SfxPoolItem* pReturn = xExecuteInfo->pDispatcher->ExecuteList( + SID_OPENDOC, SfxCallMode::SYNCHRON, + { &aName, &aFlags, &aFrame, &aReferer }); + + // save new doc + const SfxViewFrameItem *pItem = dynamic_cast<const SfxViewFrameItem*>( pReturn ); // SJ: pReturn is NULL if the Hyperlink + if ( pItem ) // creation is cancelled #106216# + { + pViewFrame = pItem->GetFrame(); + if (pViewFrame) + { + SfxStringItem aNewName( SID_FILE_NAME, xExecuteInfo->aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + SfxUnoFrameItem aDocFrame( SID_FILLFRAME, pViewFrame->GetFrame().GetFrameInterface() ); + fprintf(stderr, "is there a frame int %p\n", pViewFrame->GetFrame().GetFrameInterface().get() ); + pViewFrame->GetDispatcher()->ExecuteList( + SID_SAVEASDOC, SfxCallMode::SYNCHRON, + { &aNewName }, { &aDocFrame }); + } + } + + if (xExecuteInfo->bRbtEditNow) + { + css::uno::Reference<css::awt::XTopWindow> xWindow(xExecuteInfo->xFrame->getContainerWindow(), css::uno::UNO_QUERY); + if (xWindow.is()) //will be false if the frame was exited while the document was loading (e.g. we waited for warning dialogs) + xWindow->toFront(); + } + + if (pViewFrame && xExecuteInfo->bRbtEditLater) + { + SfxObjectShell* pObjShell = pViewFrame->GetObjectShell(); + pObjShell->DoClose(); + } + } + catch (...) + { + } +} + +/************************************************************************* +|* +|* Any action to do after apply-button is pressed +|* +\************************************************************************/ +void SvxHyperlinkNewDocTp::DoApply() +{ + weld::WaitObject aWait(mpDialog->getDialog()); + + // get data from dialog-controls + OUString aStrNewName = m_xCbbPath->get_active_text(); + + if ( aStrNewName.isEmpty() ) + aStrNewName = maStrInitURL; + + // create a real URL-String + INetURLObject aURL; + if ( !ImplGetURLObject( aStrNewName, m_xCbbPath->GetBaseURL(), aURL ) ) + return; + + // create Document + aStrNewName = aURL.GetURLPath( INetURLObject::DecodeMechanism::NONE ); + bool bCreate = true; + try + { + // check if file exists, warn before we overwrite it + std::unique_ptr<SvStream> pIStm = ::utl::UcbStreamHelper::CreateStream( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), StreamMode::READ ); + + bool bOk = pIStm && ( pIStm->GetError() == ERRCODE_NONE); + + pIStm.reset(); + + if( bOk ) + { + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(mpDialog->getDialog(), + VclMessageType::Warning, VclButtonsType::YesNo, + CuiResId(RID_CUISTR_HYPERDLG_QUERYOVERWRITE))); + bCreate = xWarn->run() == RET_YES; + } + } + catch (const uno::Exception&) + { + } + + if (!bCreate || aStrNewName.isEmpty()) + return; + + ExecuteInfo* pExecuteInfo = new ExecuteInfo; + + pExecuteInfo->bRbtEditLater = m_xRbtEditLater->get_active(); + pExecuteInfo->bRbtEditNow = m_xRbtEditNow->get_active(); + // get private-url + sal_Int32 nPos = m_xLbDocTypes->get_selected_index(); + if (nPos == -1) + nPos = 0; + pExecuteInfo->aURL = aURL; + pExecuteInfo->aStrDocName = weld::fromId<DocumentTypeData*>(m_xLbDocTypes->get_id(nPos))->aStrURL; + + // current document + pExecuteInfo->xFrame = GetDispatcher()->GetFrame()->GetFrame().GetFrameInterface(); + pExecuteInfo->pDispatcher = GetDispatcher(); + + Application::PostUserEvent(LINK(nullptr, SvxHyperlinkNewDocTp, DispatchDocument), pExecuteInfo); +} + +/************************************************************************* +|* +|* Click on imagebutton : new +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHyperlinkNewDocTp, ClickNewHdl_Impl, weld::Button&, void) +{ + DisableClose( true ); + uno::Reference < XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + uno::Reference < XFolderPicker2 > xFolderPicker = sfx2::createFolderPicker(xContext, mpDialog->getDialog()); + + OUString aStrURL; + OUString aTempStrURL( m_xCbbPath->get_active_text() ); + osl::FileBase::getFileURLFromSystemPath( aTempStrURL, aStrURL ); + + OUString aStrPath = aStrURL; + bool bZeroPath = aStrPath.isEmpty(); + bool bHandleFileName = bZeroPath; // when path has length of 0, then the rest should always be handled + // as file name, otherwise we do not yet know + + if( bZeroPath ) + aStrPath = SvtPathOptions().GetWorkPath(); + else if( !::utl::UCBContentHelper::IsFolder( aStrURL ) ) + bHandleFileName = true; + + xFolderPicker->setDisplayDirectory( aStrPath ); + sal_Int16 nResult = xFolderPicker->execute(); + DisableClose( false ); + if( ExecutableDialogResults::OK != nResult ) + return; + + char const sSlash[] = "/"; + + INetURLObject aURL( aStrURL, INetProtocol::File ); + OUString aStrName; + if( bHandleFileName ) + aStrName = bZeroPath? aTempStrURL : aURL.getName(); + + m_xCbbPath->SetBaseURL( xFolderPicker->getDirectory() ); + OUString aStrTmp( xFolderPicker->getDirectory() ); + + if( aStrTmp[ aStrTmp.getLength() - 1 ] != sSlash[0] ) + aStrTmp += sSlash; + + // append old file name + if( bHandleFileName ) + aStrTmp += aStrName; + + INetURLObject aNewURL( aStrTmp ); + + if (!aStrName.isEmpty() && !aNewURL.getExtension().isEmpty() && + m_xLbDocTypes->get_selected_index() != -1) + { + // get private-url + const sal_Int32 nPos = m_xLbDocTypes->get_selected_index(); + aNewURL.setExtension(weld::fromId<DocumentTypeData*>(m_xLbDocTypes->get_id(nPos))->aStrExt); + } + + if( aNewURL.GetProtocol() == INetProtocol::File ) + { + osl::FileBase::getSystemPathFromFileURL(aNewURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aStrTmp); + } + else + { + aStrTmp = aNewURL.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous ); + } + + m_xCbbPath->set_entry_text( aStrTmp ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/hldoctp.cxx b/cui/source/dialogs/hldoctp.cxx new file mode 100644 index 000000000..5da741202 --- /dev/null +++ b/cui/source/dialogs/hldoctp.cxx @@ -0,0 +1,317 @@ +/* -*- 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 <cuihyperdlg.hxx> +#include <osl/file.hxx> +#include <sfx2/filedlghelper.hxx> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> + +#include <hldoctp.hxx> +#include <hlmarkwn_def.hxx> + +char const sHash[] = "#"; + +/************************************************************************* +|* +|* Constructor / Destructor +|* +|************************************************************************/ + +SvxHyperlinkDocTp::SvxHyperlinkDocTp(weld::Container* pParent, SvxHpLinkDlg* pDlg, const SfxItemSet* pItemSet) + : SvxHyperlinkTabPageBase(pParent, pDlg, "cui/ui/hyperlinkdocpage.ui", "HyperlinkDocPage", pItemSet) + , m_xCbbPath(new SvxHyperURLBox(xBuilder->weld_combo_box("path"))) + , m_xBtFileopen(xBuilder->weld_button("fileopen")) + , m_xEdTarget(xBuilder->weld_entry("target")) + , m_xFtFullURL(xBuilder->weld_label("url")) + , m_xBtBrowse(xBuilder->weld_button("browse")) + , m_bMarkWndOpen(false) +{ + m_xCbbPath->SetSmartProtocol(INetProtocol::File); + + InitStdControls(); + + m_xCbbPath->show(); + m_xCbbPath->SetBaseURL(INET_FILE_SCHEME); + + SetExchangeSupport(); + + // set handlers + m_xBtFileopen->connect_clicked( LINK ( this, SvxHyperlinkDocTp, ClickFileopenHdl_Impl ) ); + m_xBtBrowse->connect_clicked( LINK ( this, SvxHyperlinkDocTp, ClickTargetHdl_Impl ) ); + m_xCbbPath->connect_changed( LINK ( this, SvxHyperlinkDocTp, ModifiedPathHdl_Impl ) ); + m_xEdTarget->connect_changed( LINK ( this, SvxHyperlinkDocTp, ModifiedTargetHdl_Impl ) ); + + m_xCbbPath->connect_focus_out( LINK ( this, SvxHyperlinkDocTp, LostFocusPathHdl_Impl ) ); + + maTimer.SetInvokeHandler ( LINK ( this, SvxHyperlinkDocTp, TimeoutHdl_Impl ) ); +} + +SvxHyperlinkDocTp::~SvxHyperlinkDocTp() +{ +} + +/************************************************************************* +|* +|* Fill all dialog-controls except controls in groupbox "more..." +|* +|************************************************************************/ +void SvxHyperlinkDocTp::FillDlgFields(const OUString& rStrURL) +{ + sal_Int32 nPos = rStrURL.indexOf(sHash); + // path + m_xCbbPath->set_entry_text( rStrURL.copy( 0, ( nPos == -1 ? rStrURL.getLength() : nPos ) ) ); + + // set target in document at editfield + OUString aStrMark; + if ( nPos != -1 && nPos < rStrURL.getLength()-1 ) + aStrMark = rStrURL.copy( nPos+1 ); + m_xEdTarget->set_text( aStrMark ); + + ModifiedPathHdl_Impl(*m_xCbbPath->getWidget()); +} + +/************************************************************************* +|* +|* retrieve current url-string +|* +|************************************************************************/ +OUString SvxHyperlinkDocTp::GetCurrentURL () const +{ + // get data from dialog-controls + OUString aStrURL; + OUString aStrPath( m_xCbbPath->get_active_text() ); + OUString aStrMark( m_xEdTarget->get_text() ); + + if ( !aStrPath.isEmpty() ) + { + INetURLObject aURL( aStrPath ); + if ( aURL.GetProtocol() != INetProtocol::NotValid ) // maybe the path is already a valid + aStrURL = aStrPath; // hyperlink, then we can use this path directly + else + { + osl::FileBase::getFileURLFromSystemPath( aStrPath, aStrURL ); + aStrURL = INetURLObject::decode(aStrURL, INetURLObject::DecodeMechanism::ToIUri, RTL_TEXTENCODING_UTF8); + } + + //#105788# always create a URL even if it is not valid + if( aStrURL.isEmpty() ) + aStrURL = aStrPath; + } + + if( !aStrMark.isEmpty() ) + { + aStrURL += sHash + aStrMark; + } + + return aStrURL; +} + +/************************************************************************* +|* +|* retrieve and prepare data from dialog-fields +|* +|************************************************************************/ +void SvxHyperlinkDocTp::GetCurentItemData ( OUString& rStrURL, OUString& aStrName, + OUString& aStrIntName, OUString& aStrFrame, + SvxLinkInsertMode& eMode ) +{ + // get data from standard-fields + rStrURL = GetCurrentURL(); + + if( rStrURL.equalsIgnoreAsciiCase( INET_FILE_SCHEME ) ) + rStrURL.clear(); + + GetDataFromCommonFields( aStrName, aStrIntName, aStrFrame, eMode ); +} + +/************************************************************************* +|* +|* static method to create Tabpage +|* +|************************************************************************/ +std::unique_ptr<IconChoicePage> SvxHyperlinkDocTp::Create(weld::Container* pWindow, SvxHpLinkDlg* pDlg, const SfxItemSet* pItemSet) +{ + return std::make_unique<SvxHyperlinkDocTp>(pWindow, pDlg, pItemSet); +} + +/************************************************************************* +|* +|* Set initial focus +|* +|************************************************************************/ +void SvxHyperlinkDocTp::SetInitFocus() +{ + m_xCbbPath->grab_focus(); +} + +/************************************************************************* +|* +|* Click on imagebutton : fileopen +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHyperlinkDocTp, ClickFileopenHdl_Impl, weld::Button&, void) +{ + DisableClose( true ); + // Open Fileopen-Dialog + sfx2::FileDialogHelper aDlg( + css::ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE, FileDialogFlags::NONE, + mpDialog->getDialog() ); + OUString aOldURL( GetCurrentURL() ); + if( aOldURL.startsWithIgnoreAsciiCase( INET_FILE_SCHEME ) ) + { + OUString aPath; + osl::FileBase::getSystemPathFromFileURL(aOldURL, aPath); + aDlg.SetDisplayFolder( aPath ); + } + + ErrCode nError = aDlg.Execute(); + DisableClose( false ); + + if ( ERRCODE_NONE != nError ) + return; + + OUString aURL( aDlg.GetPath() ); + OUString aPath; + + osl::FileBase::getSystemPathFromFileURL(aURL, aPath); + + m_xCbbPath->SetBaseURL( aURL ); + m_xCbbPath->set_entry_text(aPath); + + if ( aOldURL != GetCurrentURL() ) + ModifiedPathHdl_Impl(*m_xCbbPath->getWidget()); +} + +/************************************************************************* +|* +|* Click on imagebutton : target +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHyperlinkDocTp, ClickTargetHdl_Impl, weld::Button&, void) +{ + ShowMarkWnd(); + + if ( GetPathType ( maStrURL ) == EPathType::ExistsFile || + maStrURL.isEmpty() || + maStrURL.equalsIgnoreAsciiCase( INET_FILE_SCHEME ) || + maStrURL.startsWith( sHash ) ) + { + mxMarkWnd->SetError( LERR_NOERROR ); + + weld::WaitObject aWait(mpDialog->getDialog()); + + if ( maStrURL.equalsIgnoreAsciiCase( INET_FILE_SCHEME ) ) + mxMarkWnd->RefreshTree ( "" ); + else + mxMarkWnd->RefreshTree ( maStrURL ); + } + else + mxMarkWnd->SetError( LERR_DOCNOTOPEN ); +} + +/************************************************************************* +|* +|* Contents of combobox "Path" modified +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHyperlinkDocTp, ModifiedPathHdl_Impl, weld::ComboBox&, void) +{ + maStrURL = GetCurrentURL(); + + maTimer.SetTimeout( 2500 ); + maTimer.Start(); + + m_xFtFullURL->set_label( maStrURL ); +} + +/************************************************************************* +|* +|* If path-field was modify, to browse the new doc after timeout +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHyperlinkDocTp, TimeoutHdl_Impl, Timer *, void) +{ + if ( IsMarkWndVisible() && ( GetPathType( maStrURL )== EPathType::ExistsFile || + maStrURL.isEmpty() || + maStrURL.equalsIgnoreAsciiCase( INET_FILE_SCHEME ) ) ) + { + weld::WaitObject aWait(mpDialog->getDialog()); + + if ( maStrURL.equalsIgnoreAsciiCase( INET_FILE_SCHEME ) ) + mxMarkWnd->RefreshTree ( "" ); + else + mxMarkWnd->RefreshTree ( maStrURL ); + } +} + +/************************************************************************* +|* +|* Contents of editfield "Target" modified +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHyperlinkDocTp, ModifiedTargetHdl_Impl, weld::Entry&, void) +{ + maStrURL = GetCurrentURL(); + + if (IsMarkWndVisible()) + mxMarkWnd->SelectEntry(m_xEdTarget->get_text()); + + m_xFtFullURL->set_label( maStrURL ); +} + +/************************************************************************* +|* +|* editfield "Target" lost focus +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHyperlinkDocTp, LostFocusPathHdl_Impl, weld::Widget&, void) +{ + maStrURL = GetCurrentURL(); + + m_xFtFullURL->set_label( maStrURL ); +} + +/************************************************************************* +|* +|* Get String from Bookmark-Wnd +|* +|************************************************************************/ +void SvxHyperlinkDocTp::SetMarkStr ( const OUString& aStrMark ) +{ + m_xEdTarget->set_text(aStrMark); + + ModifiedTargetHdl_Impl ( *m_xEdTarget ); +} + +/************************************************************************* +|* +|* retrieve kind of pathstr +|* +|************************************************************************/ +SvxHyperlinkDocTp::EPathType SvxHyperlinkDocTp::GetPathType ( std::u16string_view rStrPath ) +{ + INetURLObject aURL( rStrPath, INetProtocol::File ); + + if( aURL.HasError() ) + return EPathType::Invalid; + else + return EPathType::ExistsFile; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/hlinettp.cxx b/cui/source/dialogs/hlinettp.cxx new file mode 100644 index 000000000..3a0f431ac --- /dev/null +++ b/cui/source/dialogs/hlinettp.cxx @@ -0,0 +1,389 @@ +/* -*- 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 <o3tl/string_view.hxx> +#include <unotools/useroptions.hxx> +#include <svl/adrparse.hxx> + +#include <hlinettp.hxx> +#include <hlmarkwn_def.hxx> + +constexpr OUStringLiteral sAnonymous = u"anonymous"; + +/************************************************************************* +|* +|* Constructor / Destructor +|* +|************************************************************************/ +SvxHyperlinkInternetTp::SvxHyperlinkInternetTp(weld::Container* pParent, + SvxHpLinkDlg* pDlg, + const SfxItemSet* pItemSet) + : SvxHyperlinkTabPageBase(pParent, pDlg, "cui/ui/hyperlinkinternetpage.ui", "HyperlinkInternetPage", + pItemSet) + , m_bMarkWndOpen(false) + , m_xRbtLinktypInternet(xBuilder->weld_radio_button("linktyp_internet")) + , m_xRbtLinktypFTP(xBuilder->weld_radio_button("linktyp_ftp")) + , m_xCbbTarget(new SvxHyperURLBox(xBuilder->weld_combo_box("target"))) + , m_xFtTarget(xBuilder->weld_label("target_label")) + , m_xFtLogin(xBuilder->weld_label("login_label")) + , m_xEdLogin(xBuilder->weld_entry("login")) + , m_xFtPassword(xBuilder->weld_label("password_label")) + , m_xEdPassword(xBuilder->weld_entry("password")) + , m_xCbAnonymous(xBuilder->weld_check_button("anonymous")) +{ + // gtk_size_group_set_ignore_hidden, "Measuring the size of hidden widgets + // ... they will report a size of 0 nowadays, and thus, their size will + // not affect the other size group members", which is unfortunate. So here + // before we hide the labels, take the size group width and set it as + // explicit preferred size on a label that won't be hidden + auto nLabelWidth = m_xFtTarget->get_preferred_size().Width(); + m_xFtTarget->set_size_request(nLabelWidth, -1); + + m_xCbbTarget->SetSmartProtocol(INetProtocol::Http); + + InitStdControls(); + + m_xCbbTarget->show(); + + SetExchangeSupport (); + + // set defaults + m_xRbtLinktypInternet->set_active(true); + + // set handlers + Link<weld::Toggleable&, void> aLink( LINK ( this, SvxHyperlinkInternetTp, Click_SmartProtocol_Impl ) ); + m_xRbtLinktypInternet->connect_toggled( aLink ); + m_xRbtLinktypFTP->connect_toggled( aLink ); + m_xCbAnonymous->connect_toggled( LINK ( this, SvxHyperlinkInternetTp, ClickAnonymousHdl_Impl ) ); + m_xEdLogin->connect_changed( LINK ( this, SvxHyperlinkInternetTp, ModifiedLoginHdl_Impl ) ); + m_xCbbTarget->connect_focus_out( LINK ( this, SvxHyperlinkInternetTp, LostFocusTargetHdl_Impl ) ); + m_xCbbTarget->connect_changed( LINK ( this, SvxHyperlinkInternetTp, ModifiedTargetHdl_Impl ) ); + maTimer.SetInvokeHandler ( LINK ( this, SvxHyperlinkInternetTp, TimeoutHdl_Impl ) ); +} + +SvxHyperlinkInternetTp::~SvxHyperlinkInternetTp() +{ +} + +/************************************************************************* +|* +|* Fill the all dialog-controls except controls in groupbox "more..." +|* +|************************************************************************/ +void SvxHyperlinkInternetTp::FillDlgFields(const OUString& rStrURL) +{ + INetURLObject aURL(rStrURL); + OUString aStrScheme(GetSchemeFromURL(rStrURL)); + + // set additional controls for FTP: Username / Password + if (aStrScheme.startsWith(INET_FTP_SCHEME)) + { + if ( aURL.GetUser().toAsciiLowerCase().startsWith( sAnonymous ) ) + setAnonymousFTPUser(); + else + setFTPUser(aURL.GetUser(), aURL.GetPass()); + + //do not show password and user in url + if(!aURL.GetUser().isEmpty() || !aURL.GetPass().isEmpty() ) + aURL.SetUserAndPass(u"", u""); + } + + // set URL-field + // Show the scheme, #72740 + if ( aURL.GetProtocol() != INetProtocol::NotValid ) + m_xCbbTarget->set_entry_text( aURL.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous ) ); + else + m_xCbbTarget->set_entry_text(rStrURL); + + SetScheme(aStrScheme); +} + +void SvxHyperlinkInternetTp::setAnonymousFTPUser() +{ + m_xEdLogin->set_text(sAnonymous); + SvAddressParser aAddress(SvtUserOptions().GetEmail()); + m_xEdPassword->set_text(aAddress.Count() ? aAddress.GetEmailAddress(0) : OUString()); + + m_xFtLogin->set_sensitive(false); + m_xFtPassword->set_sensitive(false); + m_xEdLogin->set_sensitive(false); + m_xEdPassword->set_sensitive(false); + m_xCbAnonymous->set_active(true); +} + +void SvxHyperlinkInternetTp::setFTPUser(const OUString& rUser, const OUString& rPassword) +{ + m_xEdLogin->set_text(rUser); + m_xEdPassword->set_text(rPassword); + + m_xFtLogin->set_sensitive(true); + m_xFtPassword->set_sensitive(true); + m_xEdLogin->set_sensitive(true); + m_xEdPassword->set_sensitive(true); + m_xCbAnonymous->set_active(false); +} + +/************************************************************************* +|* +|* retrieve and prepare data from dialog-fields +|* +|************************************************************************/ + +void SvxHyperlinkInternetTp::GetCurentItemData ( OUString& rStrURL, OUString& aStrName, + OUString& aStrIntName, OUString& aStrFrame, + SvxLinkInsertMode& eMode ) +{ + rStrURL = CreateAbsoluteURL(); + GetDataFromCommonFields( aStrName, aStrIntName, aStrFrame, eMode ); +} + +OUString SvxHyperlinkInternetTp::CreateAbsoluteURL() const +{ + // erase leading and trailing whitespaces + OUString aStrURL(m_xCbbTarget->get_active_text().trim()); + + INetURLObject aURL(aStrURL, GetSmartProtocolFromButtons()); + + // username and password for ftp-url + if( aURL.GetProtocol() == INetProtocol::Ftp && !m_xEdLogin->get_text().isEmpty() ) + aURL.SetUserAndPass ( m_xEdLogin->get_text(), m_xEdPassword->get_text() ); + + if ( aURL.GetProtocol() != INetProtocol::NotValid ) + return aURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); + else //#105788# always create a URL even if it is not valid + return aStrURL; +} + +/************************************************************************* +|* +|* static method to create Tabpage +|* +|************************************************************************/ + +std::unique_ptr<IconChoicePage> SvxHyperlinkInternetTp::Create(weld::Container* pWindow, SvxHpLinkDlg* pDlg, const SfxItemSet* pItemSet) +{ + return std::make_unique<SvxHyperlinkInternetTp>(pWindow, pDlg, pItemSet); +} + +/************************************************************************* +|* +|* Set initial focus +|* +|************************************************************************/ +void SvxHyperlinkInternetTp::SetInitFocus() +{ + m_xCbbTarget->grab_focus(); +} + +/************************************************************************* +|* +|* Contents of editfield "Target" modified +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHyperlinkInternetTp, ModifiedTargetHdl_Impl, weld::ComboBox&, void) +{ + OUString aScheme = GetSchemeFromURL( m_xCbbTarget->get_active_text() ); + if( !aScheme.isEmpty() ) + SetScheme( aScheme ); + + // start timer + maTimer.SetTimeout( 2500 ); + maTimer.Start(); +} + +/************************************************************************* +|* +|* If target-field was modify, to browse the new doc after timeout +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHyperlinkInternetTp, TimeoutHdl_Impl, Timer *, void) +{ + RefreshMarkWindow(); +} + +/************************************************************************* +|* +|* Contents of editfield "Login" modified +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHyperlinkInternetTp, ModifiedLoginHdl_Impl, weld::Entry&, void) +{ + OUString aStrLogin ( m_xEdLogin->get_text() ); + if ( aStrLogin.equalsIgnoreAsciiCase( sAnonymous ) ) + { + m_xCbAnonymous->set_active(true); + ClickAnonymousHdl_Impl(*m_xCbAnonymous); + } +} + +void SvxHyperlinkInternetTp::SetScheme(std::u16string_view rScheme) +{ + //if rScheme is empty or unknown the default behaviour is like it where HTTP + bool bFTP = o3tl::starts_with(rScheme, INET_FTP_SCHEME); + bool bInternet = !bFTP; + + //update protocol button selection: + m_xRbtLinktypFTP->set_active(bFTP); + m_xRbtLinktypInternet->set_active(bInternet); + + //update target: + RemoveImproperProtocol(rScheme); + m_xCbbTarget->SetSmartProtocol( GetSmartProtocolFromButtons() ); + + //show/hide special fields for FTP: + m_xFtLogin->set_visible( bFTP ); + m_xFtPassword->set_visible( bFTP ); + m_xEdLogin->set_visible( bFTP ); + m_xEdPassword->set_visible( bFTP ); + m_xCbAnonymous->set_visible( bFTP ); + + //update 'link target in document'-window and opening-button + if (o3tl::starts_with(rScheme, INET_HTTP_SCHEME) || rScheme.empty()) + { + if ( m_bMarkWndOpen ) + ShowMarkWnd (); + } + else + { + //disable for https and ftp + if ( m_bMarkWndOpen ) + HideMarkWnd (); + } +} + +/************************************************************************* +|* +|* Remove protocol if it does not fit to the current button selection +|* +|************************************************************************/ + +void SvxHyperlinkInternetTp::RemoveImproperProtocol(std::u16string_view aProperScheme) +{ + OUString aStrURL ( m_xCbbTarget->get_active_text() ); + if ( !aStrURL.isEmpty() ) + { + OUString aStrScheme(GetSchemeFromURL(aStrURL)); + if ( !aStrScheme.isEmpty() && aStrScheme != aProperScheme ) + { + aStrURL = aStrURL.copy( aStrScheme.getLength() ); + m_xCbbTarget->set_entry_text( aStrURL ); + } + } +} + +OUString SvxHyperlinkInternetTp::GetSchemeFromButtons() const +{ + if( m_xRbtLinktypFTP->get_active() ) + return INET_FTP_SCHEME; + return INET_HTTP_SCHEME; +} + +INetProtocol SvxHyperlinkInternetTp::GetSmartProtocolFromButtons() const +{ + if( m_xRbtLinktypFTP->get_active() ) + { + return INetProtocol::Ftp; + } + return INetProtocol::Http; +} + +/************************************************************************* +|* +|* Click on Radiobutton : Internet or FTP +|* +|************************************************************************/ +IMPL_LINK(SvxHyperlinkInternetTp, Click_SmartProtocol_Impl, weld::Toggleable&, rButton, void) +{ + if (!rButton.get_active()) + return; + OUString aScheme = GetSchemeFromButtons(); + SetScheme(aScheme); +} + +/************************************************************************* +|* +|* Click on Checkbox : Anonymous user +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHyperlinkInternetTp, ClickAnonymousHdl_Impl, weld::Toggleable&, void) +{ + // disable login-editfields if checked + if ( m_xCbAnonymous->get_active() ) + { + if ( m_xEdLogin->get_text().toAsciiLowerCase().startsWith( sAnonymous ) ) + { + maStrOldUser.clear(); + maStrOldPassword.clear(); + } + else + { + maStrOldUser = m_xEdLogin->get_text(); + maStrOldPassword = m_xEdPassword->get_text(); + } + + setAnonymousFTPUser(); + } + else + setFTPUser(maStrOldUser, maStrOldPassword); +} + +/************************************************************************* +|* +|* Combobox Target lost the focus +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHyperlinkInternetTp, LostFocusTargetHdl_Impl, weld::Widget&, void) +{ + RefreshMarkWindow(); +} + +void SvxHyperlinkInternetTp::RefreshMarkWindow() +{ + if (m_xRbtLinktypInternet->get_active() && IsMarkWndVisible()) + { + weld::WaitObject aWait(mpDialog->getDialog()); + OUString aStrURL( CreateAbsoluteURL() ); + if ( !aStrURL.isEmpty() ) + mxMarkWnd->RefreshTree ( aStrURL ); + else + mxMarkWnd->SetError( LERR_DOCNOTOPEN ); + } +} + +/************************************************************************* +|* +|* Get String from Bookmark-Wnd +|* +|************************************************************************/ +void SvxHyperlinkInternetTp::SetMarkStr ( const OUString& aStrMark ) +{ + OUString aStrURL(m_xCbbTarget->get_active_text()); + + const sal_Unicode sUHash = '#'; + sal_Int32 nPos = aStrURL.lastIndexOf( sUHash ); + + if( nPos != -1 ) + aStrURL = aStrURL.copy(0, nPos); + + aStrURL += OUStringChar(sUHash) + aStrMark; + + m_xCbbTarget->set_entry_text(aStrURL); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/hlmailtp.cxx b/cui/source/dialogs/hlmailtp.cxx new file mode 100644 index 000000000..f02a8f765 --- /dev/null +++ b/cui/source/dialogs/hlmailtp.cxx @@ -0,0 +1,222 @@ +/* -*- 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 <sfx2/request.hxx> + +#include <sfx2/viewfrm.hxx> +#include <unotools/moduleoptions.hxx> + +#include <hlmailtp.hxx> + +#include <comphelper/lok.hxx> + +using namespace ::com::sun::star; + +/************************************************************************* +|* +|* Constructor / Destructor +|* +|************************************************************************/ +SvxHyperlinkMailTp::SvxHyperlinkMailTp(weld::Container* pParent, SvxHpLinkDlg* pDlg, const SfxItemSet* pItemSet) + : SvxHyperlinkTabPageBase(pParent, pDlg, "cui/ui/hyperlinkmailpage.ui", "HyperlinkMailPage", pItemSet) + , m_xCbbReceiver(new SvxHyperURLBox(xBuilder->weld_combo_box("receiver"))) + , m_xBtAdrBook(xBuilder->weld_button("addressbook")) + , m_xFtSubject(xBuilder->weld_label("subject_label")) + , m_xEdSubject(xBuilder->weld_entry("subject")) +{ + m_xCbbReceiver->SetSmartProtocol(INetProtocol::Mailto); + + InitStdControls(); + + m_xCbbReceiver->show(); + + SetExchangeSupport (); + + // set handlers + m_xBtAdrBook->connect_clicked( LINK ( this, SvxHyperlinkMailTp, ClickAdrBookHdl_Impl ) ); + m_xCbbReceiver->connect_changed( LINK ( this, SvxHyperlinkMailTp, ModifiedReceiverHdl_Impl) ); + + if ( !SvtModuleOptions().IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) || + comphelper::LibreOfficeKit::isActive() ) + m_xBtAdrBook->hide(); +} + +SvxHyperlinkMailTp::~SvxHyperlinkMailTp() +{ +} + +/************************************************************************* +|* +|* Fill the all dialog-controls except controls in groupbox "more..." +|* +|************************************************************************/ + +void SvxHyperlinkMailTp::FillDlgFields(const OUString& rStrURL) +{ + OUString aStrScheme = GetSchemeFromURL(rStrURL); + + // set URL-field and additional controls + OUString aStrURLc (rStrURL); + // set additional controls for EMail: + if ( aStrScheme.startsWith( INET_MAILTO_SCHEME ) ) + { + // Find mail-subject + OUString aStrSubject, aStrTmp( aStrURLc ); + + sal_Int32 nPos = aStrTmp.toAsciiLowerCase().indexOf( "subject" ); + + if ( nPos != -1 ) + nPos = aStrTmp.indexOf( '=', nPos ); + + if ( nPos != -1 ) + aStrSubject = aStrURLc.copy( nPos+1 ); + + nPos = aStrURLc.indexOf( '?' ); + + if ( nPos != -1 ) + aStrURLc = aStrURLc.copy( 0, nPos ); + + m_xEdSubject->set_text( aStrSubject ); + } + else + { + m_xEdSubject->set_text(""); + } + + m_xCbbReceiver->set_entry_text(aStrURLc); + + SetScheme( aStrScheme ); +} + +/************************************************************************* +|* +|* retrieve and prepare data from dialog-fields +|* +|************************************************************************/ +void SvxHyperlinkMailTp::GetCurentItemData ( OUString& rStrURL, OUString& aStrName, + OUString& aStrIntName, OUString& aStrFrame, + SvxLinkInsertMode& eMode ) +{ + rStrURL = CreateAbsoluteURL(); + GetDataFromCommonFields( aStrName, aStrIntName, aStrFrame, eMode ); +} + +OUString SvxHyperlinkMailTp::CreateAbsoluteURL() const +{ + OUString aStrURL = m_xCbbReceiver->get_active_text(); + INetURLObject aURL(aStrURL, INetProtocol::Mailto); + + // subject for EMail-url + if( aURL.GetProtocol() == INetProtocol::Mailto ) + { + if (!m_xEdSubject->get_text().isEmpty()) + { + OUString aQuery = "subject=" + m_xEdSubject->get_text(); + aURL.SetParam(aQuery); + } + } + + if ( aURL.GetProtocol() != INetProtocol::NotValid ) + return aURL.GetMainURL( INetURLObject::DecodeMechanism::WithCharset ); + else //#105788# always create a URL even if it is not valid + return aStrURL; +} + +/************************************************************************* +|* +|* static method to create Tabpage +|* +|************************************************************************/ + +std::unique_ptr<IconChoicePage> SvxHyperlinkMailTp::Create(weld::Container* pWindow, SvxHpLinkDlg* pDlg, const SfxItemSet* pItemSet) +{ + return std::make_unique<SvxHyperlinkMailTp>(pWindow, pDlg, pItemSet); +} + +/************************************************************************* +|* +|* Set initial focus +|* +|************************************************************************/ +void SvxHyperlinkMailTp::SetInitFocus() +{ + m_xCbbReceiver->grab_focus(); +} + +/************************************************************************* +|************************************************************************/ +void SvxHyperlinkMailTp::SetScheme(std::u16string_view rScheme) +{ + //update target: + RemoveImproperProtocol(rScheme); + m_xCbbReceiver->SetSmartProtocol( INetProtocol::Mailto ); + + //show/hide special fields for MAIL: + m_xBtAdrBook->set_sensitive(true); + m_xEdSubject->set_sensitive(true); +} + +/************************************************************************* +|* +|* Remove protocol if it does not fit to the current button selection +|* +|************************************************************************/ +void SvxHyperlinkMailTp::RemoveImproperProtocol(std::u16string_view aProperScheme) +{ + OUString aStrURL(m_xCbbReceiver->get_active_text()); + if ( !aStrURL.isEmpty() ) + { + OUString aStrScheme = GetSchemeFromURL( aStrURL ); + if ( !aStrScheme.isEmpty() && aStrScheme != aProperScheme ) + { + aStrURL = aStrURL.copy( aStrScheme.getLength() ); + m_xCbbReceiver->set_entry_text(aStrURL); + } + } +} + +/************************************************************************* +|* +|* Contents of editfield "receiver" modified +|* +|************************************************************************/ +IMPL_LINK_NOARG(SvxHyperlinkMailTp, ModifiedReceiverHdl_Impl, weld::ComboBox&, void) +{ + OUString aScheme = GetSchemeFromURL( m_xCbbReceiver->get_active_text() ); + if(!aScheme.isEmpty()) + SetScheme( aScheme ); +} + +/************************************************************************* +|* +|* Click on imagebutton : addressbook +|* +|************************************************************************/ +IMPL_STATIC_LINK_NOARG(SvxHyperlinkMailTp, ClickAdrBookHdl_Impl, weld::Button&, void) +{ + SfxViewFrame* pViewFrame = SfxViewFrame::Current(); + if( pViewFrame ) + { + SfxItemPool &rPool = pViewFrame->GetPool(); + SfxRequest aReq(SID_VIEW_DATA_SOURCE_BROWSER, SfxCallMode::SLOT, rPool); + pViewFrame->ExecuteSlot( aReq, true ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/hlmarkwn.cxx b/cui/source/dialogs/hlmarkwn.cxx new file mode 100644 index 000000000..ff7730db5 --- /dev/null +++ b/cui/source/dialogs/hlmarkwn.cxx @@ -0,0 +1,520 @@ +/* -*- 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 <dialmgr.hxx> +#include <o3tl/any.hxx> +#include <comphelper/propertyvalue.hxx> +#include <unotools/viewoptions.hxx> +#include <vcl/graph.hxx> + +// UNO-Stuff +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/document/XLinkTargetSupplier.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/io/IOException.hpp> + +#include <toolkit/helper/vclunohelper.hxx> + +#include <strings.hrc> +#include <hlmarkwn.hxx> +#include <hltpbase.hxx> +#include <hlmarkwn_def.hxx> + +#include <stack> + +using namespace ::com::sun::star; + +namespace { + +// Userdata-struct for tree-entries +struct TargetData +{ + OUString aUStrLinkname; + bool bIsTarget; + + TargetData (const OUString& aUStrLName, bool bTarget) + : bIsTarget(bTarget) + { + if (bIsTarget) + aUStrLinkname = aUStrLName; + } +}; + +} + +//*** Window-Class *** +// Constructor / Destructor +SvxHlinkDlgMarkWnd::SvxHlinkDlgMarkWnd(weld::Window* pParentDialog, SvxHyperlinkTabPageBase *pParentPage) + : GenericDialogController(pParentDialog, "cui/ui/hyperlinkmarkdialog.ui", "HyperlinkMark") + , mpParent(pParentPage) + , mnError(LERR_NOERROR) + , mxBtApply(m_xBuilder->weld_button("ok")) + , mxBtClose(m_xBuilder->weld_button("close")) + , mxLbTree(m_xBuilder->weld_tree_view("TreeListBox")) + , mxError(m_xBuilder->weld_label("error")) +{ + mxLbTree->set_size_request(mxLbTree->get_approximate_digit_width() * 25, + mxLbTree->get_height_rows(12)); + mxBtApply->connect_clicked( LINK ( this, SvxHlinkDlgMarkWnd, ClickApplyHdl_Impl ) ); + mxBtClose->connect_clicked( LINK ( this, SvxHlinkDlgMarkWnd, ClickCloseHdl_Impl ) ); + mxLbTree->connect_row_activated( LINK ( this, SvxHlinkDlgMarkWnd, DoubleClickApplyHdl_Impl ) ); +} + +SvxHlinkDlgMarkWnd::~SvxHlinkDlgMarkWnd() +{ + ClearTree(); +} + +void SvxHlinkDlgMarkWnd::ErrorChanged() +{ + if (mnError == LERR_NOENTRIES) + { + OUString aStrMessage = CuiResId( RID_CUISTR_HYPDLG_ERR_LERR_NOENTRIES ); + mxError->set_label(aStrMessage); + mxError->show(); + mxLbTree->hide(); + } + else if (mnError == LERR_DOCNOTOPEN) + { + OUString aStrMessage = CuiResId( RID_CUISTR_HYPDLG_ERR_LERR_DOCNOTOPEN ); + mxError->set_label(aStrMessage); + mxError->show(); + mxLbTree->hide(); + } + else + { + mxLbTree->show(); + mxError->hide(); + } +} + +// Set an errorstatus +sal_uInt16 SvxHlinkDlgMarkWnd::SetError( sal_uInt16 nError) +{ + sal_uInt16 nOldError = mnError; + mnError = nError; + + if( mnError != LERR_NOERROR ) + ClearTree(); + + ErrorChanged(); + + return nOldError; +} + +// Move window +void SvxHlinkDlgMarkWnd::MoveTo(const Point& rNewPos) +{ + m_xDialog->window_move(rNewPos.X(), rNewPos.Y()); +} + +namespace +{ + void SelectPath(weld::TreeIter* pEntry, weld::TreeView& rLbTree, + std::deque<OUString> &rLastSelectedPath) + { + OUString sTitle(rLastSelectedPath.front()); + rLastSelectedPath.pop_front(); + if (sTitle.isEmpty()) + return; + while (pEntry) + { + if (sTitle == rLbTree.get_text(*pEntry)) + { + rLbTree.select(*pEntry); + rLbTree.scroll_to_row(*pEntry); + if (!rLastSelectedPath.empty()) + { + rLbTree.expand_row(*pEntry); + if (!rLbTree.iter_children(*pEntry)) + pEntry = nullptr; + SelectPath(pEntry, rLbTree, rLastSelectedPath); + } + break; + } + if (!rLbTree.iter_next_sibling(*pEntry)) + pEntry = nullptr; + } + } +} + +constexpr OUStringLiteral TG_SETTING_MANAGER = u"TargetInDocument"; +constexpr OUStringLiteral TG_SETTING_LASTMARK = u"LastSelectedMark"; +constexpr OUStringLiteral TG_SETTING_LASTPATH = u"LastSelectedPath"; + +void SvxHlinkDlgMarkWnd::RestoreLastSelection() +{ + bool bSelectedEntry = false; + + OUString sLastSelectedMark; + std::deque<OUString> aLastSelectedPath; + SvtViewOptions aViewSettings( EViewType::Dialog, TG_SETTING_MANAGER ); + if (aViewSettings.Exists()) + { + //Maybe we might want to have some sort of mru list and keep a mapping + //per document, rather than the current reuse of "the last thing + //selected, regardless of the document" + aViewSettings.GetUserItem(TG_SETTING_LASTMARK) >>= sLastSelectedMark; + uno::Sequence<OUString> aTmp; + aViewSettings.GetUserItem(TG_SETTING_LASTPATH) >>= aTmp; + aLastSelectedPath = comphelper::sequenceToContainer< std::deque<OUString> >(aTmp); + } + //fallback to previous entry selected the last time we executed this dialog. + //First see if the exact mark exists and re-use that + if (!sLastSelectedMark.isEmpty()) + bSelectedEntry = SelectEntry(sLastSelectedMark); + //Otherwise just select the closest path available + //now to what was available at dialog close time + if (!bSelectedEntry && !aLastSelectedPath.empty()) + { + std::deque<OUString> aTmpSelectedPath(aLastSelectedPath); + std::unique_ptr<weld::TreeIter> xEntry(mxLbTree->make_iterator()); + if (!mxLbTree->get_iter_first(*xEntry)) + xEntry.reset(); + SelectPath(xEntry.get(), *mxLbTree, aTmpSelectedPath); + } +} + +// Interface to refresh tree +void SvxHlinkDlgMarkWnd::RefreshTree (const OUString& aStrURL) +{ + OUString aUStrURL; + + weld::WaitObject aWait(m_xDialog.get()); + + ClearTree(); + + sal_Int32 nPos = aStrURL.indexOf('#'); + + if (nPos != 0) + aUStrURL = aStrURL; + + if (!RefreshFromDoc(aUStrURL)) + ErrorChanged(); + + bool bSelectedEntry = false; + + if ( nPos != -1 ) + { + OUString aStrMark = aStrURL.copy(nPos+1); + bSelectedEntry = SelectEntry(aStrMark); + } + + if (!bSelectedEntry) + RestoreLastSelection(); +} + +// get links from document +bool SvxHlinkDlgMarkWnd::RefreshFromDoc(const OUString& aURL) +{ + mnError = LERR_NOERROR; + + uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create( ::comphelper::getProcessComponentContext() ); + uno::Reference< lang::XComponent > xComp; + + if( !aURL.isEmpty() ) + { + // load from url + if( xDesktop.is() ) + { + try + { + uno::Sequence< beans::PropertyValue > aArg { comphelper::makePropertyValue("Hidden", true) }; + xComp = xDesktop->loadComponentFromURL( aURL, "_blank", 0, aArg ); + } + catch( const io::IOException& ) + { + + } + catch( const lang::IllegalArgumentException& ) + { + + } + } + } + else + { + // the component with user focus ( current document ) + xComp = xDesktop->getCurrentComponent(); + } + + if( xComp.is() ) + { + uno::Reference< document::XLinkTargetSupplier > xLTS( xComp, uno::UNO_QUERY ); + + if( xLTS.is() ) + { + if( FillTree( xLTS->getLinks() ) == 0 ) + mnError = LERR_NOENTRIES; + } + else + mnError = LERR_DOCNOTOPEN; + + if ( !aURL.isEmpty() ) + xComp->dispose(); + } + else + { + if( !aURL.isEmpty() ) + mnError=LERR_DOCNOTOPEN; + } + return (mnError==0); +} + +// Fill Tree-Control +int SvxHlinkDlgMarkWnd::FillTree( const uno::Reference< container::XNameAccess >& xLinks, const weld::TreeIter* pParentEntry ) +{ + // used to create the Headings outline parent children tree view relation + std::stack<std::pair<std::unique_ptr<weld::TreeIter>, const sal_Int32>> aHeadingsParentEntryStack; + + int nEntries=0; + const uno::Sequence< OUString > aNames( xLinks->getElementNames() ); + const sal_Int32 nLinks = aNames.getLength(); + const OUString* pNames = aNames.getConstArray(); + + static const OUStringLiteral aProp_LinkDisplayName( u"LinkDisplayName" ); + static const OUStringLiteral aProp_LinkTarget( u"com.sun.star.document.LinkTarget" ); + static const OUStringLiteral aProp_LinkDisplayBitmap( u"LinkDisplayBitmap" ); + for( sal_Int32 i = 0; i < nLinks; i++ ) + { + uno::Any aAny; + OUString aLink( *pNames++ ); + + bool bError = false; + try + { + aAny = xLinks->getByName( aLink ); + } + catch(const uno::Exception&) + { + // if the name of the target was invalid (like empty headings) + // no object can be provided + bError = true; + } + if(bError) + continue; + + uno::Reference< beans::XPropertySet > xTarget; + + if( aAny >>= xTarget ) + { + try + { + // get name to display + aAny = xTarget->getPropertyValue( aProp_LinkDisplayName ); + OUString aDisplayName; + aAny >>= aDisplayName; + OUString aStrDisplayname ( aDisplayName ); + + // is it a target ? + uno::Reference< lang::XServiceInfo > xSI( xTarget, uno::UNO_QUERY ); + bool bIsTarget = xSI->supportsService( aProp_LinkTarget ); + + // create userdata + TargetData *pData = new TargetData ( aLink, bIsTarget ); + OUString sId(weld::toId(pData)); + + std::unique_ptr<weld::TreeIter> xEntry(mxLbTree->make_iterator()); + if (pParentEntry) + { + OUString sContentType = mxLbTree->get_text(*pParentEntry); + if (sContentType == "Headings") + { + if (aHeadingsParentEntryStack.empty()) + aHeadingsParentEntryStack.push( + std::pair(mxLbTree->make_iterator(pParentEntry), -1)); + + // get the headings name to display + aAny = xTarget->getPropertyValue("ActualOutlineName"); + OUString sActualOutlineName; + aAny >>= sActualOutlineName; + + // get the headings outline level + aAny = xTarget->getPropertyValue("OutlineLevel"); + sal_Int32 nOutlineLevel = *o3tl::doAccess<sal_Int32>(aAny); + + // pop until the top of stack entry has an outline level less than + // the to be inserted heading outline level + while (nOutlineLevel <= aHeadingsParentEntryStack.top().second) + aHeadingsParentEntryStack.pop(); + + mxLbTree->insert(aHeadingsParentEntryStack.top().first.get(), -1, + &sActualOutlineName, &sId, nullptr, nullptr, false, + xEntry.get()); + + // push if the inserted entry is a child + if (nOutlineLevel > aHeadingsParentEntryStack.top().second) + aHeadingsParentEntryStack.push( + std::pair(mxLbTree->make_iterator(xEntry.get()), nOutlineLevel)); + } + else + { + mxLbTree->insert(pParentEntry, -1, &aStrDisplayname, &sId, nullptr, + nullptr, false, xEntry.get()); + } + } + else + { + mxLbTree->insert(pParentEntry, -1, &aStrDisplayname, &sId, nullptr, nullptr, + false, xEntry.get()); + } + + try + { + // get bitmap for the tree-entry + uno::Reference< awt::XBitmap > + aXBitmap( xTarget->getPropertyValue( aProp_LinkDisplayBitmap ), uno::UNO_QUERY ); + if (aXBitmap.is()) + { + Graphic aBmp(Graphic(VCLUnoHelper::GetBitmap(aXBitmap))); + // insert Displayname into treelist with bitmaps + mxLbTree->set_image(*xEntry, aBmp.GetXGraphic(), -1); + } + } + catch(const css::uno::Exception&) + { + } + + nEntries++; + + uno::Reference< document::XLinkTargetSupplier > xLTS( xTarget, uno::UNO_QUERY ); + if( xLTS.is() ) + nEntries += FillTree( xLTS->getLinks(), xEntry.get() ); + } + catch(const css::uno::Exception&) + { + } + } + } + + return nEntries; +} + +// Clear Tree +void SvxHlinkDlgMarkWnd::ClearTree() +{ + std::unique_ptr<weld::TreeIter> xEntry = mxLbTree->make_iterator(); + bool bEntry = mxLbTree->get_iter_first(*xEntry); + + while (bEntry) + { + TargetData* pUserData = weld::fromId<TargetData*>(mxLbTree->get_id(*xEntry)); + delete pUserData; + + bEntry = mxLbTree->iter_next(*xEntry); + } + + mxLbTree->clear(); +} + +// Find Entry for String +std::unique_ptr<weld::TreeIter> SvxHlinkDlgMarkWnd::FindEntry (std::u16string_view aStrName) +{ + bool bFound=false; + std::unique_ptr<weld::TreeIter> xEntry = mxLbTree->make_iterator(); + bool bEntry = mxLbTree->get_iter_first(*xEntry); + + while (bEntry && !bFound) + { + TargetData* pUserData = weld::fromId<TargetData*>(mxLbTree->get_id(*xEntry)); + if (aStrName == pUserData->aUStrLinkname) + bFound = true; + else + bEntry = mxLbTree->iter_next(*xEntry); + } + + if (!bFound) + xEntry.reset(); + + return xEntry; +} + +// Select Entry +bool SvxHlinkDlgMarkWnd::SelectEntry(std::u16string_view aStrMark) +{ + std::unique_ptr<weld::TreeIter> xEntry = FindEntry(aStrMark); + if (!xEntry) + return false; + mxLbTree->select(*xEntry); + mxLbTree->scroll_to_row(*xEntry); + return true; +} + +// Click on Apply-Button / Double-click on item in tree +IMPL_LINK_NOARG(SvxHlinkDlgMarkWnd, DoubleClickApplyHdl_Impl, weld::TreeView&, bool) +{ + ClickApplyHdl_Impl(*mxBtApply); + return true; +} + +IMPL_LINK_NOARG(SvxHlinkDlgMarkWnd, ClickApplyHdl_Impl, weld::Button&, void) +{ + std::unique_ptr<weld::TreeIter> xEntry(mxLbTree->make_iterator()); + bool bEntry = mxLbTree->get_cursor(xEntry.get()); + if (bEntry) + { + TargetData* pData = weld::fromId<TargetData*>(mxLbTree->get_id(*xEntry)); + if (pData->bIsTarget) + { + mpParent->SetMarkStr(pData->aUStrLinkname); + } + } +} + +// Click on Close-Button +IMPL_LINK_NOARG(SvxHlinkDlgMarkWnd, ClickCloseHdl_Impl, weld::Button&, void) +{ + std::unique_ptr<weld::TreeIter> xEntry(mxLbTree->make_iterator()); + bool bEntry = mxLbTree->get_cursor(xEntry.get()); + if (bEntry) + { + TargetData* pUserData = weld::fromId<TargetData*>(mxLbTree->get_id(*xEntry)); + OUString sLastSelectedMark = pUserData->aUStrLinkname; + + std::deque<OUString> aLastSelectedPath; + //If the bottommost entry is expanded but nothing + //underneath it is selected leave a dummy entry + if (mxLbTree->get_row_expanded(*xEntry)) + aLastSelectedPath.push_front(OUString()); + while (bEntry) + { + aLastSelectedPath.push_front(mxLbTree->get_text(*xEntry)); + bEntry = mxLbTree->iter_parent(*xEntry); + } + + uno::Sequence< beans::NamedValue > aSettings + { + { TG_SETTING_LASTMARK, css::uno::Any(sLastSelectedMark) }, + { TG_SETTING_LASTPATH, css::uno::Any(comphelper::containerToSequence(aLastSelectedPath)) } + }; + + // write + SvtViewOptions aViewSettings( EViewType::Dialog, TG_SETTING_MANAGER ); + aViewSettings.SetUserData( aSettings ); + } + + m_xDialog->response(RET_CANCEL); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/hltpbase.cxx b/cui/source/dialogs/hltpbase.cxx new file mode 100644 index 000000000..77d335f10 --- /dev/null +++ b/cui/source/dialogs/hltpbase.cxx @@ -0,0 +1,537 @@ +/* -*- 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 <memory> +#include <sal/config.h> + +#include <comphelper/lok.hxx> +#include <osl/file.hxx> +#include <sfx2/app.hxx> +#include <sfx2/event.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/viewfrm.hxx> +#include <sot/formats.hxx> +#include <sfx2/sfxsids.hrc> +#include <svl/macitem.hxx> +#include <ucbhelper/content.hxx> +#include <cuihyperdlg.hxx> +#include <hltpbase.hxx> +#include <macroass.hxx> +#include <svx/svxdlg.hxx> +#include <strings.hrc> +#include <dialmgr.hxx> +#include <bitmaps.hlst> + +using namespace ::ucbhelper; + +namespace { + +OUString CreateUiNameFromURL( const OUString& aStrURL ) +{ + OUString aStrUiURL; + INetURLObject aURLObj( aStrURL ); + + switch(aURLObj.GetProtocol()) + { + case INetProtocol::File: + osl::FileBase::getSystemPathFromFileURL(aURLObj.GetMainURL(INetURLObject::DecodeMechanism::NONE), aStrUiURL); + break; + case INetProtocol::Ftp : + { + //remove password from name + INetURLObject aTmpURL(aURLObj); + aTmpURL.SetPass(u""); + aStrUiURL = aTmpURL.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous ); + } + break; + default : + { + aStrUiURL = aURLObj.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous); + } + } + if(aStrUiURL.isEmpty()) + return aStrURL; + return aStrUiURL; +} + +} + +// ComboBox-Control for URL's with History and Autocompletion +SvxHyperURLBox::SvxHyperURLBox(std::unique_ptr<weld::ComboBox> xControl) + : SvtURLBox(std::move(xControl)) + , DropTargetHelper(getWidget()->get_drop_target()) +{ + SetSmartProtocol(INetProtocol::Http); +} + +sal_Int8 SvxHyperURLBox::AcceptDrop( const AcceptDropEvent& /* rEvt */ ) +{ + return IsDropFormatSupported( SotClipboardFormatId::STRING ) ? DND_ACTION_COPY : DND_ACTION_NONE; +} + +sal_Int8 SvxHyperURLBox::ExecuteDrop( const ExecuteDropEvent& rEvt ) +{ + TransferableDataHelper aDataHelper( rEvt.maDropEvent.Transferable ); + OUString aString; + sal_Int8 nRet = DND_ACTION_NONE; + + if( aDataHelper.GetString( SotClipboardFormatId::STRING, aString ) ) + { + set_entry_text(aString); + nRet = DND_ACTION_COPY; + } + + return nRet; +} + +//# Hyperlink-Dialog: Tabpages-Baseclass # + +SvxHyperlinkTabPageBase::SvxHyperlinkTabPageBase(weld::Container* pParent, + SvxHpLinkDlg* pDlg, + const OUString& rUIXMLDescription, + const OString& rID, + const SfxItemSet* pItemSet) + : IconChoicePage(pParent, rUIXMLDescription, rID, pItemSet) + , mxCbbFrame(xBuilder->weld_combo_box("frame")) + , mxLbForm(xBuilder->weld_combo_box("form")) + , mxEdIndication(xBuilder->weld_entry("indication")) + , mxEdText(xBuilder->weld_entry("name")) + , mxBtScript(xBuilder->weld_button("script")) + , mxFormLabel(xBuilder->weld_label("form_label")) + , mxFrameLabel(xBuilder->weld_label("frame_label")) + , mbIsCloseDisabled( false ) + , mpDialog( pDlg ) + , mbStdControlsInit( false ) + , maTimer("cui SvxHyperlinkTabPageBase maTimer") +{ + // create bookmark-window +} + +SvxHyperlinkTabPageBase::~SvxHyperlinkTabPageBase() +{ + maTimer.Stop(); + + HideMarkWnd(); +} + +bool SvxHyperlinkTabPageBase::QueryClose() +{ + return !mbIsCloseDisabled; +} + +void SvxHyperlinkTabPageBase::InitStdControls () +{ + if ( !mbStdControlsInit ) + { + SfxDispatcher* pDispatch = GetDispatcher(); + SfxViewFrame* pViewFrame = pDispatch ? pDispatch->GetFrame() : nullptr; + SfxFrame* pFrame = pViewFrame ? &pViewFrame->GetFrame() : nullptr; + if ( pFrame ) + { + TargetList aList; + SfxFrame::GetDefaultTargetList(aList); + if( !aList.empty() ) + { + size_t nCount = aList.size(); + size_t i; + for ( i = 0; i < nCount; i++ ) + { + mxCbbFrame->append_text( aList.at( i ) ); + } + } + } + + mxBtScript->set_from_icon_name(RID_SVXBMP_SCRIPT); + + mxBtScript->connect_clicked ( LINK ( this, SvxHyperlinkTabPageBase, ClickScriptHdl_Impl ) ); + } + + mbStdControlsInit = true; +} + +// Move Extra-Window +void SvxHyperlinkTabPageBase::MoveToExtraWnd( Point aNewPos ) +{ + mxMarkWnd->MoveTo(aNewPos); +} + +// Show Extra-Window +void SvxHyperlinkTabPageBase::ShowMarkWnd() +{ + if (mxMarkWnd) + { + mxMarkWnd->getDialog()->present(); + return; + } + + weld::Dialog* pDialog = mpDialog->getDialog(); + + mxMarkWnd = std::make_shared<SvxHlinkDlgMarkWnd>(pDialog, this); + + // Size of dialog-window in screen pixels + Point aDlgPos(pDialog->get_position()); + Size aDlgSize(pDialog->get_size()); + + // Absolute size of the screen + ::tools::Rectangle aScreen(pDialog->get_monitor_workarea()); + + // Size of Extrawindow + Size aExtraWndSize(mxMarkWnd->getDialog()->get_preferred_size()); + + // mxMarkWnd is a child of mpDialog, so coordinates for positioning must be relative to mpDialog + if( aDlgPos.X()+(1.05*aDlgSize.Width())+aExtraWndSize.Width() > aScreen.Right() ) + { + if( aDlgPos.X() - ( 0.05*aDlgSize.Width() ) - aExtraWndSize.Width() < 0 ) + { + // Pos Extrawindow anywhere + MoveToExtraWnd( Point(10,10) ); // very unlikely + } + else + { + // Pos Extrawindow on the left side of Dialog + MoveToExtraWnd( Point(0,0) - Point( tools::Long(0.05*aDlgSize.Width()), 0 ) - Point( aExtraWndSize.Width(), 0 ) ); + } + } + else + { + // Pos Extrawindow on the right side of Dialog + MoveToExtraWnd ( Point( tools::Long(1.05*aDlgSize.getWidth()), 0 ) ); + } + + // Set size of Extra-Window + mxMarkWnd->getDialog()->set_size_request(aExtraWndSize.Width(), aDlgSize.Height()); + + weld::DialogController::runAsync(mxMarkWnd, [this](sal_Int32 /*nResult*/) { mxMarkWnd.reset(); } ); +} + +void SvxHyperlinkTabPageBase::HideMarkWnd() +{ + if (mxMarkWnd) + { + mxMarkWnd->response(RET_CANCEL); + mxMarkWnd.reset(); + } +} + +// Fill Dialogfields +void SvxHyperlinkTabPageBase::FillStandardDlgFields ( const SvxHyperlinkItem* pHyperlinkItem ) +{ + if (!comphelper::LibreOfficeKit::isActive()) + { + // Frame + sal_Int32 nPos = mxCbbFrame->find_text(pHyperlinkItem->GetTargetFrame()); + if (nPos != -1) + mxCbbFrame->set_active(nPos); + + // Form + OUString aStrFormText = CuiResId( RID_CUISTR_HYPERDLG_FROM_TEXT ); + + OUString aStrFormButton = CuiResId( RID_CUISTR_HYPERDLG_FORM_BUTTON ); + + if( pHyperlinkItem->GetInsertMode() & HLINK_HTMLMODE ) + { + mxLbForm->clear(); + mxLbForm->append_text( aStrFormText ); + mxLbForm->set_active( 0 ); + } + else + { + mxLbForm->clear(); + mxLbForm->append_text( aStrFormText ); + mxLbForm->append_text( aStrFormButton ); + mxLbForm->set_active( pHyperlinkItem->GetInsertMode() == HLINK_BUTTON ? 1 : 0 ); + } + } + else + { + mxCbbFrame->hide(); + mxLbForm->hide(); + mxFormLabel->hide(); + mxFrameLabel->hide(); + } + + // URL + mxEdIndication->set_text( pHyperlinkItem->GetName() ); + + // Name + mxEdText->set_text( pHyperlinkItem->GetIntName() ); + + // Script-button + if (!comphelper::LibreOfficeKit::isActive()) + { + if ( pHyperlinkItem->GetMacroEvents() == HyperDialogEvent::NONE ) + mxBtScript->set_sensitive(false); + else + mxBtScript->set_sensitive(true); + } + else + { + mxBtScript->hide(); + } +} + +// Any action to do after apply-button is pressed +void SvxHyperlinkTabPageBase::DoApply () +{ + // default-implementation : do nothing +} + +// This method would be called from bookmark-window to set new mark-string +void SvxHyperlinkTabPageBase::SetMarkStr ( const OUString& /*aStrMark*/ ) +{ + // default-implementation : do nothing +} + +// Set initial focus +void SvxHyperlinkTabPageBase::SetInitFocus() +{ + xContainer->grab_focus(); +} + +// retrieve dispatcher +SfxDispatcher* SvxHyperlinkTabPageBase::GetDispatcher() const +{ + return mpDialog->GetDispatcher(); +} + +void SvxHyperlinkTabPageBase::DisableClose(bool _bDisable) +{ + mbIsCloseDisabled = _bDisable; + if (mbIsCloseDisabled) + maBusy.incBusy(mpDialog->getDialog()); + else + maBusy.decBusy(); +} + +// Click on imagebutton : Script +IMPL_LINK_NOARG(SvxHyperlinkTabPageBase, ClickScriptHdl_Impl, weld::Button&, void) +{ + SvxHyperlinkItem *pHyperlinkItem = const_cast<SvxHyperlinkItem*>( + GetItemSet().GetItem (SID_HYPERLINK_GETLINK)); + + if (!pHyperlinkItem || pHyperlinkItem->GetMacroEvents() == HyperDialogEvent::NONE) + return; + + // get macros from itemset + const SvxMacroTableDtor* pMacroTbl = pHyperlinkItem->GetMacroTable(); + SvxMacroItem aItem ( SID_ATTR_MACROITEM ); + if( pMacroTbl ) + aItem.SetMacroTable( *pMacroTbl ); + + // create empty itemset for macro-dlg + SfxItemSetFixed<SID_ATTR_MACROITEM, SID_ATTR_MACROITEM> aItemSet( SfxGetpApp()->GetPool() ); + aItemSet.Put ( aItem ); + + DisableClose( true ); + + SfxMacroAssignDlg aDlg(mpDialog->getDialog(), mxDocumentFrame, aItemSet); + + // add events + SfxMacroTabPage *pMacroPage = aDlg.GetTabPage(); + + if ( pHyperlinkItem->GetMacroEvents() & HyperDialogEvent::MouseOverObject ) + pMacroPage->AddEvent( CuiResId(RID_CUISTR_HYPDLG_MACROACT1), + SvMacroItemId::OnMouseOver ); + if ( pHyperlinkItem->GetMacroEvents() & HyperDialogEvent::MouseClickObject ) + pMacroPage->AddEvent( CuiResId(RID_CUISTR_HYPDLG_MACROACT2), + SvMacroItemId::OnClick); + if ( pHyperlinkItem->GetMacroEvents() & HyperDialogEvent::MouseOutObject ) + pMacroPage->AddEvent( CuiResId(RID_CUISTR_HYPDLG_MACROACT3), + SvMacroItemId::OnMouseOut); + // execute dlg + short nRet = aDlg.run(); + DisableClose( false ); + if ( RET_OK == nRet ) + { + const SfxItemSet* pOutSet = aDlg.GetOutputItemSet(); + const SfxPoolItem* pItem; + if( SfxItemState::SET == pOutSet->GetItemState( SID_ATTR_MACROITEM, false, &pItem )) + { + pHyperlinkItem->SetMacroTable( static_cast<const SvxMacroItem*>(pItem)->GetMacroTable() ); + } + } +} + +// Get Macro-Infos +HyperDialogEvent SvxHyperlinkTabPageBase::GetMacroEvents() const +{ + const SvxHyperlinkItem *pHyperlinkItem = + GetItemSet().GetItem (SID_HYPERLINK_GETLINK); + + return pHyperlinkItem ? pHyperlinkItem->GetMacroEvents() : HyperDialogEvent(); +} + +SvxMacroTableDtor* SvxHyperlinkTabPageBase::GetMacroTable() +{ + const SvxHyperlinkItem *pHyperlinkItem = + GetItemSet().GetItem (SID_HYPERLINK_GETLINK); + + return const_cast<SvxMacroTableDtor*>(pHyperlinkItem->GetMacroTable()); +} + +// try to detect the current protocol that is used in rStrURL +OUString SvxHyperlinkTabPageBase::GetSchemeFromURL( const OUString& rStrURL ) +{ + OUString aStrScheme; + + INetURLObject aURL( rStrURL ); + INetProtocol aProtocol = aURL.GetProtocol(); + + // our new INetUrlObject now has the ability + // to detect if a Url is valid or not :-( + if ( aProtocol == INetProtocol::NotValid ) + { + if ( rStrURL.startsWithIgnoreAsciiCase( INET_HTTP_SCHEME ) ) + { + aStrScheme = INET_HTTP_SCHEME; + } + else if ( rStrURL.startsWithIgnoreAsciiCase( INET_HTTPS_SCHEME ) ) + { + aStrScheme = INET_HTTPS_SCHEME; + } + else if ( rStrURL.startsWithIgnoreAsciiCase( INET_FTP_SCHEME ) ) + { + aStrScheme = INET_FTP_SCHEME; + } + else if ( rStrURL.startsWithIgnoreAsciiCase( INET_MAILTO_SCHEME ) ) + { + aStrScheme = INET_MAILTO_SCHEME; + } + } + else + aStrScheme = INetURLObject::GetScheme( aProtocol ); + return aStrScheme; +} + +void SvxHyperlinkTabPageBase::GetDataFromCommonFields( OUString& aStrName, + OUString& aStrIntName, OUString& aStrFrame, + SvxLinkInsertMode& eMode ) +{ + aStrIntName = mxEdText->get_text(); + aStrName = mxEdIndication->get_text(); + aStrFrame = mxCbbFrame->get_active_text(); + + sal_Int32 nPos = mxLbForm->get_active(); + if (nPos == -1) + // This happens when FillStandardDlgFields() hides mpLbForm. + nPos = 0; + eMode = static_cast<SvxLinkInsertMode>(nPos + 1); + + // Ask dialog whether the current doc is a HTML-doc + if (mpDialog->IsHTMLDoc()) + eMode = static_cast<SvxLinkInsertMode>( sal_uInt16(eMode) | HLINK_HTMLMODE ); +} + +// reset dialog-fields +void SvxHyperlinkTabPageBase::Reset( const SfxItemSet& rItemSet) +{ + + // Set dialog-fields from create-itemset + maStrInitURL.clear(); + + const SvxHyperlinkItem *pHyperlinkItem = + rItemSet.GetItem (SID_HYPERLINK_GETLINK); + + if ( pHyperlinkItem ) + { + // set dialog-fields + FillStandardDlgFields (pHyperlinkItem); + + // set all other fields + FillDlgFields ( pHyperlinkItem->GetURL() ); + + // Store initial URL + maStrInitURL = pHyperlinkItem->GetURL(); + } +} + +// Fill output-ItemSet +bool SvxHyperlinkTabPageBase::FillItemSet( SfxItemSet* rOut) +{ + OUString aStrURL, aStrName, aStrIntName, aStrFrame; + SvxLinkInsertMode eMode; + + GetCurentItemData ( aStrURL, aStrName, aStrIntName, aStrFrame, eMode); + if ( aStrName.isEmpty() ) //automatically create a visible name if the link is created without name + aStrName = CreateUiNameFromURL(aStrURL); + + HyperDialogEvent nEvents = GetMacroEvents(); + SvxMacroTableDtor* pTable = GetMacroTable(); + + SvxHyperlinkItem aItem( SID_HYPERLINK_SETLINK, aStrName, aStrURL, aStrFrame, + aStrIntName, eMode, nEvents, pTable ); + rOut->Put (aItem); + + return true; +} + +// Activate / Deactivate Tabpage +void SvxHyperlinkTabPageBase::ActivatePage( const SfxItemSet& rItemSet ) +{ + + // Set dialog-fields from input-itemset + const SvxHyperlinkItem *pHyperlinkItem = + rItemSet.GetItem (SID_HYPERLINK_GETLINK); + + if ( pHyperlinkItem ) + { + // standard-fields + FillStandardDlgFields (pHyperlinkItem); + } + + // show mark-window if it was open before + if ( ShouldOpenMarkWnd () ) + ShowMarkWnd (); +} + +DeactivateRC SvxHyperlinkTabPageBase::DeactivatePage( SfxItemSet* _pSet) +{ + // hide mark-wnd + SetMarkWndShouldOpen( IsMarkWndVisible () ); + HideMarkWnd (); + + // retrieve data of dialog + OUString aStrURL, aStrName, aStrIntName, aStrFrame; + SvxLinkInsertMode eMode; + + GetCurentItemData ( aStrURL, aStrName, aStrIntName, aStrFrame, eMode); + + HyperDialogEvent nEvents = GetMacroEvents(); + SvxMacroTableDtor* pTable = GetMacroTable(); + + if( _pSet ) + { + SvxHyperlinkItem aItem( SID_HYPERLINK_GETLINK, aStrName, aStrURL, aStrFrame, + aStrIntName, eMode, nEvents, pTable ); + _pSet->Put( aItem ); + } + + return DeactivateRC::LeavePage; +} + +bool SvxHyperlinkTabPageBase::ShouldOpenMarkWnd() +{ + return false; +} + +void SvxHyperlinkTabPageBase::SetMarkWndShouldOpen(bool) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/hyphen.cxx b/cui/source/dialogs/hyphen.cxx new file mode 100644 index 000000000..259ec5d03 --- /dev/null +++ b/cui/source/dialogs/hyphen.cxx @@ -0,0 +1,472 @@ +/* -*- 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 <hyphen.hxx> + +#include <com/sun/star/linguistic2/XLinguProperties.hpp> + +#include <editeng/splwrap.hxx> +#include <editeng/unolingu.hxx> +#include <svtools/langtab.hxx> +#include <sal/log.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <tools/debug.hxx> +#include <utility> + +#define HYPH_POS_CHAR '=' + +#define CUR_HYPH_POS_CHAR '-' + +using namespace css; + +IMPL_LINK_NOARG(SvxHyphenWordDialog, CursorChangeHdl_Impl, weld::Entry&, void) +{ + int nStart, nEnd; + m_xWordEdit->get_selection_bounds(nStart, nEnd); + if (nStart == m_nOldPos && nEnd == m_nOldPos + 1) + return; + bool bReSelect; + if (nStart <= m_nOldPos) + bReSelect = !SelLeft(); + else + bReSelect = !SelRight(); + if (bReSelect) + select_region(m_nOldPos, m_nOldPos + 1); +} + +void SvxHyphenWordDialog::EnableLRBtn_Impl() +{ + const sal_Int32 nLen = m_aEditWord.getLength(); + + m_xRightBtn->set_sensitive(false); + for ( sal_Int32 i = m_nOldPos + 2; i < nLen; ++i ) + { + if ( m_aEditWord[ i ] == sal_Unicode( HYPH_POS_CHAR ) ) + { + m_xRightBtn->set_sensitive(true); + break; + } + } + + DBG_ASSERT(m_nOldPos < nLen, "nOldPos out of range"); + if (m_nOldPos >= nLen) + m_nOldPos = nLen - 1; + m_xLeftBtn->set_sensitive(false); + for ( sal_Int32 i = m_nOldPos; i-- > 0; ) + { + if ( m_aEditWord[ i ] == sal_Unicode( HYPH_POS_CHAR ) ) + { + m_xLeftBtn->set_sensitive(true); + break; + } + } +} + +OUString SvxHyphenWordDialog::EraseUnusableHyphens_Impl() +{ + // returns a String showing only those hyphen positions which will result + // in a line break if hyphenation is done there + // 1) we will need to discard all hyphenation positions at the end that + // will not result in a line break where the text to the left still fits + // on the line. + // 2) since as from OOo 3.2 '-' are part of a word and thus text like + // 'multi-line-editor' is regarded as single word we also need to discard those + // hyphenation positions to the left of the rightmost '-' that is still left of + // the rightmost valid hyphenation position according to 1) + + // Example: + // If the possible hyphenation position in 'multi-line-editor' are to be marked + // by '=' then the text will look like this: 'mul=ti-line-ed=it=or'. + // If now the first line is only large enough for 'multi-line-edi' we need to discard + // the last possible hyphenation point because of 1). The right most valid + // hyphenation position is "ed=itor". The first '-' left of this position is + // "line-ed", thus because of 2) we now need to discard all possible hyphenation + // positions to the left of that as well. Thus in the end leaving us with just + // 'multi-line-ed=itor' as return value for this function. (Just one valid hyphenation + // position for the user to choose from. However ALL the '-' characters in the word + // will ALWAYS be valid implicit hyphenation positions for the core to choose from! + // And thus even if this word is skipped in the hyphenation dialog it will still be broken + // right after 'multi-line-' (actually it might already be broken up that way before + // the hyphenation dialog is called!). + // Thus rule 2) just eliminates those positions which will not be used by the core at all + // even if the user were to select one of them. + + OUString aTxt; + DBG_ASSERT(m_xPossHyph.is(), "missing possible hyphens"); + if (m_xPossHyph.is()) + { + DBG_ASSERT( m_aActWord == m_xPossHyph->getWord(), "word mismatch" ); + + aTxt = m_xPossHyph->getPossibleHyphens(); + + m_nHyphenationPositionsOffset = 0; + uno::Sequence< sal_Int16 > aHyphenationPositions( + m_xPossHyph->getHyphenationPositions() ); + sal_Int32 nLen = aHyphenationPositions.getLength(); + const sal_Int16 *pHyphenationPos = aHyphenationPositions.getConstArray(); + + // find position nIdx after which all hyphen positions are unusable + sal_Int32 nIdx = -1; + sal_Int32 nPos = 0, nPos1 = 0; + if (nLen) + { + sal_Int32 nStart = 0; + for (sal_Int32 i = 0; i < nLen; ++i) + { + if (pHyphenationPos[i] > m_nMaxHyphenationPos) + break; + else + { + // find corresponding hyphen positions in string + nPos = aTxt.indexOf( sal_Unicode( HYPH_POS_CHAR ), nStart ); + + if (nPos == -1) + break; + else + { + nIdx = nPos; + nStart = nPos + 1; + } + } + } + } + DBG_ASSERT(nIdx != -1, "no usable hyphenation position"); + + // 1) remove all not usable hyphenation positions from the end of the string + nPos = nIdx == -1 ? 0 : nIdx + 1; + nPos1 = nPos; //save for later use in 2) below + const OUString aTmp( sal_Unicode( HYPH_POS_CHAR ) ); + while (nPos != -1) + { + nPos++; + aTxt = aTxt.replaceFirst( aTmp, "", &nPos); + } + + // 2) remove all hyphenation positions from the start that are not considered by the core + const std::u16string_view aSearchRange( aTxt.subView( 0, nPos1 ) ); + size_t nPos2 = aSearchRange.rfind( '-' ); // the '-' position the core will use by default + if (nPos2 != std::u16string_view::npos && nPos2 != 0) + { + OUString aLeft( aSearchRange.substr( 0, nPos2 ) ); + nPos = 0; + while (nPos != -1) + { + nPos++; + aLeft = aLeft.replaceFirst( aTmp, "", &nPos ); + if (nPos != -1) + ++m_nHyphenationPositionsOffset; + } + aTxt = aTxt.replaceAt( 0, nPos2, aLeft ); + } + } + return aTxt; +} + +void SvxHyphenWordDialog::InitControls_Impl() +{ + m_xPossHyph = nullptr; + if (m_xHyphenator.is()) + { + lang::Locale aLocale( LanguageTag::convertToLocale(m_nActLanguage) ); + m_xPossHyph = m_xHyphenator->createPossibleHyphens( m_aActWord, aLocale, + uno::Sequence< beans::PropertyValue >() ); + if (m_xPossHyph.is()) + m_aEditWord = EraseUnusableHyphens_Impl(); + } + m_xWordEdit->set_text(m_aEditWord); + + m_nOldPos = m_aEditWord.getLength(); + SelLeft(); + EnableLRBtn_Impl(); +} + +void SvxHyphenWordDialog::ContinueHyph_Impl( sal_Int32 nInsPos ) +{ + if ( nInsPos >= 0 && m_xPossHyph.is() ) + { + if (nInsPos) + { + DBG_ASSERT(nInsPos <= m_aEditWord.getLength() - 2, "wrong hyphen position"); + + sal_Int32 nIdxPos = -1; + for (sal_Int32 i = 0; i <= nInsPos; ++i) + { + if (HYPH_POS_CHAR == m_aEditWord[ i ]) + nIdxPos++; + } + // take the possible hyphenation positions that got removed from the + // start of the word into account: + nIdxPos += m_nHyphenationPositionsOffset; + + uno::Sequence< sal_Int16 > aSeq = m_xPossHyph->getHyphenationPositions(); + sal_Int32 nLen = aSeq.getLength(); + DBG_ASSERT(nLen, "empty sequence"); + DBG_ASSERT(0 <= nIdxPos && nIdxPos < nLen, "index out of range"); + if (nLen && 0 <= nIdxPos && nIdxPos < nLen) + { + nInsPos = aSeq.getConstArray()[ nIdxPos ]; + m_pHyphWrapper->InsertHyphen( nInsPos ); + } + } + else + { + //! calling with 0 as argument will remove hyphens! + m_pHyphWrapper->InsertHyphen( nInsPos ); + } + } + + if ( m_pHyphWrapper->FindSpellError() ) + { + uno::Reference< linguistic2::XHyphenatedWord > xHyphWord( m_pHyphWrapper->GetLast(), uno::UNO_QUERY ); + + // adapt actual word and language to new found hyphenation result + if(xHyphWord.is()) + { + m_aActWord = xHyphWord->getWord(); + m_nActLanguage = LanguageTag( xHyphWord->getLocale() ).getLanguageType(); + m_nMaxHyphenationPos = xHyphWord->getHyphenationPos(); + InitControls_Impl(); + SetWindowTitle( m_nActLanguage ); + } + } + else + { + m_xCloseBtn->set_sensitive(false); + m_xDialog->response(RET_OK); + } +} + +bool SvxHyphenWordDialog::SelLeft() +{ + bool bRet = false; + DBG_ASSERT( m_nOldPos > 0, "invalid hyphenation position" ); + if (m_nOldPos > 0) + { + OUString aTxt( m_aEditWord ); + for( sal_Int32 i = m_nOldPos - 1; i > 0; --i ) + { + DBG_ASSERT(i <= aTxt.getLength(), "index out of range"); + if (aTxt[ i ] == sal_Unicode( HYPH_POS_CHAR )) + { + aTxt = aTxt.replaceAt( i, 1, rtl::OUStringChar( CUR_HYPH_POS_CHAR ) ); + + m_nOldPos = i; + m_xWordEdit->set_text(aTxt); + select_region(i, i + 1); + m_xWordEdit->grab_focus(); + bRet = true; + break; + } + } + EnableLRBtn_Impl(); + } + return bRet; +} + +bool SvxHyphenWordDialog::SelRight() +{ + bool bRet = false; + OUString aTxt( m_aEditWord ); + for ( sal_Int32 i = m_nOldPos + 1; i < aTxt.getLength(); ++i ) + { + if (aTxt[ i ] == sal_Unicode( HYPH_POS_CHAR )) + { + aTxt = aTxt.replaceAt( i, 1, rtl::OUStringChar( CUR_HYPH_POS_CHAR ) ); + + m_nOldPos = i; + m_xWordEdit->set_text(aTxt); + select_region(i, i + 1); + m_xWordEdit->grab_focus(); + bRet = true; + break; + } + } + EnableLRBtn_Impl(); + return bRet; +} + +IMPL_LINK_NOARG(SvxHyphenWordDialog, CutHdl_Impl, weld::Button&, void) +{ + if( !m_bBusy ) + { + m_bBusy = true; + ContinueHyph_Impl( /*m_nHyphPos*/m_nOldPos ); + m_bBusy = false; + } +} + +IMPL_LINK_NOARG(SvxHyphenWordDialog, HyphenateAllHdl_Impl, weld::Button&, void) +{ + if( m_bBusy ) + return; + + try + { + uno::Reference< linguistic2::XLinguProperties > xProp( LinguMgr::GetLinguPropertySet() ); + + xProp->setIsHyphAuto( true ); + + m_bBusy = true; + ContinueHyph_Impl( /*m_nHyphPos*/m_nOldPos ); + m_bBusy = false; + + xProp->setIsHyphAuto( false ); + } + catch (uno::Exception &) + { + SAL_WARN( "cui.dialogs", "Hyphenate All failed" ); + } +} + +IMPL_LINK_NOARG(SvxHyphenWordDialog, DeleteHdl_Impl, weld::Button&, void) +{ + if( !m_bBusy ) + { + m_bBusy = true; + ContinueHyph_Impl( 0 ); + m_bBusy = false; + } +} + +IMPL_LINK_NOARG(SvxHyphenWordDialog, ContinueHdl_Impl, weld::Button&, void) +{ + if( !m_bBusy ) + { + m_bBusy = true; + ContinueHyph_Impl(); + m_bBusy = false; + } +} + +IMPL_LINK_NOARG(SvxHyphenWordDialog, CancelHdl_Impl, weld::Button&, void) +{ + if( !m_bBusy ) + { + m_bBusy = true; + m_xDialog->response(RET_CANCEL); + m_bBusy = false; + } +} + +IMPL_LINK_NOARG(SvxHyphenWordDialog, Left_Impl, weld::Button&, void) +{ + if( !m_bBusy ) + { + m_bBusy = true; + SelLeft(); + m_bBusy = false; + } +} + +IMPL_LINK_NOARG(SvxHyphenWordDialog, Right_Impl, weld::Button&, void) +{ + if( !m_bBusy ) + { + m_bBusy = true; + SelRight(); + m_bBusy = false; + } +} + +void SvxHyphenWordDialog::select_region(int nStart, int nEnd) +{ + int nScrollPos = nStart + m_nWordEditWidth/2; + if (nScrollPos > m_aEditWord.getLength()) + nScrollPos = m_aEditWord.getLength() - m_nWordEditWidth/2; + if (nScrollPos < 0) + nScrollPos = 0; + m_xWordEdit->set_position(nScrollPos); + m_xWordEdit->select_region(nStart, nEnd); +} + +IMPL_LINK_NOARG(SvxHyphenWordDialog, GetFocusHdl_Impl, weld::Widget&, void) +{ + select_region(m_nOldPos, m_nOldPos + 1); +} + +// class SvxHyphenWordDialog --------------------------------------------- + +SvxHyphenWordDialog::SvxHyphenWordDialog( + OUString aWord, LanguageType nLang, + weld::Widget* pParent, + uno::Reference< linguistic2::XHyphenator > const &xHyphen, + SvxSpellWrapper* pWrapper) + : SfxDialogController(pParent, "cui/ui/hyphenate.ui", "HyphenateDialog") + , m_pHyphWrapper(pWrapper) + , m_aActWord(std::move(aWord)) + , m_nActLanguage(nLang) + , m_nMaxHyphenationPos(0) + , m_nOldPos(0) + , m_nHyphenationPositionsOffset(0) + , m_bBusy(false) + , m_xWordEdit(m_xBuilder->weld_entry("worded")) + , m_xLeftBtn(m_xBuilder->weld_button("left")) + , m_xRightBtn(m_xBuilder->weld_button("right")) + , m_xOkBtn(m_xBuilder->weld_button("ok")) + , m_xContBtn(m_xBuilder->weld_button("continue")) + , m_xDelBtn(m_xBuilder->weld_button("delete")) + , m_xHyphAll(m_xBuilder->weld_button("hyphall")) + , m_xCloseBtn(m_xBuilder->weld_button("close")) +{ + m_nWordEditWidth = m_xWordEdit->get_width_chars(); + m_aLabel = m_xDialog->get_title(); + m_xHyphenator = xHyphen; + + uno::Reference< linguistic2::XHyphenatedWord > xHyphWord( m_pHyphWrapper ? + m_pHyphWrapper->GetLast() : nullptr, uno::UNO_QUERY ); + DBG_ASSERT( xHyphWord.is(), "hyphenation result missing" ); + if (xHyphWord.is()) + { + DBG_ASSERT( m_aActWord == xHyphWord->getWord(), "word mismatch" ); + DBG_ASSERT( m_nActLanguage == LanguageTag( xHyphWord->getLocale() ).getLanguageType(), "language mismatch" ); + m_nMaxHyphenationPos = xHyphWord->getHyphenationPos(); + } + + InitControls_Impl(); + m_xWordEdit->grab_focus(); + + m_xLeftBtn->connect_clicked( LINK( this, SvxHyphenWordDialog, Left_Impl ) ); + m_xRightBtn->connect_clicked( LINK( this, SvxHyphenWordDialog, Right_Impl ) ); + m_xOkBtn->connect_clicked( LINK( this, SvxHyphenWordDialog, CutHdl_Impl ) ); + m_xContBtn->connect_clicked( LINK( this, SvxHyphenWordDialog, ContinueHdl_Impl ) ); + m_xDelBtn->connect_clicked( LINK( this, SvxHyphenWordDialog, DeleteHdl_Impl ) ); + m_xHyphAll->connect_clicked( LINK( this, SvxHyphenWordDialog, HyphenateAllHdl_Impl ) ); + m_xCloseBtn->connect_clicked( LINK( this, SvxHyphenWordDialog, CancelHdl_Impl ) ); + m_xWordEdit->connect_focus_in( LINK( this, SvxHyphenWordDialog, GetFocusHdl_Impl ) ); + m_xWordEdit->connect_cursor_position( LINK( this, SvxHyphenWordDialog, CursorChangeHdl_Impl ) ); + + SetWindowTitle( nLang ); + + // disable controls if service is not available + if (!m_xHyphenator.is()) + m_xDialog->set_sensitive(false); +} + +SvxHyphenWordDialog::~SvxHyphenWordDialog() +{ + if (m_xCloseBtn->get_sensitive()) + m_pHyphWrapper->SpellEnd(); +} + +void SvxHyphenWordDialog::SetWindowTitle(LanguageType nLang) +{ + m_xDialog->set_title(m_aLabel + " (" + SvtLanguageTable::GetLanguageString(nLang) + ")"); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/iconcdlg.cxx b/cui/source/dialogs/iconcdlg.cxx new file mode 100644 index 000000000..6e6ffaf5c --- /dev/null +++ b/cui/source/dialogs/iconcdlg.cxx @@ -0,0 +1,308 @@ +/* -*- 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 <iconcdlg.hxx> +#include <cuihyperdlg.hxx> + +#include <cassert> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> + +/********************************************************************** +| +| Ctor / Dtor +| +\**********************************************************************/ + +IconChoicePage::IconChoicePage(weld::Container* pParent, + const OUString& rUIXMLDescription, const OString& rID, + const SfxItemSet* pItemSet) + : xBuilder(Application::CreateBuilder(pParent, rUIXMLDescription)) + , xContainer(xBuilder->weld_container(rID)) + , pSet(pItemSet) + , bHasExchangeSupport(false) +{ +} + +IconChoicePage::~IconChoicePage() +{ +} + +/********************************************************************** +| +| Activate / Deactivate +| +\**********************************************************************/ + +void IconChoicePage::ActivatePage( const SfxItemSet& ) +{ +} + + +DeactivateRC IconChoicePage::DeactivatePage( SfxItemSet* ) +{ + return DeactivateRC::LeavePage; +} + +bool IconChoicePage::QueryClose() +{ + return true; +} + +/********************************************************************** +| +| add new page +| +\**********************************************************************/ +void SvxHpLinkDlg::AddTabPage(const OString& rId, CreatePage pCreateFunc /* != 0 */) +{ + weld::Container* pPage = m_xIconCtrl->get_page(rId); + maPageList.emplace_back(new IconChoicePageData(rId, pCreateFunc(pPage, this, pSet))); + maPageList.back()->xPage->Reset(*pSet); + PageCreated(*maPageList.back()->xPage); +} + +/********************************************************************** +| +| Show / Hide page or button +| +\**********************************************************************/ +void SvxHpLinkDlg::ShowPage(const OString& rId) +{ + OString sOldPageId = GetCurPageId(); + bool bInvalidate = sOldPageId != rId; + if (bInvalidate) + { + IconChoicePageData* pOldData = GetPageData(sOldPageId); + if (pOldData && pOldData->xPage) + { + DeActivatePageImpl(); + } + } + SetCurPageId(rId); + ActivatePageImpl(); +} + +/********************************************************************** +| +| select a page +| +\**********************************************************************/ +IMPL_LINK(SvxHpLinkDlg, ChosePageHdl_Impl, const OString&, rId, void) +{ + if (rId != msCurrentPageId) + { + ShowPage(rId); + } +} + +/********************************************************************** +| +| Button-handler +| +\**********************************************************************/ +IMPL_LINK_NOARG(SvxHpLinkDlg, ResetHdl, weld::Button&, void) +{ + ResetPageImpl (); + + IconChoicePageData* pData = GetPageData ( msCurrentPageId ); + assert( pData && "ID not known " ); + + pData->xPage->Reset( *pSet ); +} + +/********************************************************************** +| +| call page +| +\**********************************************************************/ +void SvxHpLinkDlg::ActivatePageImpl() +{ + assert( !maPageList.empty() && "no Pages registered " ); + IconChoicePageData* pData = GetPageData ( msCurrentPageId ); + assert( pData && "ID not known " ); + + if ( pData->bRefresh ) + { + pData->xPage->Reset( *pSet ); + pData->bRefresh = false; + } + + if ( pExampleSet ) + pData->xPage->ActivatePage( *pExampleSet ); + m_xDialog->set_help_id(pData->xPage->GetHelpId()); + + m_xResetBtn->show(); +} + +void SvxHpLinkDlg::DeActivatePageImpl () +{ + IconChoicePageData *pData = GetPageData ( msCurrentPageId ); + + DeactivateRC nRet = DeactivateRC::LeavePage; + + if ( !pData ) + return; + + IconChoicePage * pPage = pData->xPage.get(); + + if ( !pExampleSet && pPage->HasExchangeSupport() && pSet ) + pExampleSet.reset(new SfxItemSet( *pSet->GetPool(), pSet->GetRanges() )); + + if ( pSet ) + { + SfxItemSet aTmp( *pSet->GetPool(), pSet->GetRanges() ); + + if ( pPage->HasExchangeSupport() ) + nRet = pPage->DeactivatePage( &aTmp ); + + if ( ( DeactivateRC::LeavePage & nRet ) && + aTmp.Count() ) + { + if (pExampleSet) + pExampleSet->Put(aTmp); + pOutSet->Put( aTmp ); + } + } + else + { + if ( pPage->HasExchangeSupport() ) //!!! + { + if ( !pExampleSet ) + { + SfxItemPool* pPool = pPage->GetItemSet().GetPool(); + pExampleSet.reset( + new SfxItemSet( *pPool, GetInputRanges( *pPool ) ) ); + } + nRet = pPage->DeactivatePage( pExampleSet.get() ); + } + else + nRet = pPage->DeactivatePage( nullptr ); + } + + if ( nRet & DeactivateRC::RefreshSet ) + { + // TODO refresh input set + // flag all pages to be newly initialized + for (auto & pObj : maPageList) + { + if ( pObj->xPage.get() != pPage ) + pObj->bRefresh = true; + else + pObj->bRefresh = false; + } + } +} + + +void SvxHpLinkDlg::ResetPageImpl () +{ + IconChoicePageData *pData = GetPageData ( msCurrentPageId ); + + assert( pData && "ID not known " ); + + pData->xPage->Reset( *pSet ); +} + +/********************************************************************** +| +| handling itemsets +| +\**********************************************************************/ + +WhichRangesContainer SvxHpLinkDlg::GetInputRanges( const SfxItemPool& ) +{ + if ( pSet ) + { + SAL_WARN( "cui.dialogs", "Set does already exist!" ); + return pSet->GetRanges(); + } + + if ( !pRanges.empty() ) + return pRanges; + + return WhichRangesContainer(); +} + + +void SvxHpLinkDlg::SetInputSet( const SfxItemSet* pInSet ) +{ + bool bSet = ( pSet != nullptr ); + + pSet = pInSet; + + if ( !bSet && !pExampleSet && !pOutSet ) + { + pExampleSet.reset(new SfxItemSet( *pSet )); + pOutSet.reset(new SfxItemSet( *pSet->GetPool(), pSet->GetRanges() )); + } +} + +bool SvxHpLinkDlg::QueryClose() +{ + bool bRet = true; + for (auto & pData : maPageList) + { + if ( pData->xPage && !pData->xPage->QueryClose() ) + { + bRet = false; + break; + } + } + return bRet; +} + +void SvxHpLinkDlg::Start() +{ + SwitchPage(msCurrentPageId); + ActivatePageImpl(); +} + +/********************************************************************** +| +| tool-methods +| +\**********************************************************************/ + +IconChoicePageData* SvxHpLinkDlg::GetPageData ( std::string_view rId ) +{ + IconChoicePageData *pRet = nullptr; + for (const auto & pData : maPageList) + { + if ( pData->sId == rId ) + { + pRet = pData.get(); + break; + } + } + return pRet; +} + +/********************************************************************** +| +| OK-Status +| +\**********************************************************************/ + +void SvxHpLinkDlg::SwitchPage( const OString& rId ) +{ + m_xIconCtrl->set_current_page(rId); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/insdlg.cxx b/cui/source/dialogs/insdlg.cxx new file mode 100644 index 000000000..3806c4dc2 --- /dev/null +++ b/cui/source/dialogs/insdlg.cxx @@ -0,0 +1,635 @@ +/* -*- 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 <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/embed/EmbedStates.hpp> +#include <com/sun/star/embed/XInsertObjectDialog.hpp> +#include <com/sun/star/embed/MSOLEObjectSystemCreator.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> +#include <com/sun/star/task/XStatusIndicatorFactory.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> + +#include <insdlg.hxx> +#include <dialmgr.hxx> +#include <osl/diagnose.h> +#include <svtools/imagemgr.hxx> +#include <svtools/strings.hrc> +#include <svtools/svtresid.hxx> + +#include <tools/urlobj.hxx> +#include <tools/debug.hxx> +#include <tools/stream.hxx> +#include <tools/diagnose_ex.h> +#include <utility> +#include <vcl/image.hxx> +#include <vcl/weld.hxx> +#include <vcl/svapp.hxx> +#include <comphelper/classids.hxx> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/frmdescr.hxx> +#include <sfx2/viewsh.hxx> +#include <comphelper/seqstream.hxx> +#include <sfx2/viewfrm.hxx> + +#include <strings.hrc> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::ui::dialogs; + +bool InsertObjectDialog_Impl::IsCreateNew() const +{ + return false; +} + +uno::Reference< io::XInputStream > InsertObjectDialog_Impl::GetIconIfIconified( OUString* /*pGraphicMediaType*/ ) +{ + return uno::Reference< io::XInputStream >(); +} + +InsertObjectDialog_Impl::InsertObjectDialog_Impl(weld::Window* pParent, + const OUString& rUIXMLDescription, const OString& rID, + css::uno::Reference < css::embed::XStorage > xStorage) + : GenericDialogController(pParent, rUIXMLDescription, rID) + , m_xStorage(std::move( xStorage )) + , aCnt( m_xStorage ) +{ +} + +IMPL_LINK_NOARG(SvInsertOleDlg, DoubleClickHdl, weld::TreeView&, bool) +{ + m_xDialog->response(RET_OK); + return true; +} + +IMPL_LINK_NOARG(SvInsertOleDlg, BrowseHdl, weld::Button&, void) +{ + sfx2::FileDialogHelper aHelper(ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE, FileDialogFlags::NONE, m_xDialog.get()); + aHelper.SetContext(sfx2::FileDialogHelper::InsertOLE); + const Reference< XFilePicker3 >& xFilePicker = aHelper.GetFilePicker(); + + // add filter + try + { + xFilePicker->appendFilter(CuiResId(RID_CUISTR_FILTER_ALL), "*.*"); + } + catch( const IllegalArgumentException& ) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", "caught IllegalArgumentException when registering filter" ); + } + + if( xFilePicker->execute() == ExecutableDialogResults::OK ) + { + Sequence< OUString > aPathSeq( xFilePicker->getSelectedFiles() ); + INetURLObject aObj( aPathSeq[0] ); + m_xEdFilepath->set_text(aObj.PathToFileName()); + } +} + +IMPL_LINK(SvInsertOleDlg, RadioHdl, weld::Toggleable&, rButton, void) +{ + if (!rButton.get_active()) + return; + + if (m_xRbNewObject->get_active()) + { + m_xObjectTypeFrame->show(); + m_xFileFrame->hide(); + } + else + { + m_xFileFrame->show(); + m_xObjectTypeFrame->hide(); + } +} + +SvInsertOleDlg::SvInsertOleDlg(weld::Window* pParent, const Reference<embed::XStorage>& xStorage, + const SvObjectServerList* pServers) + : InsertObjectDialog_Impl( pParent, "cui/ui/insertoleobject.ui", "InsertOLEObjectDialog", xStorage) + , m_pServers( pServers ) + , m_xRbNewObject(m_xBuilder->weld_radio_button("createnew")) + , m_xRbObjectFromfile(m_xBuilder->weld_radio_button("createfromfile")) + , m_xObjectTypeFrame(m_xBuilder->weld_frame("objecttypeframe")) + , m_xLbObjecttype(m_xBuilder->weld_tree_view("types")) + , m_xFileFrame(m_xBuilder->weld_frame("fileframe")) + , m_xEdFilepath(m_xBuilder->weld_entry("urled")) + , m_xBtnFilepath(m_xBuilder->weld_button("urlbtn")) + , m_xCbFilelink(m_xBuilder->weld_check_button("linktofile")) + , m_xCbAsIcon(m_xBuilder->weld_check_button("asicon")) +{ + m_xLbObjecttype->set_size_request(m_xLbObjecttype->get_approximate_digit_width() * 32, + m_xLbObjecttype->get_height_rows(6)); + m_xLbObjecttype->connect_row_activated(LINK(this, SvInsertOleDlg, DoubleClickHdl)); + m_xBtnFilepath->connect_clicked(LINK( this, SvInsertOleDlg, BrowseHdl)); + Link<weld::Toggleable&,void> aLink( LINK( this, SvInsertOleDlg, RadioHdl ) ); + m_xRbNewObject->connect_toggled( aLink ); + m_xRbObjectFromfile->connect_toggled( aLink ); + m_xRbNewObject->set_active(true); +} + +short SvInsertOleDlg::run() +{ + short nRet = RET_OK; + SvObjectServerList aObjS; + if ( !m_pServers ) + { + // if no list was provided, take the complete one + aObjS.FillInsertObjects(); + m_pServers = &aObjS; + } + + // fill listbox and select default + m_xLbObjecttype->freeze(); + for ( size_t i = 0; i < m_pServers->Count(); i++ ) + m_xLbObjecttype->append_text((*m_pServers)[i].GetHumanName()); + m_xLbObjecttype->thaw(); + m_xLbObjecttype->select(0); + + DBG_ASSERT( m_xStorage.is(), "No storage!"); + if ( m_xStorage.is() && ( nRet = InsertObjectDialog_Impl::run() ) == RET_OK ) + { + OUString aFileName; + OUString aName; + bool bCreateNew = IsCreateNew(); + if ( bCreateNew ) + { + // create and insert new embedded object + OUString aServerName = m_xLbObjecttype->get_selected_text(); + const SvObjectServer* pS = m_pServers->Get( aServerName ); + if ( pS ) + { + if( pS->GetClassName() == SvGlobalName( SO3_OUT_CLASSID ) ) + { + try + { + uno::Reference < embed::XInsertObjectDialog > xDialogCreator( + embed::MSOLEObjectSystemCreator::create( ::comphelper::getProcessComponentContext() ), + uno::UNO_QUERY ); + + if ( xDialogCreator.is() ) + { + aName = aCnt.CreateUniqueObjectName(); + + uno::Reference<task::XStatusIndicator> xProgress; + OUString aProgressText; + SfxViewFrame* pFrame = SfxViewFrame::Current(); + if (pFrame) + { + // Have a current frame, create a matching progressbar, but don't start it yet. + uno::Reference<frame::XFrame> xFrame + = pFrame->GetFrame().GetFrameInterface(); + uno::Reference<task::XStatusIndicatorFactory> xProgressFactory( + xFrame, uno::UNO_QUERY); + if (xProgressFactory.is()) + { + xProgress = xProgressFactory->createStatusIndicator(); + if (xProgress) + { + aProgressText = CuiResId(RID_CUISTR_OLE_INSERT); + } + } + } + + const embed::InsertedObjectInfo aNewInf = xDialogCreator->createInstanceByDialog( + m_xStorage, + aName, + {comphelper::makePropertyValue("StatusIndicator", xProgress), + comphelper::makePropertyValue("StatusIndicatorText", aProgressText)} ); + + OSL_ENSURE( aNewInf.Object.is(), "The object must be created or an exception must be thrown!" ); + m_xObj = aNewInf.Object; + for ( const auto& opt : aNewInf.Options ) + if ( opt.Name == "Icon" ) + { + opt.Value >>= m_aIconMetaFile; + } + else if ( opt.Name == "IconFormat" ) + { + datatransfer::DataFlavor aFlavor; + if ( opt.Value >>= aFlavor ) + m_aIconMediaType = aFlavor.MimeType; + } + + } + } + catch( ucb::CommandAbortedException& ) + { + // the user has pressed cancel + } + catch( uno::Exception& ) + { + // TODO: Error handling + } + } + else + { + // create object with desired ClassId + m_xObj = aCnt.CreateEmbeddedObject( pS->GetClassName().GetByteSequence(), aName ); + } + + if ( !m_xObj.is() ) + { + if( !aFileName.isEmpty() ) // from OLE Dialog + { + // object couldn't be created from file + // global Resource from svtools (former so3 resource) + OUString aErr(SvtResId(STR_ERROR_OBJNOCREATE_FROM_FILE)); + aErr = aErr.replaceFirst( "%", aFileName ); + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, aErr)); + xBox->run(); + } + else + { + // object couldn't be created + // global Resource from svtools (former so3 resource) + OUString aErr(SvtResId(STR_ERROR_OBJNOCREATE)); + aErr = aErr.replaceFirst( "%", aServerName ); + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, aErr)); + xBox->run(); + } + } + } + } + else + { + aFileName = m_xEdFilepath->get_text(); + INetURLObject aURL; + aURL.SetSmartProtocol( INetProtocol::File ); + aURL.SetSmartURL( aFileName ); + aFileName = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + bool bLink = m_xCbFilelink->get_active(); + + if ( !aFileName.isEmpty() ) + { + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + uno::Reference< task::XInteractionHandler2 > xInteraction( + task::InteractionHandler::createWithParent(xContext, nullptr) ); + + // create MediaDescriptor for file to create object from + uno::Sequence < beans::PropertyValue > aMedium{ + comphelper::makePropertyValue("URL", aFileName), + comphelper::makePropertyValue("InteractionHandler", xInteraction) + }; + + // create object from media descriptor + + uno::Reference<task::XStatusIndicator> xProgress; + SfxViewFrame* pFrame = SfxViewFrame::Current(); + if (pFrame) + { + // Have a current frame, create visual indication that insert is in progress. + uno::Reference<frame::XFrame> xFrame = pFrame->GetFrame().GetFrameInterface(); + uno::Reference<task::XStatusIndicatorFactory> xProgressFactory(xFrame, uno::UNO_QUERY); + if (xProgressFactory.is()) + { + xProgress = xProgressFactory->createStatusIndicator(); + if (xProgress) + { + OUString aOleInsert(CuiResId(RID_CUISTR_OLE_INSERT)); + xProgress->start(aOleInsert, 100); + } + } + } + + if ( bLink ) + m_xObj = aCnt.InsertEmbeddedLink( aMedium, aName ); + else + m_xObj = aCnt.InsertEmbeddedObject( aMedium, aName ); + + if (xProgress.is()) + { + xProgress->end(); + } + } + + if ( !m_xObj.is() ) + { + // object couldn't be created from file + // global Resource from svtools (former so3 resource) + OUString aErr(SvtResId(STR_ERROR_OBJNOCREATE_FROM_FILE)); + aErr = aErr.replaceFirst( "%", aFileName ); + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, aErr)); + xBox->run(); + } + else + { + if (m_xCbAsIcon->get_active()) + { + //something nice here I guess would be to write the filename into + //the image with this icon above it + Image aImage = SvFileInformationManager::GetImage(aURL, true); + SvMemoryStream aTemp; + WriteDIBBitmapEx(aImage.GetBitmapEx(), aTemp); + m_aIconMetaFile = Sequence<sal_Int8>(static_cast<const sal_Int8*>(aTemp.GetData()), aTemp.TellEnd()); + m_aIconMediaType = "application/x-openoffice-bitmap;windows_formatname=\"Bitmap\""; + } + } + } + } + + m_pServers = nullptr; + return nRet; +} + +uno::Reference< io::XInputStream > SvInsertOleDlg::GetIconIfIconified( OUString* pGraphicMediaType ) +{ + if ( m_aIconMetaFile.hasElements() ) + { + if ( pGraphicMediaType ) + *pGraphicMediaType = m_aIconMediaType; + + return uno::Reference< io::XInputStream >( new ::comphelper::SequenceInputStream( m_aIconMetaFile ) ); + } + + return uno::Reference< io::XInputStream >(); +} + + +SfxInsertFloatingFrameDialog::SfxInsertFloatingFrameDialog(weld::Window *pParent, + const css::uno::Reference < css::embed::XStorage >& xStorage) + : InsertObjectDialog_Impl(pParent, "cui/ui/insertfloatingframe.ui", "InsertFloatingFrameDialog", + xStorage) +{ + Init(); +} + +SfxInsertFloatingFrameDialog::SfxInsertFloatingFrameDialog(weld::Window *pParent, + const uno::Reference < embed::XEmbeddedObject >& xObj) + : InsertObjectDialog_Impl(pParent, "cui/ui/insertfloatingframe.ui", "InsertFloatingFrameDialog", + uno::Reference<embed::XStorage>()) +{ + m_xObj = xObj; + + Init(); +} + +void SfxInsertFloatingFrameDialog::Init() +{ + m_xEDName = m_xBuilder->weld_entry("edname"); + m_xEDURL = m_xBuilder->weld_entry("edurl"); + m_xBTOpen = m_xBuilder->weld_button("buttonbrowse"); + m_xRBScrollingOn = m_xBuilder->weld_radio_button("scrollbaron"); + m_xRBScrollingOff = m_xBuilder->weld_radio_button("scrollbaroff"); + m_xRBScrollingAuto = m_xBuilder->weld_radio_button("scrollbarauto"); + m_xRBFrameBorderOn = m_xBuilder->weld_radio_button("borderon"); + m_xRBFrameBorderOff = m_xBuilder->weld_radio_button("borderoff"); + m_xFTMarginWidth = m_xBuilder->weld_label("widthlabel"); + m_xNMMarginWidth = m_xBuilder->weld_spin_button("width"); + m_xCBMarginWidthDefault = m_xBuilder->weld_check_button("defaultwidth"); + m_xFTMarginHeight = m_xBuilder->weld_label("heightlabel"); + m_xNMMarginHeight = m_xBuilder->weld_spin_button("height"); + m_xCBMarginHeightDefault = m_xBuilder->weld_check_button("defaultheight"); + + Link<weld::Toggleable&, void> aLink(LINK(this, SfxInsertFloatingFrameDialog, CheckHdl)); + m_xCBMarginWidthDefault->connect_toggled(aLink); + m_xCBMarginHeightDefault->connect_toggled(aLink); + + m_xCBMarginWidthDefault->set_active(true); + m_xCBMarginHeightDefault->set_active(true); + m_xRBScrollingAuto->set_active(true); + m_xRBFrameBorderOn->set_active(true); + + m_xBTOpen->connect_clicked(LINK(this, SfxInsertFloatingFrameDialog, OpenHdl)); +} + +short SfxInsertFloatingFrameDialog::run() +{ + short nRet = RET_OK; + bool bOK = false; + uno::Reference < beans::XPropertySet > xSet; + if ( m_xObj.is() ) + { + try + { + if ( m_xObj->getCurrentState() == embed::EmbedStates::LOADED ) + m_xObj->changeState( embed::EmbedStates::RUNNING ); + xSet.set( m_xObj->getComponent(), uno::UNO_QUERY ); + OUString aStr; + uno::Any aAny = xSet->getPropertyValue( "FrameURL" ); + if ( aAny >>= aStr ) + m_xEDURL->set_text( aStr ); + aAny = xSet->getPropertyValue( "FrameName" ); + if ( aAny >>= aStr ) + m_xEDName->set_text(aStr); + + sal_Int32 nSize = SIZE_NOT_SET; + aAny = xSet->getPropertyValue( "FrameMarginWidth" ); + aAny >>= nSize; + + if ( nSize == SIZE_NOT_SET ) + { + m_xCBMarginWidthDefault->set_active(true); + m_xNMMarginWidth->set_text(OUString::number(DEFAULT_MARGIN_WIDTH)); + m_xFTMarginWidth->set_sensitive(false); + m_xNMMarginWidth->set_sensitive(false); + } + else + m_xNMMarginWidth->set_text(OUString::number(nSize)); + + aAny = xSet->getPropertyValue( "FrameMarginHeight" ); + aAny >>= nSize; + + if ( nSize == SIZE_NOT_SET ) + { + m_xCBMarginHeightDefault->set_active(true); + m_xNMMarginHeight->set_text(OUString::number(DEFAULT_MARGIN_HEIGHT)); + m_xFTMarginHeight->set_sensitive(false); + m_xNMMarginHeight->set_sensitive(false); + } + else + m_xNMMarginHeight->set_text(OUString::number(nSize)); + + bool bScrollOn = false; + bool bScrollOff = false; + bool bScrollAuto = false; + + bool bSet = false; + aAny = xSet->getPropertyValue( "FrameIsAutoScroll" ); + aAny >>= bSet; + if ( !bSet ) + { + aAny = xSet->getPropertyValue( "FrameIsScrollingMode" ); + aAny >>= bSet; + bScrollOn = bSet; + bScrollOff = !bSet; + } + else + bScrollAuto = true; + + m_xRBScrollingOn->set_sensitive(bScrollOn); + m_xRBScrollingOff->set_sensitive(bScrollOff); + m_xRBScrollingAuto->set_sensitive(bScrollAuto); + + bSet = false; + aAny = xSet->getPropertyValue( "FrameIsAutoBorder" ); + aAny >>= bSet; + if ( !bSet ) + { + aAny = xSet->getPropertyValue( "FrameIsBorder" ); + aAny >>= bSet; + m_xRBFrameBorderOn->set_active(bSet); + m_xRBFrameBorderOff->set_active(!bSet); + } + + bOK = true; + } + catch ( uno::Exception& ) + { + OSL_FAIL( "No IFrame!" ); + } + } + else + { + DBG_ASSERT( m_xStorage.is(), "No storage!"); + bOK = m_xStorage.is(); + } + + if (!bOK) + return RET_OK; + + nRet = InsertObjectDialog_Impl::run(); + if ( nRet == RET_OK ) + { + OUString aURL; + if (!m_xEDURL->get_text().isEmpty()) + { + // URL can be a valid and absolute URL or a system file name + INetURLObject aObj; + aObj.SetSmartProtocol( INetProtocol::File ); + if ( aObj.SetSmartURL( m_xEDURL->get_text() ) ) + aURL = aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + + if ( !m_xObj.is() && !aURL.isEmpty() ) + { + // create the object + OUString aName; + SvGlobalName aClassId( SO3_IFRAME_CLASSID ); + m_xObj = aCnt.CreateEmbeddedObject( aClassId.GetByteSequence(), aName ); + if ( m_xObj->getCurrentState() == embed::EmbedStates::LOADED ) + m_xObj->changeState( embed::EmbedStates::RUNNING ); + xSet.set( m_xObj->getComponent(), uno::UNO_QUERY ); + } + + if ( m_xObj.is() ) + { + try + { + bool bIPActive = m_xObj->getCurrentState() == embed::EmbedStates::INPLACE_ACTIVE; + if ( bIPActive ) + m_xObj->changeState( embed::EmbedStates::RUNNING ); + + OUString aName = m_xEDName->get_text(); + ScrollingMode eScroll = ScrollingMode::No; + if (m_xRBScrollingOn->get_active()) + eScroll = ScrollingMode::Yes; + if (m_xRBScrollingOff->get_active()) + eScroll = ScrollingMode::No; + if (m_xRBScrollingAuto->get_active()) + eScroll = ScrollingMode::Auto; + + bool bHasBorder = m_xRBFrameBorderOn->get_active(); + + tools::Long lMarginWidth; + if (!m_xCBMarginWidthDefault->get_active()) + lMarginWidth = static_cast<tools::Long>(m_xNMMarginWidth->get_text().toInt32()); + else + lMarginWidth = SIZE_NOT_SET; + + tools::Long lMarginHeight; + if (!m_xCBMarginHeightDefault->get_active()) + lMarginHeight = static_cast<tools::Long>(m_xNMMarginHeight->get_text().toInt32()); + else + lMarginHeight = SIZE_NOT_SET; + + xSet->setPropertyValue( "FrameURL", Any( aURL ) ); + xSet->setPropertyValue( "FrameName", Any( aName ) ); + + if ( eScroll == ScrollingMode::Auto ) + xSet->setPropertyValue( "FrameIsAutoScroll", Any( true ) ); + else + xSet->setPropertyValue( "FrameIsScrollingMode", Any( eScroll == ScrollingMode::Yes ) ); + + xSet->setPropertyValue( "FrameIsBorder", Any( bHasBorder ) ); + xSet->setPropertyValue( "FrameMarginWidth", Any( sal_Int32( lMarginWidth ) ) ); + xSet->setPropertyValue( "FrameMarginHeight", Any( sal_Int32( lMarginHeight ) ) ); + + if ( bIPActive ) + m_xObj->changeState( embed::EmbedStates::INPLACE_ACTIVE ); + } + catch ( uno::Exception& ) + { + OSL_FAIL( "No IFrame!" ); + } + } + } + + return nRet; +} + +IMPL_LINK(SfxInsertFloatingFrameDialog, CheckHdl, weld::Toggleable&, rButton, void) +{ + weld::CheckButton& rCB = dynamic_cast<weld::CheckButton&>(rButton); + if (&rCB == m_xCBMarginWidthDefault.get()) + { + if (rCB.get_active()) + m_xNMMarginWidth->set_text(OUString::number(DEFAULT_MARGIN_WIDTH)); + m_xFTMarginWidth->set_sensitive(!rCB.get_active()); + m_xNMMarginWidth->set_sensitive(!rCB.get_active()); + } + + if (&rCB == m_xCBMarginHeightDefault.get()) + { + if (rCB.get_active()) + m_xNMMarginHeight->set_text(OUString::number(DEFAULT_MARGIN_HEIGHT)); + m_xFTMarginHeight->set_sensitive(!rCB.get_active()); + m_xNMMarginHeight->set_sensitive(!rCB.get_active()); + } +} + +IMPL_LINK_NOARG( SfxInsertFloatingFrameDialog, OpenHdl, weld::Button&, void) +{ + // create the file dialog + sfx2::FileDialogHelper aFileDlg( + ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE, FileDialogFlags::NONE, OUString(), + SfxFilterFlags::NONE, SfxFilterFlags::NONE, m_xDialog.get()); + + // set the title + aFileDlg.SetTitle(CuiResId(RID_CUISTR_SELECT_FILE_IFRAME)); + + // show the dialog + if ( aFileDlg.Execute() == ERRCODE_NONE ) + m_xEDURL->set_text(INetURLObject(aFileDlg.GetPath()).GetMainURL(INetURLObject::DecodeMechanism::WithCharset)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/insrc.cxx b/cui/source/dialogs/insrc.cxx new file mode 100644 index 000000000..bec311442 --- /dev/null +++ b/cui/source/dialogs/insrc.cxx @@ -0,0 +1,59 @@ +/* -*- 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 <dialmgr.hxx> +#include <strings.hrc> +#include <insrc.hxx> + +bool SvxInsRowColDlg::isInsertBefore() const +{ + return !m_xAfterBtn->get_active(); +} + +sal_uInt16 SvxInsRowColDlg::getInsertCount() const +{ + return m_xCountEdit->get_value(); +} + +SvxInsRowColDlg::SvxInsRowColDlg(weld::Window* pParent, bool bColumn, const OString& rHelpId) + : GenericDialogController(pParent, "cui/ui/insertrowcolumn.ui", "InsertRowColumnDialog") + , m_xCountEdit(m_xBuilder->weld_spin_button("insert_number")) + , m_xBeforeBtn(m_xBuilder->weld_radio_button("insert_before")) + , m_xAfterBtn(m_xBuilder->weld_radio_button("insert_after")) +{ + m_xDialog->set_title(bColumn ? CuiResId(RID_CUISTR_COL) : CuiResId(RID_CUISTR_ROW)); + + // tdf#119293 + if (bColumn) { + m_xBeforeBtn->set_label(CuiResId(RID_CUISTR_INSERTCOL_BEFORE)); + m_xAfterBtn->set_label(CuiResId(RID_CUISTR_INSERTCOL_AFTER)); + } else { + m_xBeforeBtn->set_label(CuiResId(RID_CUISTR_INSERTROW_BEFORE)); + m_xAfterBtn->set_label(CuiResId(RID_CUISTR_INSERTROW_AFTER)); + } + + m_xDialog->set_help_id(rHelpId); +} + +short SvxInsRowColDlg::Execute() +{ + return run(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/linkdlg.cxx b/cui/source/dialogs/linkdlg.cxx new file mode 100644 index 000000000..055721d73 --- /dev/null +++ b/cui/source/dialogs/linkdlg.cxx @@ -0,0 +1,644 @@ +/* -*- 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 <linkdlg.hxx> +#include <o3tl/safeint.hxx> +#include <vcl/svapp.hxx> + +#include <tools/diagnose_ex.h> +#include <tools/debug.hxx> +#include <tools/urlobj.hxx> +#include <vcl/idle.hxx> +#include <vcl/timer.hxx> +#include <vcl/weld.hxx> +#include <vcl/weldutils.hxx> + +#include <strings.hrc> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/linkmgr.hxx> +#include <sfx2/linksrc.hxx> +#include <sfx2/lnkbase.hxx> +#include <sfx2/objsh.hxx> + +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <com/sun/star/ui/dialogs/XFolderPicker2.hpp> +#include <comphelper/processfactory.hxx> + +#include <dialmgr.hxx> + + +using namespace sfx2; +using namespace ::com::sun::star; + +namespace { + +class SvBaseLinkMemberList { +private: + std::vector<SvBaseLink*> mLinks; + +public: + ~SvBaseLinkMemberList() + { + for (auto const& link : mLinks) + { + if( link ) + link->ReleaseRef(); + } + } + + size_t size() const { return mLinks.size(); } + + SvBaseLink *operator[](size_t i) const { return mLinks[i]; } + + void push_back(SvBaseLink* p) + { + mLinks.push_back(p); + p->AddFirstRef(); + } +}; + +} + +SvBaseLinksDlg::SvBaseLinksDlg(weld::Window * pParent, LinkManager* pMgr, bool bHtmlMode) + : GenericDialogController(pParent, "cui/ui/baselinksdialog.ui", "BaseLinksDialog") + , aStrAutolink( CuiResId( STR_AUTOLINK ) ) + , aStrManuallink( CuiResId( STR_MANUALLINK ) ) + , aStrBrokenlink( CuiResId( STR_BROKENLINK ) ) + , aStrCloselinkmsg( CuiResId( STR_CLOSELINKMSG ) ) + , aStrCloselinkmsgMulti( CuiResId( STR_CLOSELINKMSG_MULTI ) ) + , aStrWaitinglink( CuiResId( STR_WAITINGLINK ) ) + , pLinkMgr( nullptr ) + , aUpdateIdle("cui SvBaseLinksDlg UpdateIdle") + , m_xTbLinks(m_xBuilder->weld_tree_view("TB_LINKS")) + , m_xFtFullFileName(m_xBuilder->weld_link_button("FULL_FILE_NAME")) + , m_xFtFullSourceName(m_xBuilder->weld_label("FULL_SOURCE_NAME")) + , m_xFtFullTypeName(m_xBuilder->weld_label("FULL_TYPE_NAME")) + , m_xRbAutomatic(m_xBuilder->weld_radio_button("AUTOMATIC")) + , m_xRbManual(m_xBuilder->weld_radio_button("MANUAL")) + , m_xPbUpdateNow(m_xBuilder->weld_button("UPDATE_NOW")) + , m_xPbChangeSource(m_xBuilder->weld_button("CHANGE_SOURCE")) + , m_xPbBreakLink(m_xBuilder->weld_button("BREAK_LINK")) + , m_xVirDev(VclPtr<VirtualDevice>::Create()) +{ + // expand the point size of the desired font to the equivalent pixel size + weld::SetPointFont(*m_xVirDev, m_xTbLinks->get_font()); + m_xTbLinks->set_size_request(m_xTbLinks->get_approximate_digit_width() * 90, + m_xTbLinks->get_height_rows(12)); + + m_xTbLinks->set_selection_mode(SelectionMode::Multiple); + + std::vector<int> aWidths + { + o3tl::narrowing<int>(m_xTbLinks->get_approximate_digit_width() * 30), + o3tl::narrowing<int>(m_xTbLinks->get_approximate_digit_width() * 20), + o3tl::narrowing<int>(m_xTbLinks->get_approximate_digit_width() * 20) + }; + m_xTbLinks->set_column_fixed_widths(aWidths); + + // UpdateTimer for DDE-/Grf-links, which are waited for + aUpdateIdle.SetInvokeHandler( LINK( this, SvBaseLinksDlg, UpdateWaitingHdl ) ); + aUpdateIdle.SetPriority( TaskPriority::LOWEST ); + + m_xTbLinks->connect_changed( LINK( this, SvBaseLinksDlg, LinksSelectHdl ) ); + m_xTbLinks->connect_row_activated( LINK( this, SvBaseLinksDlg, LinksDoubleClickHdl ) ); + m_xRbAutomatic->connect_toggled( LINK( this, SvBaseLinksDlg, ToggleHdl ) ); + m_xRbManual->connect_toggled( LINK( this, SvBaseLinksDlg, ToggleHdl ) ); + m_xPbUpdateNow->connect_clicked( LINK( this, SvBaseLinksDlg, UpdateNowClickHdl ) ); + m_xPbChangeSource->connect_clicked( LINK( this, SvBaseLinksDlg, ChangeSourceClickHdl ) ); + if(!bHtmlMode) + m_xPbBreakLink->connect_clicked( LINK( this, SvBaseLinksDlg, BreakLinkClickHdl ) ); + else + m_xPbBreakLink->hide(); + + SetManager( pMgr ); +} + +SvBaseLinksDlg::~SvBaseLinksDlg() +{ +} + +/************************************************************************* +|* SvBaseLinksDlg::Handler() +*************************************************************************/ +IMPL_LINK(SvBaseLinksDlg, LinksSelectHdl, weld::TreeView&, rTreeView, void) +{ + LinksSelectHdl(&rTreeView); +} + +void SvBaseLinksDlg::LinksSelectHdl(weld::TreeView* pSvTabListBox) +{ + const int nSelectionCount = pSvTabListBox ? + pSvTabListBox->count_selected_rows() : 0; + if (nSelectionCount > 1) + { + // possibly deselect old entries in case of multi-selection + int nSelEntry = pSvTabListBox->get_selected_index(); + SvBaseLink* pLink = weld::fromId<SvBaseLink*>(pSvTabListBox->get_id(nSelEntry)); + SvBaseLinkObjectType nObjectType = pLink->GetObjType(); + if(!isClientFileType(nObjectType)) + { + pSvTabListBox->unselect_all(); + pSvTabListBox->select(nSelEntry); + } + else + { + std::vector<int> aRows = pSvTabListBox->get_selected_rows(); + for (auto nEntry : aRows) + { + pLink = weld::fromId<SvBaseLink*>(pSvTabListBox->get_id(nEntry)); + DBG_ASSERT(pLink, "Where is the Link?"); + if (!pLink) + continue; + if( !isClientFileType(pLink->GetObjType()) ) + pSvTabListBox->unselect(nEntry); + } + } + + m_xPbUpdateNow->set_sensitive(true); + m_xRbAutomatic->set_sensitive(false); + m_xRbManual->set_active(true); + m_xRbManual->set_sensitive(false); + } + else + { + int nPos; + SvBaseLink* pLink = GetSelEntry( &nPos ); + if( !pLink ) + return; + + m_xPbUpdateNow->set_sensitive(true); + + OUString sType, sLink; + OUString *pLinkNm = &sLink, *pFilter = nullptr; + + if( isClientFileType(pLink->GetObjType()) ) + { + m_xRbAutomatic->set_sensitive(false); + m_xRbManual->set_active(true); + m_xRbManual->set_sensitive(false); + if( SvBaseLinkObjectType::ClientGraphic == pLink->GetObjType() ) + { + pLinkNm = nullptr; + pFilter = &sLink; + } + } + else + { + m_xRbAutomatic->set_sensitive(true); + m_xRbManual->set_sensitive(true); + + if( SfxLinkUpdateMode::ALWAYS == pLink->GetUpdateMode() ) + m_xRbAutomatic->set_active(true); + else + m_xRbManual->set_active(true); + } + + OUString aFileName; + sfx2::LinkManager::GetDisplayNames( pLink, &sType, &aFileName, pLinkNm, pFilter ); + aFileName = INetURLObject::decode(aFileName, INetURLObject::DecodeMechanism::Unambiguous); + m_xFtFullFileName->set_label( aFileName ); + m_xFtFullFileName->set_uri( aFileName ); + m_xFtFullSourceName->set_label( sLink ); + m_xFtFullTypeName->set_label( sType ); + } +} + +IMPL_LINK_NOARG( SvBaseLinksDlg, LinksDoubleClickHdl, weld::TreeView&, bool ) +{ + ChangeSourceClickHdl(*m_xPbChangeSource); + return true; +} + +IMPL_LINK(SvBaseLinksDlg, ToggleHdl, weld::Toggleable&, rButton, void) +{ + if (!rButton.get_active()) + return; + + int nPos; + SvBaseLink* pLink = GetSelEntry( &nPos ); + + if (m_xRbAutomatic->get_active()) + { + if( pLink && !isClientFileType( pLink->GetObjType() ) && + SfxLinkUpdateMode::ALWAYS != pLink->GetUpdateMode() ) + SetType( *pLink, nPos, SfxLinkUpdateMode::ALWAYS ); + } + else + { + if( pLink && !isClientFileType( pLink->GetObjType() ) && + SfxLinkUpdateMode::ONCALL != pLink->GetUpdateMode()) + SetType( *pLink, nPos, SfxLinkUpdateMode::ONCALL ); + } +} + +IMPL_LINK_NOARG(SvBaseLinksDlg, UpdateNowClickHdl, weld::Button&, void) +{ + std::vector< SvBaseLink* > aLnkArr; + std::vector< sal_Int16 > aPosArr; + + std::vector<int> aRows = m_xTbLinks->get_selected_rows(); + for (int nFndPos : aRows) + { + aLnkArr.push_back( weld::fromId<SvBaseLink*>( m_xTbLinks->get_id(nFndPos) ) ); + aPosArr.push_back( nFndPos ); + } + + if( aLnkArr.empty() ) + return; + + for( size_t n = 0; n < aLnkArr.size(); ++n ) + { + tools::SvRef<SvBaseLink> xLink = aLnkArr[ n ]; + + // first look for the entry in the array + for(const auto & i : pLinkMgr->GetLinks()) + if( xLink == i ) + { + SetType( *xLink, aPosArr[ n ], xLink->GetUpdateMode() ); + break; + } + } + + // if somebody is of the opinion to swap his links (SD) + LinkManager* pNewMgr = pLinkMgr; + pLinkMgr = nullptr; + SetManager( pNewMgr ); + + + OUString sId = weld::toId(aLnkArr[0]); + int nE = m_xTbLinks->find_id(sId); + if (nE == -1) + nE = m_xTbLinks->get_selected_index(); + int nSelEntry = m_xTbLinks->get_selected_index(); + if (nE != nSelEntry) + m_xTbLinks->unselect(nSelEntry); + m_xTbLinks->select(nE); + m_xTbLinks->scroll_to_row(nE); + + pNewMgr->CloseCachedComps(); +} + +IMPL_LINK_NOARG(SvBaseLinksDlg, ChangeSourceClickHdl, weld::Button&, void) +{ + std::vector<int> aRows = m_xTbLinks->get_selected_rows(); + if (aRows.size() > 1) + { + try + { + uno::Reference<ui::dialogs::XFolderPicker2> xFolderPicker = sfx2::createFolderPicker( + comphelper::getProcessComponentContext(), m_xDialog.get()); + + OUString sType, sFile, sLinkName; + OUString sFilter; + SvBaseLink* pLink = weld::fromId<SvBaseLink*>(m_xTbLinks->get_id(aRows[0])); + sfx2::LinkManager::GetDisplayNames( pLink, &sType, &sFile ); + INetURLObject aUrl(sFile); + if(aUrl.GetProtocol() == INetProtocol::File) + { + OUString sOldPath(aUrl.PathToFileName()); + sal_Int32 nLen = aUrl.GetLastName().getLength(); + sOldPath = sOldPath.copy(0, sOldPath.getLength() - nLen); + xFolderPicker->setDisplayDirectory(sOldPath); + } + if (xFolderPicker->execute() == ui::dialogs::ExecutableDialogResults::OK) + { + OUString aPath = xFolderPicker->getDirectory(); + + for (auto nRow : aRows) + { + pLink = weld::fromId<SvBaseLink*>(m_xTbLinks->get_id(nRow)); + DBG_ASSERT(pLink,"Where is the link?"); + if (!pLink) + continue; + sfx2::LinkManager::GetDisplayNames( pLink, &sType, &sFile, &sLinkName, &sFilter ); + INetURLObject aUrl_(sFile); + INetURLObject aUrl2(aPath, INetProtocol::File); + aUrl2.insertName( aUrl_.getName() ); + OUString sNewLinkName; + MakeLnkName( sNewLinkName, nullptr , + aUrl2.GetMainURL(INetURLObject::DecodeMechanism::ToIUri), sLinkName, &sFilter); + pLink->SetLinkSourceName( sNewLinkName ); + pLink->Update(); + } + if( pLinkMgr->GetPersist() ) + pLinkMgr->GetPersist()->SetModified(); + LinkManager* pNewMgr = pLinkMgr; + pLinkMgr = nullptr; + SetManager( pNewMgr ); + } + } + catch (const uno::Exception &) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", "SvBaseLinksDlg"); + } + } + else + { + int nPos; + SvBaseLink* pLink = GetSelEntry( &nPos ); + if ( pLink && !pLink->GetLinkSourceName().isEmpty() ) + pLink->Edit(m_xDialog.get(), LINK(this, SvBaseLinksDlg, EndEditHdl)); + } +} + +IMPL_LINK_NOARG( SvBaseLinksDlg, BreakLinkClickHdl, weld::Button&, void ) +{ + bool bModified = false; + if (m_xTbLinks->count_selected_rows() <= 1) + { + int nPos; + tools::SvRef<SvBaseLink> xLink = GetSelEntry( &nPos ); + if( !xLink.is() ) + return; + + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Question, VclButtonsType::YesNo, + aStrCloselinkmsg)); + xQueryBox->set_default_response(RET_YES); + + if (RET_YES == xQueryBox->run()) + { + m_xTbLinks->remove(nPos); + + // close object, if it's still existing + bool bNewLnkMgr = SvBaseLinkObjectType::ClientFile == xLink->GetObjType(); + + // tell the link that it will be resolved! + xLink->Closed(); + + // if somebody has forgotten to deregister himself + if( xLink.is() ) + pLinkMgr->Remove( xLink.get() ); + + if( bNewLnkMgr ) + { + LinkManager* pNewMgr = pLinkMgr; + pLinkMgr = nullptr; + SetManager( pNewMgr ); + m_xTbLinks->set_cursor(nPos ? --nPos : 0); + } + bModified = true; + } + } + else + { + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Question, VclButtonsType::YesNo, + aStrCloselinkmsgMulti)); + xQueryBox->set_default_response(RET_YES); + + if (RET_YES == xQueryBox->run()) + { + std::vector<int> aRows = m_xTbLinks->get_selected_rows(); + SvBaseLinkMemberList aLinkList; + for (auto nRow : aRows) + { + SvBaseLink* pLink = weld::fromId<SvBaseLink*>(m_xTbLinks->get_id(nRow)); + if (pLink) + aLinkList.push_back(pLink); + } + std::sort(aRows.begin(), aRows.end()); + for (auto it = aRows.rbegin(); it != aRows.rend(); ++it) + m_xTbLinks->remove(*it); + for (size_t i = 0; i < aLinkList.size(); ++i) + { + tools::SvRef<SvBaseLink> xLink = aLinkList[i]; + // tell the link that it will be resolved! + xLink->Closed(); + + // if somebody has forgotten to deregister himself + pLinkMgr->Remove( xLink.get() ); + bModified = true; + } + // then remove all selected entries + } + } + if(!bModified) + return; + + if (!m_xTbLinks->n_children()) + { + m_xRbAutomatic->set_sensitive(false); + m_xRbManual->set_sensitive(false); + m_xPbUpdateNow->set_sensitive(false); + m_xPbChangeSource->set_sensitive(false); + m_xPbBreakLink->set_sensitive(false); + + m_xFtFullSourceName->set_label( "" ); + m_xFtFullTypeName->set_label( "" ); + } + if( pLinkMgr && pLinkMgr->GetPersist() ) + pLinkMgr->GetPersist()->SetModified(); +} + +IMPL_LINK_NOARG( SvBaseLinksDlg, UpdateWaitingHdl, Timer*, void ) +{ + m_xTbLinks->freeze(); + for (int nPos = m_xTbLinks->n_children(); nPos; --nPos) + { + tools::SvRef<SvBaseLink> xLink( weld::fromId<SvBaseLink*>(m_xTbLinks->get_id(nPos)) ); + if( xLink.is() ) + { + OUString sCur( ImplGetStateStr( *xLink ) ), + sOld( m_xTbLinks->get_text(nPos, 3) ); + if( sCur != sOld ) + m_xTbLinks->set_text(nPos, sCur, 3); + } + } + m_xTbLinks->thaw(); +} + +IMPL_LINK( SvBaseLinksDlg, EndEditHdl, sfx2::SvBaseLink&, _rLink, void ) +{ + int nPos; + GetSelEntry( &nPos ); + + if( !_rLink.WasLastEditOK() ) + return; + + // StarImpress/Draw swap the LinkObjects themselves! + // So search for the link in the manager; if it does not exist + // anymore, fill the list completely new. Otherwise only the + // edited link needs to be refreshed. + bool bLinkFnd = false; + for( size_t n = pLinkMgr->GetLinks().size(); n; ) + if( &_rLink == &(*pLinkMgr->GetLinks()[ --n ]) ) + { + bLinkFnd = true; + break; + } + + if( bLinkFnd ) + { + m_xTbLinks->remove(nPos); + int nToUnselect = m_xTbLinks->get_selected_index(); + InsertEntry(_rLink, nPos, true); + if (nToUnselect != -1) + m_xTbLinks->unselect(nToUnselect); + } + else + { + LinkManager* pNewMgr = pLinkMgr; + pLinkMgr = nullptr; + SetManager( pNewMgr ); + } + if (pLinkMgr && pLinkMgr->GetPersist()) + pLinkMgr->GetPersist()->SetModified(); +} + +OUString SvBaseLinksDlg::ImplGetStateStr( const SvBaseLink& rLnk ) +{ + OUString sRet; + if( !rLnk.GetObj() ) + sRet = aStrBrokenlink; + else if( rLnk.GetObj()->IsPending() ) + { + sRet = aStrWaitinglink; + aUpdateIdle.Start(); + } + else if( SfxLinkUpdateMode::ALWAYS == rLnk.GetUpdateMode() ) + sRet = aStrAutolink; + else + sRet = aStrManuallink; + + return sRet; +} + +void SvBaseLinksDlg::SetManager( LinkManager* pNewMgr ) +{ + if( pLinkMgr == pNewMgr ) + return; + + if (pNewMgr) + { + // update has to be stopped before clear + m_xTbLinks->freeze(); + } + + m_xTbLinks->clear(); + pLinkMgr = pNewMgr; + + if( !pLinkMgr ) + return; + + SvBaseLinks& rLnks = const_cast<SvBaseLinks&>(pLinkMgr->GetLinks()); + for( size_t n = 0; n < rLnks.size(); ++n ) + { + tools::SvRef<SvBaseLink>& rLinkRef = rLnks[ n ]; + if( !rLinkRef.is() ) + { + rLnks.erase( rLnks.begin() + n ); + --n; + continue; + } + if( rLinkRef->IsVisible() ) + InsertEntry( *rLinkRef ); + } + + m_xTbLinks->thaw(); + + if( !rLnks.empty() ) + { + m_xTbLinks->set_cursor(0); + m_xTbLinks->select(0); + LinksSelectHdl( nullptr ); + } +} + +void SvBaseLinksDlg::InsertEntry(const SvBaseLink& rLink, int nPos, bool bSelect) +{ + OUString sFileNm, sLinkNm, sTypeNm, sFilter; + + sfx2::LinkManager::GetDisplayNames( &rLink, &sTypeNm, &sFileNm, &sLinkNm, &sFilter ); + + auto nWidthPixel = m_xTbLinks->get_column_width(0); + OUString aTxt = m_xVirDev->GetEllipsisString(sFileNm, nWidthPixel, DrawTextFlags::PathEllipsis); + INetURLObject aPath( sFileNm, INetProtocol::File ); + OUString aFileName = aPath.getName( + INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::Unambiguous); + + if( aFileName.getLength() > aTxt.getLength() ) + aTxt = aFileName; + else if (!aFileName.isEmpty() && aTxt.indexOf(aFileName, aTxt.getLength() - aFileName.getLength()) == -1) + // filename not in string + aTxt = aFileName; + + if (nPos == -1) + nPos = m_xTbLinks->n_children(); + m_xTbLinks->insert(nPos); + m_xTbLinks->set_text(nPos, aTxt, 0); + m_xTbLinks->set_id(nPos, weld::toId(&rLink)); + if( SvBaseLinkObjectType::ClientGraphic == rLink.GetObjType() ) + m_xTbLinks->set_text(nPos, sFilter, 1); + else + m_xTbLinks->set_text(nPos, sLinkNm, 1); + m_xTbLinks->set_text(nPos, sTypeNm, 2); + m_xTbLinks->set_text(nPos, ImplGetStateStr(rLink), 3); + if (bSelect) + m_xTbLinks->select(nPos); +} + +SvBaseLink* SvBaseLinksDlg::GetSelEntry(int* pPos) +{ + int nPos = m_xTbLinks->get_selected_index(); + if (nPos != -1) + { + if (pPos) + *pPos = nPos; + return weld::fromId<SvBaseLink*>(m_xTbLinks->get_id(nPos)); + } + return nullptr; +} + +void SvBaseLinksDlg::SetType(SvBaseLink& rLink, + int nSelPos, + SfxLinkUpdateMode nType) +{ + rLink.SetUpdateMode( nType ); + rLink.Update(); + m_xTbLinks->set_text(nSelPos, ImplGetStateStr(rLink), 3); + if (pLinkMgr->GetPersist()) + pLinkMgr->GetPersist()->SetModified(); +} + +void SvBaseLinksDlg::SetActLink( SvBaseLink const * pLink ) +{ + if( !pLinkMgr ) + return; + + const SvBaseLinks& rLnks = pLinkMgr->GetLinks(); + int nSelect = 0; + for(const auto & rLinkRef : rLnks) + { + // #109573# only visible links have been inserted into the TreeListBox, + // invisible ones have to be skipped here + if( rLinkRef->IsVisible() ) + { + if( pLink == rLinkRef.get() ) + { + m_xTbLinks->select(nSelect); + LinksSelectHdl( nullptr ); + return ; + } + ++nSelect; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/multipat.cxx b/cui/source/dialogs/multipat.cxx new file mode 100644 index 000000000..085d21f99 --- /dev/null +++ b/cui/source/dialogs/multipat.cxx @@ -0,0 +1,316 @@ +/* -*- 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 <sal/config.h> + +#include <osl/file.hxx> +#include <sfx2/filedlghelper.hxx> +#include <tools/urlobj.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +#include <multipat.hxx> +#include <dialmgr.hxx> + +#include <strings.hrc> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/ui/dialogs/XFolderPicker2.hpp> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> + +#include <unotools/pathoptions.hxx> +#include <o3tl/string_view.hxx> + +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::ui::dialogs; +using namespace ::com::sun::star::uno; + +IMPL_LINK_NOARG(SvxMultiPathDialog, SelectHdl_Impl, weld::TreeView&, void) +{ + auto nCount = m_xRadioLB->n_children(); + bool bIsSelected = m_xRadioLB->get_selected_index() != -1; + bool bEnable = nCount > 1; + m_xDelBtn->set_sensitive(bEnable && bIsSelected); +} + +IMPL_LINK_NOARG(SvxPathSelectDialog, SelectHdl_Impl, weld::TreeView&, void) +{ + auto nCount = m_xPathLB->n_children(); + bool bIsSelected = m_xPathLB->get_selected_index() != -1; + bool bEnable = nCount > 1; + m_xDelBtn->set_sensitive(bEnable && bIsSelected); +} + +void SvxMultiPathDialog::HandleEntryChecked(int nRow) +{ + m_xRadioLB->select(nRow); + bool bChecked = m_xRadioLB->get_toggle(nRow) == TRISTATE_TRUE; + if (bChecked) + { + // we have radio button behavior -> so uncheck the other entries + int nCount = m_xRadioLB->n_children(); + for (int i = 0; i < nCount; ++i) + { + if (i != nRow) + m_xRadioLB->set_toggle(i, TRISTATE_FALSE); + } + } +} + +IMPL_LINK(SvxMultiPathDialog, CheckHdl_Impl, const weld::TreeView::iter_col&, rRowCol, void) +{ + HandleEntryChecked(m_xRadioLB->get_iter_index_in_parent(rRowCol.first)); +} + +void SvxMultiPathDialog::AppendEntry(const OUString& rText, const OUString& rId) +{ + m_xRadioLB->append(); + const int nRow = m_xRadioLB->n_children() - 1; + m_xRadioLB->set_toggle(nRow, TRISTATE_FALSE); + m_xRadioLB->set_text(nRow, rText, 0); + m_xRadioLB->set_id(nRow, rId); +} + +IMPL_LINK_NOARG(SvxMultiPathDialog, AddHdl_Impl, weld::Button&, void) +{ + Reference < XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + Reference < XFolderPicker2 > xFolderPicker = sfx2::createFolderPicker(xContext, m_xDialog.get()); + + if ( xFolderPicker->execute() != ExecutableDialogResults::OK ) + return; + + INetURLObject aPath( xFolderPicker->getDirectory() ); + aPath.removeFinalSlash(); + OUString aURL = aPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + OUString sInsPath; + osl::FileBase::getSystemPathFromFileURL(aURL, sInsPath); + + if (m_xRadioLB->find_text(sInsPath) != -1) + { + OUString sMsg( CuiResId( RID_MULTIPATH_DBL_ERR ) ); + sMsg = sMsg.replaceFirst( "%1", sInsPath ); + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Info, VclButtonsType::Ok, sMsg)); + xInfoBox->run(); + } + else + { + AppendEntry(sInsPath, aURL); + } + + SelectHdl_Impl(*m_xRadioLB); +} + +IMPL_LINK_NOARG(SvxPathSelectDialog, AddHdl_Impl, weld::Button&, void) +{ + Reference < XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + Reference < XFolderPicker2 > xFolderPicker = sfx2::createFolderPicker(xContext, m_xDialog.get()); + + if ( xFolderPicker->execute() != ExecutableDialogResults::OK ) + return; + + INetURLObject aPath( xFolderPicker->getDirectory() ); + aPath.removeFinalSlash(); + OUString aURL = aPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + OUString sInsPath; + osl::FileBase::getSystemPathFromFileURL(aURL, sInsPath); + + if (m_xPathLB->find_text(sInsPath) != -1) + { + OUString sMsg( CuiResId( RID_MULTIPATH_DBL_ERR ) ); + sMsg = sMsg.replaceFirst( "%1", sInsPath ); + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Info, VclButtonsType::Ok, sMsg)); + xInfoBox->run(); + } + else + { + m_xPathLB->append(aURL, sInsPath); + } + + SelectHdl_Impl(*m_xPathLB); +} + +IMPL_LINK_NOARG(SvxMultiPathDialog, DelHdl_Impl, weld::Button&, void) +{ + int nPos = m_xRadioLB->get_selected_index(); + bool bChecked = m_xRadioLB->get_toggle(nPos) == TRISTATE_TRUE; + m_xRadioLB->remove(nPos); + int nCnt = m_xRadioLB->n_children(); + if (nCnt) + { + --nCnt; + + if ( nPos > nCnt ) + nPos = nCnt; + if (bChecked) + { + m_xRadioLB->set_toggle(nPos, TRISTATE_TRUE); + HandleEntryChecked(nPos); + } + m_xRadioLB->select(nPos); + } + + SelectHdl_Impl(*m_xRadioLB); +} + +IMPL_LINK_NOARG(SvxPathSelectDialog, DelHdl_Impl, weld::Button&, void) +{ + int nPos = m_xPathLB->get_selected_index(); + m_xPathLB->remove(nPos); + int nCnt = m_xPathLB->n_children(); + + if (nCnt) + { + --nCnt; + + if ( nPos > nCnt ) + nPos = nCnt; + m_xPathLB->select(nPos); + } + + SelectHdl_Impl(*m_xPathLB); +} + +SvxMultiPathDialog::SvxMultiPathDialog(weld::Window* pParent) + : GenericDialogController(pParent, "cui/ui/multipathdialog.ui", "MultiPathDialog") + , m_xRadioLB(m_xBuilder->weld_tree_view("paths")) + , m_xAddBtn(m_xBuilder->weld_button("add")) + , m_xDelBtn(m_xBuilder->weld_button("delete")) +{ + m_xRadioLB->set_size_request(m_xRadioLB->get_approximate_digit_width() * 60, + m_xRadioLB->get_text_height() * 10); + m_xRadioLB->enable_toggle_buttons(weld::ColumnToggleType::Radio); + m_xRadioLB->connect_toggled(LINK(this, SvxMultiPathDialog, CheckHdl_Impl)); + m_xRadioLB->connect_changed(LINK(this, SvxMultiPathDialog, SelectHdl_Impl)); + + m_xAddBtn->connect_clicked(LINK(this, SvxMultiPathDialog, AddHdl_Impl)); + m_xDelBtn->connect_clicked(LINK(this, SvxMultiPathDialog, DelHdl_Impl)); + + SelectHdl_Impl(*m_xRadioLB); +} + +SvxPathSelectDialog::SvxPathSelectDialog(weld::Window* pParent) + : GenericDialogController(pParent, "cui/ui/selectpathdialog.ui", "SelectPathDialog") + , m_xPathLB(m_xBuilder->weld_tree_view("paths")) + , m_xAddBtn(m_xBuilder->weld_button("add")) + , m_xDelBtn(m_xBuilder->weld_button("delete")) +{ + m_xPathLB->set_size_request(m_xPathLB->get_approximate_digit_width() * 60, + m_xPathLB->get_text_height() * 10); + + m_xPathLB->connect_changed(LINK(this, SvxPathSelectDialog, SelectHdl_Impl)); + m_xAddBtn->connect_clicked(LINK(this, SvxPathSelectDialog, AddHdl_Impl)); + m_xDelBtn->connect_clicked(LINK(this, SvxPathSelectDialog, DelHdl_Impl)); + + SelectHdl_Impl(*m_xPathLB); +} + +SvxMultiPathDialog::~SvxMultiPathDialog() +{ +} + +OUString SvxMultiPathDialog::GetPath() const +{ + OUStringBuffer sNewPath; + sal_Unicode cDelim = SVT_SEARCHPATH_DELIMITER; + + OUString sWritable; + for (int i = 0, nCount = m_xRadioLB->n_children(); i < nCount; ++i) + { + if (m_xRadioLB->get_toggle(i) == TRISTATE_TRUE) + sWritable = m_xRadioLB->get_id(i); + else + { + if (!sNewPath.isEmpty()) + sNewPath.append(cDelim); + sNewPath.append(m_xRadioLB->get_id(i)); + } + } + if (!sNewPath.isEmpty()) + sNewPath.append(cDelim); + sNewPath.append(sWritable); + + return sNewPath.makeStringAndClear(); +} + +OUString SvxPathSelectDialog::GetPath() const +{ + OUStringBuffer sNewPath; + + for (int i = 0; i < m_xPathLB->n_children(); ++i) + { + if ( !sNewPath.isEmpty() ) + sNewPath.append(SVT_SEARCHPATH_DELIMITER); + sNewPath.append( m_xPathLB->get_id(i)); + } + + return sNewPath.makeStringAndClear(); +} + +void SvxMultiPathDialog::SetPath( std::u16string_view rPath ) +{ + if ( !rPath.empty() ) + { + const sal_Unicode cDelim = SVT_SEARCHPATH_DELIMITER; + int nCount = 0; + sal_Int32 nIndex = 0; + do + { + const OUString sPath( o3tl::getToken(rPath, 0, cDelim, nIndex ) ); + OUString sSystemPath; + bool bIsSystemPath = + osl::FileBase::getSystemPathFromFileURL(sPath, sSystemPath) == osl::FileBase::E_None; + + const OUString sEntry((bIsSystemPath ? sSystemPath : sPath)); + AppendEntry(sEntry, sPath); + ++nCount; + } + while (nIndex >= 0); + + if (nCount) + { + m_xRadioLB->set_toggle(nCount - 1, TRISTATE_TRUE); + HandleEntryChecked(nCount - 1); + } + } + + SelectHdl_Impl(*m_xRadioLB); +} + +void SvxPathSelectDialog::SetPath(std::u16string_view rPath) +{ + if ( !rPath.empty() ) + { + sal_Int32 nIndex = 0; + do + { + const OUString sPath( o3tl::getToken(rPath, 0, SVT_SEARCHPATH_DELIMITER, nIndex ) ); + OUString sSystemPath; + bool bIsSystemPath = + osl::FileBase::getSystemPathFromFileURL(sPath, sSystemPath) == osl::FileBase::E_None; + + m_xPathLB->append(sPath, bIsSystemPath ? sSystemPath : sPath); + } + while (nIndex >= 0); + } + + SelectHdl_Impl(*m_xPathLB); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/newtabledlg.cxx b/cui/source/dialogs/newtabledlg.cxx new file mode 100644 index 000000000..9eb50987e --- /dev/null +++ b/cui/source/dialogs/newtabledlg.cxx @@ -0,0 +1,39 @@ +/* -*- 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 <newtabledlg.hxx> + +SvxNewTableDialog::SvxNewTableDialog(weld::Window* pWindow) + : GenericDialogController(pWindow, "cui/ui/newtabledialog.ui", "NewTableDialog") + , mxNumColumns(m_xBuilder->weld_spin_button("columns")) + , mxNumRows(m_xBuilder->weld_spin_button("rows")) +{ +} + +sal_Int32 SvxNewTableDialog::getRows() const +{ + return sal::static_int_cast<sal_Int32>(mxNumRows->get_value()); +} + +sal_Int32 SvxNewTableDialog::getColumns() const +{ + return sal::static_int_cast<sal_Int32>(mxNumColumns->get_value()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/passwdomdlg.cxx b/cui/source/dialogs/passwdomdlg.cxx new file mode 100644 index 000000000..8a8fcb358 --- /dev/null +++ b/cui/source/dialogs/passwdomdlg.cxx @@ -0,0 +1,171 @@ +/* -*- 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 <vcl/svapp.hxx> +#include <passwdomdlg.hxx> +#include <strings.hrc> +#include <dialmgr.hxx> + +IMPL_LINK_NOARG(PasswordToOpenModifyDialog, OkBtnClickHdl, weld::Button&, void) +{ + bool bInvalidState = !m_xOpenReadonlyCB->get_active() && + m_xPasswdToOpenED->get_text().isEmpty() && + m_xPasswdToModifyED->get_text().isEmpty(); + if (bInvalidState) + { + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, + m_bIsPasswordToModify? m_aInvalidStateForOkButton : m_aInvalidStateForOkButton_v2)); + xErrorBox->run(); + } + else // check for mismatched passwords... + { + const bool bToOpenMatch = m_xPasswdToOpenED->get_text() == m_xReenterPasswdToOpenED->get_text(); + const bool bToModifyMatch = m_xPasswdToModifyED->get_text() == m_xReenterPasswdToModifyED->get_text(); + const int nMismatch = (bToOpenMatch? 0 : 1) + (bToModifyMatch? 0 : 1); + if (nMismatch > 0) + { + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, + nMismatch == 1 ? m_aOneMismatch : m_aTwoMismatch)); + xErrorBox->run(); + + weld::Entry* pEdit = !bToOpenMatch ? m_xPasswdToOpenED.get() : m_xPasswdToModifyED.get(); + weld::Entry* pRepeatEdit = !bToOpenMatch? m_xReenterPasswdToOpenED.get() : m_xReenterPasswdToModifyED.get(); + if (nMismatch == 1) + { + pEdit->set_text( "" ); + pRepeatEdit->set_text( "" ); + } + else if (nMismatch == 2) + { + m_xPasswdToOpenED->set_text( "" ); + m_xReenterPasswdToOpenED->set_text( "" ); + m_xPasswdToModifyED->set_text( "" ); + m_xReenterPasswdToModifyED->set_text( "" ); + } + pEdit->grab_focus(); + } + else + { + m_xDialog->response(RET_OK); + } + } +} + +IMPL_LINK(PasswordToOpenModifyDialog, ChangeHdl, weld::Entry&, rEntry, void) +{ + weld::Label* pIndicator = nullptr; + int nLength = rEntry.get_text().getLength(); + if (&rEntry == m_xPasswdToOpenED.get()) + pIndicator = m_xPasswdToOpenInd.get(); + else if (&rEntry == m_xReenterPasswdToOpenED.get()) + pIndicator = m_xReenterPasswdToOpenInd.get(); + else if (&rEntry == m_xPasswdToModifyED.get()) + pIndicator = m_xPasswdToModifyInd.get(); + else if (&rEntry == m_xReenterPasswdToModifyED.get()) + pIndicator = m_xReenterPasswdToModifyInd.get(); + assert(pIndicator); + pIndicator->set_visible(nLength >= m_nMaxPasswdLen); +} + +PasswordToOpenModifyDialog::PasswordToOpenModifyDialog(weld::Window * pParent, sal_uInt16 nMaxPasswdLen, bool bIsPasswordToModify) + : SfxDialogController(pParent, "cui/ui/password.ui", "PasswordDialog") + , m_xPasswdToOpenED(m_xBuilder->weld_entry("newpassEntry")) + , m_xPasswdToOpenInd(m_xBuilder->weld_label("newpassIndicator")) + , m_xReenterPasswdToOpenED(m_xBuilder->weld_entry("confirmpassEntry")) + , m_xReenterPasswdToOpenInd(m_xBuilder->weld_label("confirmpassIndicator")) + , m_xOptionsExpander(m_xBuilder->weld_expander("expander")) + , m_xOk(m_xBuilder->weld_button("ok")) + , m_xOpenReadonlyCB(m_xBuilder->weld_check_button("readonly")) + , m_xPasswdToModifyFT(m_xBuilder->weld_label("label7")) + , m_xPasswdToModifyED(m_xBuilder->weld_entry("newpassroEntry")) + , m_xPasswdToModifyInd(m_xBuilder->weld_label("newpassroIndicator")) + , m_xReenterPasswdToModifyFT(m_xBuilder->weld_label("label8")) + , m_xReenterPasswdToModifyED(m_xBuilder->weld_entry("confirmropassEntry")) + , m_xReenterPasswdToModifyInd(m_xBuilder->weld_label("confirmropassIndicator")) + , m_aOneMismatch( CuiResId( RID_CUISTR_ONE_PASSWORD_MISMATCH ) ) + , m_aTwoMismatch( CuiResId( RID_CUISTR_TWO_PASSWORDS_MISMATCH ) ) + , m_aInvalidStateForOkButton( CuiResId( RID_CUISTR_INVALID_STATE_FOR_OK_BUTTON ) ) + , m_aInvalidStateForOkButton_v2( CuiResId( RID_CUISTR_INVALID_STATE_FOR_OK_BUTTON_V2 ) ) + , m_nMaxPasswdLen(nMaxPasswdLen) + , m_bIsPasswordToModify( bIsPasswordToModify ) +{ + m_xOk->connect_clicked(LINK(this, PasswordToOpenModifyDialog, OkBtnClickHdl)); + + if (nMaxPasswdLen) + { + OUString aIndicatorTemplate(CuiResId(RID_CUISTR_PASSWORD_LEN_INDICATOR).replaceFirst("%1", OUString::number(nMaxPasswdLen))); + m_xPasswdToOpenED->set_max_length( nMaxPasswdLen ); + m_xPasswdToOpenED->connect_changed(LINK(this, PasswordToOpenModifyDialog, ChangeHdl)); + m_xPasswdToOpenInd->set_label(aIndicatorTemplate); + m_xReenterPasswdToOpenED->set_max_length( nMaxPasswdLen ); + m_xReenterPasswdToOpenED->connect_changed(LINK(this, PasswordToOpenModifyDialog, ChangeHdl)); + m_xReenterPasswdToOpenInd->set_label(aIndicatorTemplate); + m_xPasswdToModifyED->set_max_length( nMaxPasswdLen ); + m_xPasswdToModifyED->connect_changed(LINK(this, PasswordToOpenModifyDialog, ChangeHdl)); + m_xPasswdToModifyInd->set_label(aIndicatorTemplate); + m_xReenterPasswdToModifyED->set_max_length( nMaxPasswdLen ); + m_xReenterPasswdToModifyED->connect_changed(LINK(this, PasswordToOpenModifyDialog, ChangeHdl)); + m_xReenterPasswdToModifyInd->set_label(aIndicatorTemplate); + } + + m_xPasswdToOpenED->grab_focus(); + + m_xOptionsExpander->set_sensitive(bIsPasswordToModify); + if (!bIsPasswordToModify) + m_xOptionsExpander->hide(); + + m_xOpenReadonlyCB->connect_toggled(LINK(this, PasswordToOpenModifyDialog, ReadonlyOnOffHdl)); + ReadonlyOnOffHdl(*m_xOpenReadonlyCB); +} + +OUString PasswordToOpenModifyDialog::GetPasswordToOpen() const +{ + const bool bPasswdOk = + !m_xPasswdToOpenED->get_text().isEmpty() && + m_xPasswdToOpenED->get_text() == m_xReenterPasswdToOpenED->get_text(); + return bPasswdOk ? m_xPasswdToOpenED->get_text() : OUString(); +} + + +OUString PasswordToOpenModifyDialog::GetPasswordToModify() const +{ + const bool bPasswdOk = + !m_xPasswdToModifyED->get_text().isEmpty() && + m_xPasswdToModifyED->get_text() == m_xReenterPasswdToModifyED->get_text(); + return bPasswdOk ? m_xPasswdToModifyED->get_text() : OUString(); +} + + +bool PasswordToOpenModifyDialog::IsRecommendToOpenReadonly() const +{ + return m_xOpenReadonlyCB->get_active(); +} + +IMPL_LINK_NOARG(PasswordToOpenModifyDialog, ReadonlyOnOffHdl, weld::Toggleable&, void) +{ + bool bEnable = m_xOpenReadonlyCB->get_active(); + m_xPasswdToModifyED->set_sensitive(bEnable); + m_xPasswdToModifyFT->set_sensitive(bEnable); + m_xReenterPasswdToModifyED->set_sensitive(bEnable); + m_xReenterPasswdToModifyFT->set_sensitive(bEnable); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/pastedlg.cxx b/cui/source/dialogs/pastedlg.cxx new file mode 100644 index 000000000..d86d277cc --- /dev/null +++ b/cui/source/dialogs/pastedlg.cxx @@ -0,0 +1,339 @@ +/* -*- 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 <memory> + +#include <pastedlg.hxx> +#include <svtools/insdlg.hxx> +#include <sot/exchange.hxx> +#include <sot/formats.hxx> +#include <svtools/strings.hrc> +#include <svtools/svtresid.hxx> +#include <tools/lineend.hxx> +#include <comphelper/dispatchcommand.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> + +SvPasteObjectDialog::SvPasteObjectDialog(weld::Window* pParent) + : GenericDialogController(pParent, "cui/ui/pastespecial.ui", "PasteSpecialDialog") + , m_xFtObjectSource(m_xBuilder->weld_label("source")) + , m_xLbInsertList(m_xBuilder->weld_tree_view("list")) + , m_xOKButton(m_xBuilder->weld_button("ok")) +{ + m_xLbInsertList->set_size_request(m_xLbInsertList->get_approximate_digit_width() * 40, + m_xLbInsertList->get_height_rows(6)); + m_xOKButton->set_sensitive(false); + + ObjectLB().connect_changed(LINK(this, SvPasteObjectDialog, SelectHdl)); + ObjectLB().connect_row_activated(LINK( this, SvPasteObjectDialog, DoubleClickHdl)); +} + +void SvPasteObjectDialog::SelectObject() +{ + if (m_xLbInsertList->n_children()) + { + m_xLbInsertList->select(0); + SelectHdl(*m_xLbInsertList); + } +} + +IMPL_LINK_NOARG(SvPasteObjectDialog, SelectHdl, weld::TreeView&, void) +{ + if (!m_xOKButton->get_sensitive()) + m_xOKButton->set_sensitive(true); +} + +IMPL_LINK_NOARG(SvPasteObjectDialog, DoubleClickHdl, weld::TreeView&, bool) +{ + m_xDialog->response(RET_OK); + return true; +} + +/************************************************************************* +|* SvPasteObjectDialog::Insert() +*************************************************************************/ +void SvPasteObjectDialog::Insert( SotClipboardFormatId nFormat, const OUString& rFormatName ) +{ + aSupplementMap.insert( std::make_pair( nFormat, rFormatName ) ); +} + +void SvPasteObjectDialog::InsertUno(const OUString& sCmd, const OUString& sLabel) +{ + aExtraCommand.first = sCmd; + aExtraCommand.second = sLabel; +} + + +void SvPasteObjectDialog::PreGetFormat( const TransferableDataHelper &rHelper ) +{ + //TODO/LATER: why is the Descriptor never used?! + TransferableObjectDescriptor aDesc; + if (rHelper.HasFormat(SotClipboardFormatId::OBJECTDESCRIPTOR)) + { + (void)const_cast<TransferableDataHelper&>(rHelper).GetTransferableObjectDescriptor( + SotClipboardFormatId::OBJECTDESCRIPTOR, aDesc); + } + const DataFlavorExVector* pFormats = &rHelper.GetDataFlavorExVector(); + + // create and fill dialog box + OUString aSourceName, aTypeName; + SvGlobalName aEmptyNm; + + //ObjectLB().SetUpdateMode( false ); + ObjectLB().freeze(); + + DataFlavorExVector::iterator aIter( const_cast<DataFlavorExVector&>(*pFormats).begin() ), + aEnd( const_cast<DataFlavorExVector&>(*pFormats).end() ); + while( aIter != aEnd ) + { + SotClipboardFormatId nFormat = (*aIter++).mnSotId; + + std::map< SotClipboardFormatId, OUString >::iterator itName = + aSupplementMap.find( nFormat ); + + // if there is an "Embed Source" or and "Embedded Object" on the + // Clipboard we read the Description and the Source of this object + // from an accompanied "Object Descriptor" format on the clipboard + // Remember: these formats mostly appear together on the clipboard + OUString aName; + const OUString* pName = nullptr; + if ( itName == aSupplementMap.end() ) + { + SvPasteObjectHelper::GetEmbeddedName(rHelper,aName,aSourceName,nFormat); + if ( !aName.isEmpty() ) + pName = &aName; + } + else + { + pName = &(itName->second); + } + + if( pName ) + { + aName = *pName; + + if( SotClipboardFormatId::EMBED_SOURCE == nFormat ) + { + if( aDesc.maClassName != aEmptyNm ) + { + aSourceName = aDesc.maDisplayName; + + if( aDesc.maClassName == aObjClassName ) + aName = aObjName; + else + aName = aTypeName = aDesc.maTypeName; + } + } + else if( SotClipboardFormatId::LINK_SOURCE == nFormat ) + { + continue; + } + else if( aName.isEmpty() ) + aName = SvPasteObjectHelper::GetSotFormatUIName( nFormat ); + + // Show RICHTEXT only in case RTF is not present. + if (nFormat == SotClipboardFormatId::RICHTEXT && + std::any_of(pFormats->begin(), pFormats->end(), + [](const DataFlavorEx& rFlavor) { + return rFlavor.mnSotId == SotClipboardFormatId::RTF; + })) + { + continue; + } + + if (ObjectLB().find_text(aName) == -1) + { + ObjectLB().append(OUString::number(static_cast<sal_uInt32>(nFormat)), aName); + } + } + } + + if( aTypeName.isEmpty() && aSourceName.isEmpty() ) + { + if( aDesc.maClassName != aEmptyNm ) + { + aSourceName = aDesc.maDisplayName; + aTypeName = aDesc.maTypeName; + } + + if( aTypeName.isEmpty() && aSourceName.isEmpty() ) + { + // global resource from svtools (former so3 resource) + aSourceName = SvtResId(STR_UNKNOWN_SOURCE); + } + } + + ObjectLB().thaw(); + SelectObject(); + + if( !aSourceName.isEmpty() ) + { + if( !aTypeName.isEmpty() ) + aTypeName += "\n"; + + aTypeName += aSourceName; + aTypeName = convertLineEnd(aTypeName, GetSystemLineEnd()); + } + + m_xFtObjectSource->set_label(aTypeName); +} + +SotClipboardFormatId SvPasteObjectDialog::GetFormatOnly() +{ + return static_cast<SotClipboardFormatId>(ObjectLB().get_selected_id().toUInt32()); +} + +SotClipboardFormatId SvPasteObjectDialog::GetFormat( const TransferableDataHelper& rHelper) +{ + //TODO/LATER: why is the Descriptor never used?! + TransferableObjectDescriptor aDesc; + if (rHelper.HasFormat(SotClipboardFormatId::OBJECTDESCRIPTOR)) + { + (void)const_cast<TransferableDataHelper&>(rHelper).GetTransferableObjectDescriptor( + SotClipboardFormatId::OBJECTDESCRIPTOR, aDesc); + } + const DataFlavorExVector* pFormats = &rHelper.GetDataFlavorExVector(); + + // create and fill dialog box + OUString aSourceName, aTypeName; + SotClipboardFormatId nSelFormat = SotClipboardFormatId::NONE; + SvGlobalName aEmptyNm; + + ObjectLB().freeze(); + + for (auto const& format : *pFormats) + { + SotClipboardFormatId nFormat = format.mnSotId; + + std::map< SotClipboardFormatId, OUString >::iterator itName = + aSupplementMap.find( nFormat ); + + // if there is an "Embed Source" or and "Embedded Object" on the + // Clipboard we read the Description and the Source of this object + // from an accompanied "Object Descriptor" format on the clipboard + // Remember: these formats mostly appear together on the clipboard + OUString aName; + const OUString* pName = nullptr; + if ( itName == aSupplementMap.end() ) + { + SvPasteObjectHelper::GetEmbeddedName(rHelper,aName,aSourceName,nFormat); + if ( !aName.isEmpty() ) + pName = &aName; + } + else + { + pName = &(itName->second); + } + + if( pName ) + { + aName = *pName; + + if( SotClipboardFormatId::EMBED_SOURCE == nFormat ) + { + if( aDesc.maClassName != aEmptyNm ) + { + aSourceName = aDesc.maDisplayName; + + if( aDesc.maClassName == aObjClassName ) + aName = aObjName; + else + aName = aTypeName = aDesc.maTypeName; + } + } + else if( SotClipboardFormatId::LINK_SOURCE == nFormat ) + { + continue; + } + else if( aName.isEmpty() ) + aName = SvPasteObjectHelper::GetSotFormatUIName( nFormat ); + + // Show RICHTEXT only in case RTF is not present. + if (nFormat == SotClipboardFormatId::RICHTEXT && + std::any_of(pFormats->begin(), pFormats->end(), + [](const DataFlavorEx& rFlavor) { + return rFlavor.mnSotId == SotClipboardFormatId::RTF; + })) + { + continue; + } + + if (ObjectLB().find_text(aName) == -1) + { + ObjectLB().append(OUString::number(static_cast<sal_uInt32>(nFormat)), aName); + } + } + } + + if( aTypeName.isEmpty() && aSourceName.isEmpty() ) + { + if( aDesc.maClassName != aEmptyNm ) + { + aSourceName = aDesc.maDisplayName; + aTypeName = aDesc.maTypeName; + } + + if( aTypeName.isEmpty() && aSourceName.isEmpty() ) + { + // global resource from svtools (former so3 resource) + aSourceName = SvtResId(STR_UNKNOWN_SOURCE); + } + } + + if (!aExtraCommand.first.isEmpty()) + { + ObjectLB().append(aExtraCommand.first, aExtraCommand.second); + } + + ObjectLB().thaw(); + SelectObject(); + + if( !aSourceName.isEmpty() ) + { + if( !aTypeName.isEmpty() ) + aTypeName += "\n"; + + aTypeName += aSourceName; + aTypeName = convertLineEnd(aTypeName, GetSystemLineEnd()); + } + + m_xFtObjectSource->set_label(aTypeName); + + if (run() == RET_OK) + { + if (ObjectLB().get_selected_id().startsWithIgnoreAsciiCase(".uno")) + { + comphelper::dispatchCommand(aExtraCommand.first, {}); + nSelFormat = SotClipboardFormatId::NONE; + } + else + { + nSelFormat = static_cast<SotClipboardFormatId>(ObjectLB().get_selected_id().toUInt32()); + } + } + + return nSelFormat; +} + +void SvPasteObjectDialog::SetObjName( const SvGlobalName & rClass, const OUString & rObjName ) +{ + aObjClassName = rClass; + aObjName = rObjName; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/postdlg.cxx b/cui/source/dialogs/postdlg.cxx new file mode 100644 index 000000000..1bcb6b7a0 --- /dev/null +++ b/cui/source/dialogs/postdlg.cxx @@ -0,0 +1,162 @@ +/* -*- 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 <tools/date.hxx> +#include <tools/lineend.hxx> +#include <tools/time.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <svl/itempool.hxx> +#include <svl/itemset.hxx> +#include <unotools/useroptions.hxx> +#include <unotools/localedatawrapper.hxx> +#include <svx/svxids.hrc> + +#include <svx/postattr.hxx> +#include <postdlg.hxx> + +// class SvxPostItDialog ------------------------------------------------- + +SvxPostItDialog::SvxPostItDialog(weld::Widget* pParent, const SfxItemSet& rCoreSet, + bool bPrevNext) + : SfxDialogController(pParent, "cui/ui/comment.ui", "CommentDialog") + , m_rSet(rCoreSet) + , m_xLastEditFT(m_xBuilder->weld_label("lastedit")) + , m_xAltTitle(m_xBuilder->weld_label("alttitle")) + , m_xEditED(m_xBuilder->weld_text_view("edit")) + , m_xInsertAuthor(m_xBuilder->weld_widget("insertauthor")) + , m_xAuthorBtn(m_xBuilder->weld_button("author")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) + , m_xPrevBtn(m_xBuilder->weld_button("previous")) + , m_xNextBtn(m_xBuilder->weld_button("next")) +{ + m_xPrevBtn->connect_clicked( LINK( this, SvxPostItDialog, PrevHdl ) ); + m_xNextBtn->connect_clicked( LINK( this, SvxPostItDialog, NextHdl ) ); + m_xAuthorBtn->connect_clicked( LINK( this, SvxPostItDialog, Stamp ) ); + m_xOKBtn->connect_clicked( LINK( this, SvxPostItDialog, OKHdl ) ); + + bool bNew = true; + + m_xPrevBtn->set_visible(bPrevNext); + m_xNextBtn->set_visible(bPrevNext); + + OUString aAuthorStr, aDateStr; + + if (m_rSet.GetItemState( SID_ATTR_POSTIT_AUTHOR ) >= SfxItemState::DEFAULT) + { + bNew = false; + const SvxPostItAuthorItem& rAuthor = m_rSet.Get(SID_ATTR_POSTIT_AUTHOR); + aAuthorStr = rAuthor.GetValue(); + } + else + aAuthorStr = SvtUserOptions().GetID(); + + if (m_rSet.GetItemState( SID_ATTR_POSTIT_DATE ) >= SfxItemState::DEFAULT) + { + const SvxPostItDateItem& rDate = m_rSet.Get( SID_ATTR_POSTIT_DATE ); + aDateStr = rDate.GetValue(); + } + else + { + const LocaleDataWrapper& rLocaleWrapper( Application::GetSettings().GetLocaleDataWrapper() ); + aDateStr = rLocaleWrapper.getDate( Date( Date::SYSTEM ) ); + } + + OUString aTextStr; + if (m_rSet.GetItemState( SID_ATTR_POSTIT_TEXT ) >= SfxItemState::DEFAULT) + { + const SvxPostItTextItem& rText = m_rSet.Get( SID_ATTR_POSTIT_TEXT ); + aTextStr = rText.GetValue(); + } + + ShowLastAuthor(aAuthorStr, aDateStr); + + //lock to an initial size before replacing contents + m_xEditED->set_size_request(m_xEditED->get_approximate_digit_width() * 32, + m_xEditED->get_height_rows(10)); + m_xEditED->set_text(convertLineEnd(aTextStr, GetSystemLineEnd())); + + if (!bNew) + m_xDialog->set_title(m_xAltTitle->get_label()); +} + + +SvxPostItDialog::~SvxPostItDialog() +{ +} + +void SvxPostItDialog::ShowLastAuthor(std::u16string_view rAuthor, std::u16string_view rDate) +{ + OUString sTxt = OUString::Concat(rAuthor) + ", " + rDate; + m_xLastEditFT->set_label( sTxt ); +} + +WhichRangesContainer SvxPostItDialog::GetRanges() +{ + return WhichRangesContainer(svl::Items<SID_ATTR_POSTIT_AUTHOR, SID_ATTR_POSTIT_TEXT>); +} + +void SvxPostItDialog::EnableTravel(bool bNext, bool bPrev) +{ + m_xPrevBtn->set_sensitive(bPrev); + m_xNextBtn->set_sensitive(bNext); +} + +IMPL_LINK_NOARG(SvxPostItDialog, PrevHdl, weld::Button&, void) +{ + m_aPrevHdlLink.Call( *this ); +} + +IMPL_LINK_NOARG(SvxPostItDialog, NextHdl, weld::Button&, void) +{ + m_aNextHdlLink.Call( *this ); +} + +IMPL_LINK_NOARG(SvxPostItDialog, Stamp, weld::Button&, void) +{ + Date aDate( Date::SYSTEM ); + tools::Time aTime( tools::Time::SYSTEM ); + OUString aTmp( SvtUserOptions().GetID() ); + const LocaleDataWrapper& rLocaleWrapper( Application::GetSettings().GetLocaleDataWrapper() ); + OUString aStr( m_xEditED->get_text() + "\n---- " ); + + if ( !aTmp.isEmpty() ) + { + aStr += aTmp + ", "; + } + aStr += rLocaleWrapper.getDate(aDate) + ", " + rLocaleWrapper.getTime(aTime, false) + " ----\n"; + aStr = convertLineEnd(aStr, GetSystemLineEnd()); + + m_xEditED->set_text(aStr); + sal_Int32 nLen = aStr.getLength(); + m_xEditED->grab_focus(); + m_xEditED->select_region(nLen, nLen); +} + +IMPL_LINK_NOARG(SvxPostItDialog, OKHdl, weld::Button&, void) +{ + const LocaleDataWrapper& rLocaleWrapper( Application::GetSettings().GetLocaleDataWrapper() ); + m_xOutSet.reset(new SfxItemSet(m_rSet)); + m_xOutSet->Put( SvxPostItAuthorItem(SvtUserOptions().GetID(), SID_ATTR_POSTIT_AUTHOR ) ); + m_xOutSet->Put( SvxPostItDateItem(rLocaleWrapper.getDate( Date( Date::SYSTEM ) ), SID_ATTR_POSTIT_DATE ) ); + m_xOutSet->Put( SvxPostItTextItem(m_xEditED->get_text(), SID_ATTR_POSTIT_TEXT ) ); + m_xDialog->response(RET_OK); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/screenshotannotationdlg.cxx b/cui/source/dialogs/screenshotannotationdlg.cxx new file mode 100644 index 000000000..1f21c5278 --- /dev/null +++ b/cui/source/dialogs/screenshotannotationdlg.cxx @@ -0,0 +1,581 @@ +/* -*- 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 <screenshotannotationdlg.hxx> + +#include <strings.hrc> +#include <dialmgr.hxx> + +#include <basegfx/range/b2irange.hxx> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> + +#include <comphelper/random.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <sfx2/filedlghelper.hxx> +#include <tools/stream.hxx> +#include <tools/urlobj.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/customweld.hxx> +#include <vcl/event.hxx> +#include <vcl/pngwrite.hxx> +#include <vcl/svapp.hxx> +#include <vcl/salgtype.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weld.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <set> +#include <string_view> + +using namespace com::sun::star; + +namespace +{ + OUString lcl_genRandom( std::u16string_view rId ) + { + //FIXME: plus timestamp + unsigned int nRand = comphelper::rng::uniform_uint_distribution(0, 0xFFFF); + return OUString( rId + OUString::number( nRand ) ); + } + + + OUString lcl_AltDescr() + { + OUString aTempl("<alt id=\"%1\">" + " " //FIXME real dialog title or something + "</alt>"); + aTempl = aTempl.replaceFirst( "%1", lcl_genRandom(u"alt_id") ); + + return aTempl; + } + + OUString lcl_Image( std::u16string_view rScreenshotId, const Size& rSize ) + { + OUString aTempl("<image id=\"%1\" src=\"media/screenshots/%2.png\"" + " width=\"%3cm\" height=\"%4cm\">" + "%5" + "</image>"); + aTempl = aTempl.replaceFirst( "%1", lcl_genRandom(u"img_id") ); + aTempl = aTempl.replaceFirst( "%2", rScreenshotId ); + aTempl = aTempl.replaceFirst( "%3", OUString::number( rSize.Width() ) ); + aTempl = aTempl.replaceFirst( "%4", OUString::number( rSize.Height() ) ); + aTempl = aTempl.replaceFirst( "%5", lcl_AltDescr() ); + + return aTempl; + } + + OUString lcl_ParagraphWithImage( std::u16string_view rScreenshotId, const Size& rSize ) + { + OUString aTempl( "<paragraph id=\"%1\" role=\"paragraph\">%2" + "</paragraph>" SAL_NEWLINE_STRING ); + aTempl = aTempl.replaceFirst( "%1", lcl_genRandom(u"par_id") ); + aTempl = aTempl.replaceFirst( "%2", lcl_Image(rScreenshotId, rSize) ); + + return aTempl; + } + + OUString lcl_Bookmark( std::u16string_view rWidgetId ) + { + OUString aTempl = "<!-- Bookmark for widget %1 -->" SAL_NEWLINE_STRING + "<bookmark branch=\"hid/%2\" id=\"%3\" localize=\"false\"/>" SAL_NEWLINE_STRING; + aTempl = aTempl.replaceFirst( "%1", rWidgetId ); + aTempl = aTempl.replaceFirst( "%2", rWidgetId ); + aTempl = aTempl.replaceFirst( "%3", lcl_genRandom(u"bm_id") ); + + return aTempl; + } +} + +namespace +{ + class Picture : public weld::CustomWidgetController + { + private: + ScreenshotAnnotationDlg_Impl *m_pDialog; + bool m_bMouseOver; + private: + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) override; + virtual bool MouseMove(const MouseEvent& rMouseEvent) override; + virtual bool MouseButtonUp(const MouseEvent& rMouseEvent) override; + public: + Picture(ScreenshotAnnotationDlg_Impl* pDialog) + : m_pDialog(pDialog) + , m_bMouseOver(false) + { + } + + bool IsMouseOver() const + { + return m_bMouseOver; + } + }; +} + +class ScreenshotAnnotationDlg_Impl +{ +public: + ScreenshotAnnotationDlg_Impl( + weld::Window* pParent, + weld::Builder& rParent, + weld::Dialog& rParentDialog); + ~ScreenshotAnnotationDlg_Impl(); + +private: + // Handler for click on save + DECL_LINK(saveButtonHandler, weld::Button&, void); + + // helper methods + weld::ScreenShotEntry* CheckHit(const basegfx::B2IPoint& rPosition); + void PaintScreenShotEntry( + const weld::ScreenShotEntry& rEntry, + const Color& rColor, + double fLineWidth, + double fTransparency); + void RepaintToBuffer( + bool bUseDimmed = false, + bool bPaintHilight = false); + void RepaintPictureElement(); + Point GetOffsetInPicture() const; + + // local variables + weld::Window* mpParentWindow; + weld::Dialog& mrParentDialog; + BitmapEx maParentDialogBitmap; + BitmapEx maDimmedDialogBitmap; + Size maParentDialogSize; + + // VirtualDevice for buffered interaction paints + VclPtr<VirtualDevice> mxVirtualBufferDevice; + + // all detected children + weld::ScreenShotCollection maAllChildren; + + // highlighted/selected children + weld::ScreenShotEntry* mpHilighted; + std::set< weld::ScreenShotEntry* > + maSelected; + + // list of detected controls + Picture maPicture; + std::unique_ptr<weld::CustomWeld> mxPicture; + std::unique_ptr<weld::TextView> mxText; + std::unique_ptr<weld::Button> mxSave; + + // save as text + OUString maSaveAsText; + OUString maMainMarkupText; + + // folder URL + static OUString maLastFolderURL; +public: + void Paint(vcl::RenderContext& rRenderContext); + bool MouseMove(const MouseEvent& rMouseEvent); + bool MouseButtonUp(); +}; + +OUString ScreenshotAnnotationDlg_Impl::maLastFolderURL = OUString(); + +ScreenshotAnnotationDlg_Impl::ScreenshotAnnotationDlg_Impl( + weld::Window* pParent, + weld::Builder& rParentBuilder, + weld::Dialog& rParentDialog) +: mpParentWindow(pParent), + mrParentDialog(rParentDialog), + mxVirtualBufferDevice(nullptr), + mpHilighted(nullptr), + maPicture(this), + maSaveAsText(CuiResId(RID_CUISTR_SAVE_SCREENSHOT_AS)) +{ + VclPtr<VirtualDevice> xParentDialogSurface(rParentDialog.screenshot()); + maParentDialogSize = xParentDialogSurface->GetOutputSizePixel(); + maParentDialogBitmap = xParentDialogSurface->GetBitmapEx(Point(), maParentDialogSize); + maDimmedDialogBitmap = maParentDialogBitmap; + + // image ain't empty + assert(!maParentDialogBitmap.IsEmpty()); + assert(0 != maParentDialogBitmap.GetSizePixel().Width()); + assert(0 != maParentDialogBitmap.GetSizePixel().Height()); + + // get needed widgets + mxPicture.reset(new weld::CustomWeld(rParentBuilder, "picture", maPicture)); + assert(mxPicture); + mxText = rParentBuilder.weld_text_view("text"); + assert(mxText); + mxSave = rParentBuilder.weld_button("save"); + assert(mxSave); + + // set screenshot image at DrawingArea, resize, set event listener + if (mxPicture) + { + maAllChildren = mrParentDialog.collect_screenshot_data(); + + // to make clear that maParentDialogBitmap is a background image, adjust + // luminance a bit for maDimmedDialogBitmap - other methods may be applied + maDimmedDialogBitmap.Adjust(-15, 0, 0, 0, 0); + + // init paint buffering VirtualDevice + mxVirtualBufferDevice = VclPtr<VirtualDevice>::Create(*Application::GetDefaultDevice(), DeviceFormat::DEFAULT); + mxVirtualBufferDevice->SetOutputSizePixel(maParentDialogSize); + mxVirtualBufferDevice->SetFillColor(COL_TRANSPARENT); + + // initially set image for picture control + mxVirtualBufferDevice->DrawBitmapEx(Point(0, 0), maDimmedDialogBitmap); + + // set size for picture control, this will re-layout so that + // the picture control shows the whole dialog + maPicture.SetOutputSizePixel(maParentDialogSize); + mxPicture->set_size_request(maParentDialogSize.Width(), maParentDialogSize.Height()); + + mxPicture->queue_draw(); + } + + // set some test text at VclMultiLineEdit and make read-only - only + // copying content to clipboard is allowed + if (mxText) + { + mxText->set_size_request(400, mxText->get_height_rows(10)); + OUString aHelpId = OStringToOUString( mrParentDialog.get_help_id(), RTL_TEXTENCODING_UTF8 ); + Size aSizeCm = Application::GetDefaultDevice()->PixelToLogic(maParentDialogSize, MapMode(MapUnit::MapCM)); + maMainMarkupText = lcl_ParagraphWithImage( aHelpId, aSizeCm ); + mxText->set_text( maMainMarkupText ); + mxText->set_editable(false); + } + + // set click handler for save button + if (mxSave) + { + mxSave->connect_clicked(LINK(this, ScreenshotAnnotationDlg_Impl, saveButtonHandler)); + } +} + +ScreenshotAnnotationDlg_Impl::~ScreenshotAnnotationDlg_Impl() +{ + mxVirtualBufferDevice.disposeAndClear(); +} + +IMPL_LINK_NOARG(ScreenshotAnnotationDlg_Impl, saveButtonHandler, weld::Button&, void) +{ + // 'save screenshot...' pressed, offer to save maParentDialogBitmap + // as PNG image, use *.id file name as screenshot file name offering + // get a suggestion for the filename from buildable name + OString aDerivedFileName = mrParentDialog.get_buildable_name(); + + auto xFileDlg = std::make_unique<sfx2::FileDialogHelper>(ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION, + FileDialogFlags::NONE, mpParentWindow); + xFileDlg->SetContext(sfx2::FileDialogHelper::ScreenshotAnnotation); + + const uno::Reference< ui::dialogs::XFilePicker3 > xFilePicker = xFileDlg->GetFilePicker(); + + xFilePicker->setTitle(maSaveAsText); + + if (!maLastFolderURL.isEmpty()) + { + xFilePicker->setDisplayDirectory(maLastFolderURL); + } + + xFilePicker->appendFilter("*.png", "*.png"); + xFilePicker->setCurrentFilter("*.png"); + xFilePicker->setDefaultName(OStringToOUString(aDerivedFileName, RTL_TEXTENCODING_UTF8)); + xFilePicker->setMultiSelectionMode(false); + + if (xFilePicker->execute() != ui::dialogs::ExecutableDialogResults::OK) + return; + + maLastFolderURL = xFilePicker->getDisplayDirectory(); + const uno::Sequence< OUString > files(xFilePicker->getSelectedFiles()); + + if (!files.hasElements()) + return; + + OUString aConfirmedName = files[0]; + + if (aConfirmedName.isEmpty()) + return; + + INetURLObject aConfirmedURL(aConfirmedName); + OUString aCurrentExtension(aConfirmedURL.getExtension()); + + if (!aCurrentExtension.isEmpty() && aCurrentExtension != "png") + { + aConfirmedURL.removeExtension(); + aCurrentExtension.clear(); + } + + if (aCurrentExtension.isEmpty()) + { + aConfirmedURL.setExtension(u"png"); + } + + // open stream + SvFileStream aNew(aConfirmedURL.PathToFileName(), StreamMode::WRITE | StreamMode::TRUNC); + + if (!aNew.IsOpen()) + return; + + // prepare bitmap to save - do use the original screenshot here, + // not the dimmed one + RepaintToBuffer(); + + // extract Bitmap + const BitmapEx aTargetBitmap( + mxVirtualBufferDevice->GetBitmapEx( + Point(0, 0), + mxVirtualBufferDevice->GetOutputSizePixel())); + + // write as PNG + vcl::PNGWriter aPNGWriter(aTargetBitmap); + aPNGWriter.Write(aNew); +} + +weld::ScreenShotEntry* ScreenshotAnnotationDlg_Impl::CheckHit(const basegfx::B2IPoint& rPosition) +{ + weld::ScreenShotEntry* pRetval = nullptr; + + for (auto&& rCandidate : maAllChildren) + { + if (rCandidate.getB2IRange().isInside(rPosition)) + { + if (pRetval) + { + if (pRetval->getB2IRange().isInside(rCandidate.getB2IRange().getMinimum()) + && pRetval->getB2IRange().isInside(rCandidate.getB2IRange().getMaximum())) + { + pRetval = &rCandidate; + } + } + else + { + pRetval = &rCandidate; + } + } + } + + return pRetval; +} + +void ScreenshotAnnotationDlg_Impl::PaintScreenShotEntry( + const weld::ScreenShotEntry& rEntry, + const Color& rColor, + double fLineWidth, + double fTransparency) +{ + if (!(mxPicture && mxVirtualBufferDevice)) + return; + + basegfx::B2DRange aB2DRange(rEntry.getB2IRange()); + + // grow in pixels to be a little bit 'outside'. This also + // ensures that getWidth()/getHeight() ain't 0.0 (see division below) + static const double fGrowTopLeft(1.5); + static const double fGrowBottomRight(0.5); + aB2DRange.expand(aB2DRange.getMinimum() - basegfx::B2DPoint(fGrowTopLeft, fGrowTopLeft)); + aB2DRange.expand(aB2DRange.getMaximum() + basegfx::B2DPoint(fGrowBottomRight, fGrowBottomRight)); + + // edge rounding in pixel. Need to convert, value for + // createPolygonFromRect is relative [0.0 .. 1.0] + static const double fEdgeRoundPixel(8.0); + const basegfx::B2DPolygon aPolygon( + basegfx::utils::createPolygonFromRect( + aB2DRange, + fEdgeRoundPixel / aB2DRange.getWidth(), + fEdgeRoundPixel / aB2DRange.getHeight())); + + mxVirtualBufferDevice->SetLineColor(rColor); + + // try to use transparency + if (!mxVirtualBufferDevice->DrawPolyLineDirect( + basegfx::B2DHomMatrix(), + aPolygon, + fLineWidth, + fTransparency, + nullptr, // MM01 + basegfx::B2DLineJoin::Round)) + { + // no transparency, draw without + mxVirtualBufferDevice->DrawPolyLine( + aPolygon, + fLineWidth); + } +} + +Point ScreenshotAnnotationDlg_Impl::GetOffsetInPicture() const +{ + const Size aPixelSizeTarget(maPicture.GetOutputSizePixel()); + + return Point( + aPixelSizeTarget.Width() > maParentDialogSize.Width() ? (aPixelSizeTarget.Width() - maParentDialogSize.Width()) >> 1 : 0, + aPixelSizeTarget.Height() > maParentDialogSize.Height() ? (aPixelSizeTarget.Height() - maParentDialogSize.Height()) >> 1 : 0); +} + +void ScreenshotAnnotationDlg_Impl::RepaintToBuffer( + bool bUseDimmed, + bool bPaintHilight) +{ + if (!mxVirtualBufferDevice) + return; + + // reset with original screenshot bitmap + mxVirtualBufferDevice->DrawBitmapEx( + Point(0, 0), + bUseDimmed ? maDimmedDialogBitmap : maParentDialogBitmap); + + // get various options + const Color aHilightColor(SvtOptionsDrawinglayer::getHilightColor()); + const double fTransparence(SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0.01); + const bool bIsAntiAliasing(SvtOptionsDrawinglayer::IsAntiAliasing()); + const AntialiasingFlags nOldAA(mxVirtualBufferDevice->GetAntialiasing()); + + if (bIsAntiAliasing) + { + mxVirtualBufferDevice->SetAntialiasing(AntialiasingFlags::Enable); + } + + // paint selected entries + for (auto&& rCandidate : maSelected) + { + static const double fLineWidthEntries(5.0); + PaintScreenShotEntry(*rCandidate, COL_LIGHTRED, fLineWidthEntries, fTransparence * 0.2); + } + + // paint highlighted entry + if (mpHilighted && bPaintHilight) + { + static const double fLineWidthHilight(7.0); + PaintScreenShotEntry(*mpHilighted, aHilightColor, fLineWidthHilight, fTransparence); + } + + if (bIsAntiAliasing) + { + mxVirtualBufferDevice->SetAntialiasing(nOldAA); + } +} + +void ScreenshotAnnotationDlg_Impl::RepaintPictureElement() +{ + if (mxPicture && mxVirtualBufferDevice) + { + // reset image in buffer, use dimmed version and allow highlight + RepaintToBuffer(true, true); + mxPicture->queue_draw(); + } +} + +void ScreenshotAnnotationDlg_Impl::Paint(vcl::RenderContext& rRenderContext) +{ + Point aPos(GetOffsetInPicture()); + Size aSize(mxVirtualBufferDevice->GetOutputSizePixel()); + rRenderContext.DrawOutDev(aPos, aSize, Point(), aSize, *mxVirtualBufferDevice); +} + +void Picture::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + m_pDialog->Paint(rRenderContext); +} + +bool ScreenshotAnnotationDlg_Impl::MouseMove(const MouseEvent& rMouseEvent) +{ + bool bRepaint(false); + + if (maPicture.IsMouseOver()) + { + const weld::ScreenShotEntry* pOldHit = mpHilighted; + const Point aOffset(GetOffsetInPicture()); + const basegfx::B2IPoint aMousePos( + rMouseEvent.GetPosPixel().X() - aOffset.X(), + rMouseEvent.GetPosPixel().Y() - aOffset.Y()); + const weld::ScreenShotEntry* pHit = CheckHit(aMousePos); + + if (pHit && pOldHit != pHit) + { + mpHilighted = const_cast<weld::ScreenShotEntry*>(pHit); + bRepaint = true; + } + } + else if (mpHilighted) + { + mpHilighted = nullptr; + bRepaint = true; + } + + if (bRepaint) + { + RepaintPictureElement(); + } + + return true; +} + +bool Picture::MouseMove(const MouseEvent& rMouseEvent) +{ + if (rMouseEvent.IsEnterWindow()) + m_bMouseOver = true; + if (rMouseEvent.IsLeaveWindow()) + m_bMouseOver = false; + return m_pDialog->MouseMove(rMouseEvent); +} + +bool ScreenshotAnnotationDlg_Impl::MouseButtonUp() +{ + // event in picture frame + bool bRepaint(false); + + if (maPicture.IsMouseOver() && mpHilighted) + { + if (maSelected.erase(mpHilighted) == 0) + { + maSelected.insert(mpHilighted); + } + + OUStringBuffer aBookmarks(maMainMarkupText); + for (auto&& rCandidate : maSelected) + { + OUString aHelpId = OStringToOUString( rCandidate->GetHelpId(), RTL_TEXTENCODING_UTF8 ); + aBookmarks.append(lcl_Bookmark( aHelpId )); + } + + mxText->set_text( aBookmarks.makeStringAndClear() ); + bRepaint = true; + } + + if (bRepaint) + { + RepaintPictureElement(); + } + + return true; +} + +bool Picture::MouseButtonUp(const MouseEvent&) +{ + return m_pDialog->MouseButtonUp(); +} + +ScreenshotAnnotationDlg::ScreenshotAnnotationDlg(weld::Dialog& rParentDialog) + : GenericDialogController(&rParentDialog, "cui/ui/screenshotannotationdialog.ui", "ScreenshotAnnotationDialog") +{ + m_pImpl.reset(new ScreenshotAnnotationDlg_Impl(m_xDialog.get(), *m_xBuilder, rParentDialog)); +} + +ScreenshotAnnotationDlg::~ScreenshotAnnotationDlg() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/scriptdlg.cxx b/cui/source/dialogs/scriptdlg.cxx new file mode 100644 index 000000000..8e787e8d1 --- /dev/null +++ b/cui/source/dialogs/scriptdlg.cxx @@ -0,0 +1,1338 @@ +/* -*- 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 <memory> +#include <string_view> +#include <utility> + +#include <sfx2/objsh.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +#include <strings.hrc> +#include <bitmaps.hlst> +#include <scriptdlg.hxx> +#include <dialmgr.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/script/provider/ScriptFrameworkErrorException.hpp> +#include <com/sun/star/script/provider/XScriptProviderSupplier.hpp> +#include <com/sun/star/script/provider/XScriptProvider.hpp> +#include <com/sun/star/script/browse/BrowseNodeTypes.hpp> +#include <com/sun/star/script/browse/XBrowseNodeFactory.hpp> +#include <com/sun/star/script/browse/BrowseNodeFactoryViewTypes.hpp> +#include <com/sun/star/script/browse/theBrowseNodeFactory.hpp> +#include <com/sun/star/script/provider/ScriptErrorRaisedException.hpp> +#include <com/sun/star/script/provider/ScriptExceptionRaisedException.hpp> +#include <com/sun/star/script/provider/ScriptFrameworkErrorType.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/script/XInvocation.hpp> +#include <com/sun/star/document/XEmbeddedScripts.hpp> + +#include <comphelper/lok.hxx> +#include <comphelper/SetFlagContextHelper.hxx> +#include <comphelper/documentinfo.hxx> +#include <comphelper/processfactory.hxx> +#include <o3tl/string_view.hxx> + +#include <svtools/imagemgr.hxx> +#include <tools/urlobj.hxx> +#include <tools/diagnose_ex.h> + +using namespace ::com::sun::star; +using namespace css::uno; +using namespace css::script; +using namespace css::frame; +using namespace css::document; + +void SvxScriptOrgDialog::delUserData(const weld::TreeIter& rIter) +{ + SFEntry* pUserData = weld::fromId<SFEntry*>(m_xScriptsBox->get_id(rIter)); + if (pUserData) + { + delete pUserData; + // TBD seem to get a Select event on node that is remove ( below ) + // so need to be able to detect that this node is not to be + // processed in order to do this, setting userData to NULL ( must + // be a better way to do this ) + m_xScriptsBox->set_id(rIter, OUString()); + } +} + +void SvxScriptOrgDialog::deleteTree(const weld::TreeIter& rIter) +{ + delUserData(rIter); + std::unique_ptr<weld::TreeIter> xIter = m_xScriptsBox->make_iterator(&rIter); + if (!m_xScriptsBox->iter_children(*xIter)) + return; + + std::unique_ptr<weld::TreeIter> xAltIter = m_xScriptsBox->make_iterator(); + bool bNextEntry; + do + { + m_xScriptsBox->copy_iterator(*xIter, *xAltIter); + bNextEntry = m_xScriptsBox->iter_next_sibling(*xAltIter); + deleteTree(*xIter); + m_xScriptsBox->remove(*xIter); + m_xScriptsBox->copy_iterator(*xAltIter, *xIter); + } + while (bNextEntry); +} + +void SvxScriptOrgDialog::deleteAllTree() +{ + std::unique_ptr<weld::TreeIter> xIter = m_xScriptsBox->make_iterator(); + if (!m_xScriptsBox->get_iter_first(*xIter)) + return; + + std::unique_ptr<weld::TreeIter> xAltIter = m_xScriptsBox->make_iterator(); + // TBD - below is a candidate for a destroyAllTrees method + bool bNextEntry; + do + { + m_xScriptsBox->copy_iterator(*xIter, *xAltIter); + bNextEntry = m_xScriptsBox->iter_next_sibling(*xAltIter); + deleteTree(*xIter); + m_xScriptsBox->remove(*xIter); + m_xScriptsBox->copy_iterator(*xAltIter, *xIter); + } + while (bNextEntry); +} + +void SvxScriptOrgDialog::Init( std::u16string_view language ) +{ + m_xScriptsBox->freeze(); + + deleteAllTree(); + + Reference< browse::XBrowseNode > rootNode; + Reference< XComponentContext > xCtx( + comphelper::getProcessComponentContext() ); + + Sequence< Reference< browse::XBrowseNode > > children; + + OUString userStr("user"); + OUString const shareStr("share"); + + try + { + Reference< browse::XBrowseNodeFactory > xFac = browse::theBrowseNodeFactory::get(xCtx); + + rootNode.set( xFac->createView( + browse::BrowseNodeFactoryViewTypes::MACROORGANIZER ) ); + + if ( rootNode.is() && rootNode->hasChildNodes() ) + { + children = rootNode->getChildNodes(); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", "Exception getting root browse node from factory"); + // TODO exception handling + } + + Reference<XModel> xDocumentModel; + for ( const Reference< browse::XBrowseNode >& childNode : std::as_const(children) ) + { + bool app = false; + OUString uiName = childNode->getName(); + OUString factoryURL; + if ( uiName == userStr || uiName == shareStr ) + { + app = true; + if ( uiName == userStr ) + { + uiName = m_sMyMacros; + } + else + { + uiName = m_sProdMacros; + } + } + else + { + xDocumentModel.set(getDocumentModel(xCtx, uiName ), UNO_QUERY); + + if ( xDocumentModel.is() ) + { + Reference< frame::XModuleManager2 > xModuleManager( frame::ModuleManager::create(xCtx) ); + + // get the long name of the document: + Sequence<beans::PropertyValue> moduleDescr; + try{ + OUString appModule = xModuleManager->identify( xDocumentModel ); + xModuleManager->getByName(appModule) >>= moduleDescr; + } catch(const uno::Exception&) + {} + + for ( const beans::PropertyValue& prop : std::as_const(moduleDescr)) + { + if ( prop.Name == "ooSetupFactoryEmptyDocumentURL" ) + { + prop.Value >>= factoryURL; + break; + } + } + } + } + + Reference< browse::XBrowseNode > langEntries = + getLangNodeFromRootNode( childNode, language ); + + insertEntry( uiName, app ? OUString(RID_CUIBMP_HARDDISK) : OUString(RID_CUIBMP_DOC), + nullptr, true, std::make_unique< SFEntry >( langEntries, xDocumentModel ), factoryURL, false ); + } + + m_xScriptsBox->thaw(); +} + +Reference< XInterface > +SvxScriptOrgDialog::getDocumentModel( Reference< XComponentContext > const & xCtx, std::u16string_view docName ) +{ + Reference< XInterface > xModel; + Reference< frame::XDesktop2 > desktop = frame::Desktop::create(xCtx); + + Reference< container::XEnumerationAccess > componentsAccess = + desktop->getComponents(); + Reference< container::XEnumeration > components = + componentsAccess->createEnumeration(); + while (components->hasMoreElements()) + { + Reference< frame::XModel > model( + components->nextElement(), UNO_QUERY ); + if ( model.is() ) + { + OUString sTdocUrl = ::comphelper::DocumentInfo::getDocumentTitle( model ); + if( sTdocUrl == docName ) + { + xModel = model; + break; + } + } + } + return xModel; +} + +Reference< browse::XBrowseNode > +SvxScriptOrgDialog::getLangNodeFromRootNode( Reference< browse::XBrowseNode > const & rootNode, std::u16string_view language ) +{ + Reference< browse::XBrowseNode > langNode; + + try + { + auto tryFind = [&] { + const Sequence<Reference<browse::XBrowseNode>> children = rootNode->getChildNodes(); + const auto it = std::find_if(children.begin(), children.end(), + [&](const Reference<browse::XBrowseNode>& child) { + return child->getName() == language; + }); + return (it != children.end()) ? *it : nullptr; + }; + { + // First try without Java interaction, to avoid warnings for non-JRE-dependent providers + css::uno::ContextLayer layer(comphelper::NoEnableJavaInteractionContext()); + langNode = tryFind(); + } + if (!langNode) + { + // Now try with Java interaction enabled + langNode = tryFind(); + } + } + catch ( Exception& ) + { + // if getChildNodes() throws an exception we just return + // the empty Reference + } + return langNode; +} + +void SvxScriptOrgDialog::RequestSubEntries(const weld::TreeIter& rRootEntry, Reference< css::script::browse::XBrowseNode > const & node, + Reference< XModel >& model) +{ + if (!node.is()) + { + return; + } + + Sequence< Reference< browse::XBrowseNode > > children; + try + { + children = node->getChildNodes(); + } + catch ( Exception& ) + { + // if we catch an exception in getChildNodes then no entries are added + } + + for ( const Reference< browse::XBrowseNode >& childNode : std::as_const(children) ) + { + OUString name( childNode->getName() ); + if ( childNode->getType() != browse::BrowseNodeTypes::SCRIPT) + { + insertEntry(name, RID_CUIBMP_LIB, &rRootEntry, true, std::make_unique<SFEntry>(childNode, model), false); + } + else + { + insertEntry(name, RID_CUIBMP_MACRO, &rRootEntry, false, std::make_unique<SFEntry>(childNode, model), false); + } + } +} + +void SvxScriptOrgDialog::insertEntry(const OUString& rText, const OUString& rBitmap, + const weld::TreeIter* pParent, bool bChildrenOnDemand, std::unique_ptr<SFEntry> && aUserData, + std::u16string_view factoryURL, bool bSelect) +{ + if (rBitmap == RID_CUIBMP_DOC && !factoryURL.empty()) + { + OUString aImage = SvFileInformationManager::GetFileImageId(INetURLObject(factoryURL)); + insertEntry(rText, aImage, pParent, bChildrenOnDemand, std::move(aUserData), bSelect); + return; + } + insertEntry(rText, rBitmap, pParent, bChildrenOnDemand, std::move(aUserData), bSelect); +} + +void SvxScriptOrgDialog::insertEntry( + const OUString& rText, const OUString& rBitmap, const weld::TreeIter* pParent, + bool bChildrenOnDemand, std::unique_ptr<SFEntry> && aUserData, bool bSelect) +{ + OUString sId(weld::toId(aUserData.release())); // XXX possible leak + m_xScriptsBox->insert(pParent, -1, &rText, &sId, nullptr, nullptr, + bChildrenOnDemand, m_xScratchIter.get()); + m_xScriptsBox->set_image(*m_xScratchIter, rBitmap); + if (bSelect) + { + m_xScriptsBox->set_cursor(*m_xScratchIter); + m_xScriptsBox->select(*m_xScratchIter); + } +} + +IMPL_LINK(SvxScriptOrgDialog, ExpandingHdl, const weld::TreeIter&, rIter, bool) +{ + SFEntry* userData = weld::fromId<SFEntry*>(m_xScriptsBox->get_id(rIter)); + + Reference< browse::XBrowseNode > node; + Reference< XModel > model; + if ( userData && !userData->isLoaded() ) + { + node = userData->GetNode(); + model = userData->GetModel(); + RequestSubEntries(rIter, node, model); + userData->setLoaded(); + } + + return true; +} + +// CuiInputDialog ------------------------------------------------------------ +CuiInputDialog::CuiInputDialog(weld::Window * pParent, InputDialogMode nMode) + : GenericDialogController(pParent, "cui/ui/newlibdialog.ui", "NewLibDialog") + , m_xEdit(m_xBuilder->weld_entry("entry")) +{ + m_xEdit->grab_focus(); + + std::unique_ptr<weld::Label> xNewLibFT(m_xBuilder->weld_label("newlibft")); + + if ( nMode == InputDialogMode::NEWMACRO ) + { + xNewLibFT->hide(); + std::unique_ptr<weld::Label> xNewMacroFT(m_xBuilder->weld_label("newmacroft")); + xNewMacroFT->show(); + std::unique_ptr<weld::Label> xAltTitle(m_xBuilder->weld_label("altmacrotitle")); + m_xDialog->set_title(xAltTitle->get_label()); + } + else if ( nMode == InputDialogMode::RENAME ) + { + xNewLibFT->hide(); + std::unique_ptr<weld::Label> xRenameFT(m_xBuilder->weld_label("renameft")); + xRenameFT->show(); + std::unique_ptr<weld::Label> xAltTitle(m_xBuilder->weld_label("altrenametitle")); + m_xDialog->set_title(xAltTitle->get_label()); + } +} + +// ScriptOrgDialog ------------------------------------------------------------ + +SvxScriptOrgDialog::SvxScriptOrgDialog(weld::Window* pParent, OUString language) + : SfxDialogController(pParent, "cui/ui/scriptorganizer.ui", "ScriptOrganizerDialog") + , m_pParent(pParent) + , m_sLanguage(std::move(language)) + , m_delErrStr(CuiResId(RID_CUISTR_DELFAILED)) + , m_delErrTitleStr(CuiResId(RID_CUISTR_DELFAILED_TITLE)) + , m_delQueryStr(CuiResId(RID_CUISTR_DELQUERY)) + , m_delQueryTitleStr(CuiResId(RID_CUISTR_DELQUERY_TITLE)) + , m_createErrStr(CuiResId(RID_CUISTR_CREATEFAILED)) + , m_createDupStr(CuiResId(RID_CUISTR_CREATEFAILEDDUP)) + , m_createErrTitleStr(CuiResId(RID_CUISTR_CREATEFAILED_TITLE)) + , m_renameErrStr(CuiResId(RID_CUISTR_RENAMEFAILED)) + , m_renameErrTitleStr(CuiResId(RID_CUISTR_RENAMEFAILED_TITLE)) + , m_sMyMacros(CuiResId(RID_CUISTR_MYMACROS)) + , m_sProdMacros(CuiResId(RID_CUISTR_PRODMACROS)) + , m_xScriptsBox(m_xBuilder->weld_tree_view("scripts")) + , m_xScratchIter(m_xScriptsBox->make_iterator()) + , m_xRunButton(m_xBuilder->weld_button("ok")) + , m_xCloseButton(m_xBuilder->weld_button("close")) + , m_xCreateButton(m_xBuilder->weld_button("create")) + , m_xEditButton(m_xBuilder->weld_button("edit")) + , m_xRenameButton(m_xBuilder->weld_button("rename")) + , m_xDelButton(m_xBuilder->weld_button("delete")) +{ + // must be a neater way to deal with the strings than as above + // append the language to the dialog title + OUString winTitle(m_xDialog->get_title()); + winTitle = winTitle.replaceFirst( "%MACROLANG", m_sLanguage ); + m_xDialog->set_title(winTitle); + + m_xScriptsBox->set_size_request(m_xScriptsBox->get_approximate_digit_width() * 45, + m_xScriptsBox->get_height_rows(12)); + + m_xScriptsBox->connect_changed( LINK( this, SvxScriptOrgDialog, ScriptSelectHdl ) ); + m_xScriptsBox->connect_expanding(LINK( this, SvxScriptOrgDialog, ExpandingHdl ) ); + m_xRunButton->connect_clicked( LINK( this, SvxScriptOrgDialog, ButtonHdl ) ); + m_xCloseButton->connect_clicked( LINK( this, SvxScriptOrgDialog, ButtonHdl ) ); + m_xRenameButton->connect_clicked( LINK( this, SvxScriptOrgDialog, ButtonHdl ) ); + m_xEditButton->connect_clicked( LINK( this, SvxScriptOrgDialog, ButtonHdl ) ); + m_xDelButton->connect_clicked( LINK( this, SvxScriptOrgDialog, ButtonHdl ) ); + m_xCreateButton->connect_clicked( LINK( this, SvxScriptOrgDialog, ButtonHdl ) ); + + m_xRunButton->set_sensitive(false); + m_xRenameButton->set_sensitive(false); + m_xEditButton->set_sensitive(false); + m_xDelButton->set_sensitive(false); + m_xCreateButton->set_sensitive(false); + + Init(m_sLanguage); + RestorePreviousSelection(); +} + +SvxScriptOrgDialog::~SvxScriptOrgDialog() +{ + deleteAllTree(); +} + +short SvxScriptOrgDialog::run() +{ + SfxObjectShell *pDoc = SfxObjectShell::GetFirst(); + + // force load of MSPs for all documents + while ( pDoc ) + { + Reference< provider::XScriptProviderSupplier > xSPS( pDoc->GetModel(), UNO_QUERY ); + if ( xSPS.is() ) + { + xSPS->getScriptProvider(); + } + + pDoc = SfxObjectShell::GetNext(*pDoc); + } + + return SfxDialogController::run(); +} + +void SvxScriptOrgDialog::CheckButtons( Reference< browse::XBrowseNode > const & node ) +{ + if ( node.is() ) + { + if ( node->getType() == browse::BrowseNodeTypes::SCRIPT) + { + m_xRunButton->set_sensitive(true); + } + else + { + m_xRunButton->set_sensitive(false); + } + Reference< beans::XPropertySet > xProps( node, UNO_QUERY ); + + if ( !xProps.is() ) + { + m_xEditButton->set_sensitive(false); + m_xDelButton->set_sensitive(false); + m_xCreateButton->set_sensitive(false); + m_xRunButton->set_sensitive(false); + return; + } + + OUString sName("Editable"); + + if ( getBoolProperty( xProps, sName ) ) + { + m_xEditButton->set_sensitive(true); + } + else + { + m_xEditButton->set_sensitive(false); + } + + sName = "Deletable"; + + if ( getBoolProperty( xProps, sName ) ) + { + m_xDelButton->set_sensitive(true); + } + else + { + m_xDelButton->set_sensitive(false); + } + + sName = "Creatable"; + + if ( getBoolProperty( xProps, sName ) ) + { + m_xCreateButton->set_sensitive(true); + } + else + { + m_xCreateButton->set_sensitive(false); + } + + sName = "Renamable"; + + if ( getBoolProperty( xProps, sName ) ) + { + m_xRenameButton->set_sensitive(true); + } + else + { + m_xRenameButton->set_sensitive(false); + } + } + else + { + // no node info available, disable all configurable actions + m_xDelButton->set_sensitive(false); + m_xCreateButton->set_sensitive(false); + m_xEditButton->set_sensitive(false); + m_xRunButton->set_sensitive(false); + m_xRenameButton->set_sensitive(false); + } +} + +IMPL_LINK_NOARG(SvxScriptOrgDialog, ScriptSelectHdl, weld::TreeView&, void) +{ + std::unique_ptr<weld::TreeIter> xIter = m_xScriptsBox->make_iterator(); + if (!m_xScriptsBox->get_selected(xIter.get())) + return; + + SFEntry* userData = weld::fromId<SFEntry*>(m_xScriptsBox->get_id(*xIter)); + + Reference< browse::XBrowseNode > node; + if (userData) + { + node = userData->GetNode(); + CheckButtons(node); + } +} + +IMPL_LINK(SvxScriptOrgDialog, ButtonHdl, weld::Button&, rButton, void) +{ + if ( &rButton == m_xCloseButton.get() ) + { + StoreCurrentSelection(); + m_xDialog->response(RET_CANCEL); + } + if (!(&rButton == m_xEditButton.get() || + &rButton == m_xCreateButton.get() || + &rButton == m_xDelButton.get() || + &rButton == m_xRunButton.get() || + &rButton == m_xRenameButton.get())) + + return; + + std::unique_ptr<weld::TreeIter> xIter = m_xScriptsBox->make_iterator(); + if (!m_xScriptsBox->get_selected(xIter.get())) + return; + SFEntry* userData = weld::fromId<SFEntry*>(m_xScriptsBox->get_id(*xIter)); + if (!userData) + return; + + Reference< browse::XBrowseNode > node; + Reference< XModel > xModel; + + node = userData->GetNode(); + xModel = userData->GetModel(); + + if ( !node.is() ) + { + return; + } + + if (&rButton == m_xRunButton.get()) + { + OUString tmpString; + Reference< beans::XPropertySet > xProp( node, UNO_QUERY ); + Reference< provider::XScriptProvider > mspNode; + if( !xProp.is() ) + { + return; + } + + if ( xModel.is() ) + { + Reference< XEmbeddedScripts > xEmbeddedScripts( xModel, UNO_QUERY); + if( !xEmbeddedScripts.is() ) + { + return; + } + + if (!xEmbeddedScripts->getAllowMacroExecution()) + { + // Please FIXME: Show a message box if AllowMacroExecution is false + return; + } + } + + std::unique_ptr<weld::TreeIter> xParentIter = m_xScriptsBox->make_iterator(xIter.get()); + bool bParent = m_xScriptsBox->iter_parent(*xParentIter); + while (bParent && !mspNode.is() ) + { + SFEntry* mspUserData = weld::fromId<SFEntry*>(m_xScriptsBox->get_id(*xParentIter)); + mspNode.set( mspUserData->GetNode() , UNO_QUERY ); + bParent = m_xScriptsBox->iter_parent(*xParentIter); + } + xProp->getPropertyValue("URI") >>= tmpString; + const OUString scriptURL( tmpString ); + + if ( mspNode.is() ) + { + try + { + Reference< provider::XScript > xScript( + mspNode->getScript( scriptURL ), UNO_SET_THROW ); + + const Sequence< Any > args(0); + Sequence< sal_Int16 > outIndex; + Sequence< Any > outArgs( 0 ); + xScript->invoke( args, outIndex, outArgs ); + } + catch ( reflection::InvocationTargetException& ite ) + { + SvxScriptErrorDialog::ShowAsyncErrorDialog(m_pParent, css::uno::Any(ite)); + } + catch ( provider::ScriptFrameworkErrorException& ite ) + { + SvxScriptErrorDialog::ShowAsyncErrorDialog(m_pParent, css::uno::Any(ite)); + } + catch ( RuntimeException& re ) + { + SvxScriptErrorDialog::ShowAsyncErrorDialog(m_pParent, css::uno::Any(re)); + } + catch ( Exception& e ) + { + SvxScriptErrorDialog::ShowAsyncErrorDialog(m_pParent, css::uno::Any(e)); + } + } + StoreCurrentSelection(); + m_xDialog->response(RET_CANCEL); + } + else if ( &rButton == m_xEditButton.get() ) + { + Reference< script::XInvocation > xInv( node, UNO_QUERY ); + if ( xInv.is() ) + { + StoreCurrentSelection(); + m_xDialog->response(RET_CANCEL); + Sequence< Any > args(0); + Sequence< Any > outArgs( 0 ); + Sequence< sal_Int16 > outIndex; + try + { + // ISSUE need code to run script here + xInv->invoke( "Editable", args, outIndex, outArgs ); + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", "Caught exception trying to invoke" ); + } + } + } + else if ( &rButton == m_xCreateButton.get() ) + { + createEntry(*xIter); + } + else if ( &rButton == m_xDelButton.get() ) + { + deleteEntry(*xIter); + } + else if ( &rButton == m_xRenameButton.get() ) + { + renameEntry(*xIter); + } +} + +Reference< browse::XBrowseNode > SvxScriptOrgDialog::getBrowseNode(const weld::TreeIter& rEntry) +{ + Reference< browse::XBrowseNode > node; + SFEntry* userData = weld::fromId<SFEntry*>(m_xScriptsBox->get_id(rEntry)); + if (userData) + { + node = userData->GetNode(); + } + return node; +} + +Reference< XModel > SvxScriptOrgDialog::getModel(const weld::TreeIter& rEntry) +{ + Reference< XModel > model; + SFEntry* userData = weld::fromId<SFEntry*>(m_xScriptsBox->get_id(rEntry)); + if ( userData ) + { + model = userData->GetModel(); + } + return model; +} + +void SvxScriptOrgDialog::createEntry(const weld::TreeIter& rEntry) +{ + + Reference< browse::XBrowseNode > aChildNode; + Reference< browse::XBrowseNode > node = getBrowseNode( rEntry ); + Reference< script::XInvocation > xInv( node, UNO_QUERY ); + + if ( xInv.is() ) + { + OUString aNewName; + OUString aNewStdName; + InputDialogMode nMode = InputDialogMode::NEWLIB; + if (m_xScriptsBox->get_iter_depth(rEntry) == 0) + { + aNewStdName = "Library" ; + } + else + { + aNewStdName = "Macro" ; + nMode = InputDialogMode::NEWMACRO; + } + //do we need L10N for this? ie something like: + //String aNewStdName( ResId( STR_STDMODULENAME ) ); + bool bValid = false; + sal_Int32 i = 1; + + Sequence< Reference< browse::XBrowseNode > > childNodes; + // no children => ok to create Parcel1 or Script1 without checking + try + { + if( !node->hasChildNodes() ) + { + aNewName = aNewStdName + OUString::number(i); + bValid = true; + } + else + { + childNodes = node->getChildNodes(); + } + } + catch ( Exception& ) + { + // ignore, will continue on with empty sequence + } + + OUString extn; + while ( !bValid ) + { + aNewName = aNewStdName + OUString::number(i); + bool bFound = false; + if(childNodes.hasElements() ) + { + OUString nodeName = childNodes[0]->getName(); + sal_Int32 extnPos = nodeName.lastIndexOf( '.' ); + if(extnPos>0) + extn = nodeName.copy(extnPos); + } + for( const Reference< browse::XBrowseNode >& n : std::as_const(childNodes) ) + { + if (OUStringConcatenation(aNewName+extn) == n->getName()) + { + bFound = true; + break; + } + } + if( bFound ) + { + i++; + } + else + { + bValid = true; + } + } + + CuiInputDialog aNewDlg(m_xDialog.get(), nMode); + aNewDlg.SetObjectName(aNewName); + + do + { + if (aNewDlg.run() && !aNewDlg.GetObjectName().isEmpty()) + { + OUString aUserSuppliedName = aNewDlg.GetObjectName(); + bValid = true; + for( const Reference< browse::XBrowseNode >& n : std::as_const(childNodes) ) + { + if (OUStringConcatenation(aUserSuppliedName+extn) == n->getName()) + { + bValid = false; + OUString aError = m_createErrStr + m_createDupStr; + + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, aError)); + xErrorBox->set_title(m_createErrTitleStr); + xErrorBox->run(); + aNewDlg.SetObjectName(aNewName); + break; + } + } + if( bValid ) + aNewName = aUserSuppliedName; + } + else + { + // user hit cancel or hit OK with nothing in the editbox + + return; + } + } + while ( !bValid ); + + // open up parent node (which ensures it's loaded) + m_xScriptsBox->expand_row(rEntry); + + Sequence< Any > args{ Any(aNewName) }; + Sequence< Any > outArgs; + Sequence< sal_Int16 > outIndex; + try + { + Any aResult = xInv->invoke( "Creatable", args, outIndex, outArgs ); + Reference< browse::XBrowseNode > newNode( aResult, UNO_QUERY ); + aChildNode = newNode; + + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", "Caught exception trying to Create" ); + } + } + if ( aChildNode.is() ) + { + OUString aChildName = aChildNode->getName(); + + Reference<XModel> xDocumentModel = getModel( rEntry ); + + // ISSUE do we need to remove all entries for parent + // to achieve sort? Just need to determine position + // -- Basic doesn't do this on create. + // Suppose we could avoid this too. -> created nodes are + // not in alphabetical order + if ( aChildNode->getType() == browse::BrowseNodeTypes::SCRIPT ) + { + insertEntry(aChildName, RID_CUIBMP_MACRO, &rEntry, false, + std::make_unique<SFEntry>(aChildNode,xDocumentModel), true); + } + else + { + insertEntry(aChildName, RID_CUIBMP_LIB, &rEntry, false, + std::make_unique<SFEntry>(aChildNode,xDocumentModel), true); + + // If the Parent is not loaded then set to + // loaded, this will prevent RequestingChildren ( called + // from vcl via RequestingChildren ) from + // creating new ( duplicate ) children + SFEntry* userData = weld::fromId<SFEntry*>(m_xScriptsBox->get_id(rEntry)); + if ( userData && !userData->isLoaded() ) + { + userData->setLoaded(); + } + } + } + else + { + //ISSUE L10N & message from exception? + OUString aError( m_createErrStr ); + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, aError)); + xErrorBox->set_title(m_createErrTitleStr); + xErrorBox->run(); + } +} + +void SvxScriptOrgDialog::renameEntry(const weld::TreeIter& rEntry) +{ + + Reference< browse::XBrowseNode > aChildNode; + Reference< browse::XBrowseNode > node = getBrowseNode(rEntry); + Reference< script::XInvocation > xInv( node, UNO_QUERY ); + + if ( xInv.is() ) + { + OUString aNewName = node->getName(); + sal_Int32 extnPos = aNewName.lastIndexOf( '.' ); + if(extnPos>0) + { + aNewName = aNewName.copy(0,extnPos); + } + CuiInputDialog aNewDlg(m_xDialog.get(), InputDialogMode::RENAME); + aNewDlg.SetObjectName(aNewName); + + if (!aNewDlg.run() || aNewDlg.GetObjectName().isEmpty()) + return; // user hit cancel or hit OK with nothing in the editbox + + aNewName = aNewDlg.GetObjectName(); + + Sequence< Any > args{ Any(aNewName) }; + Sequence< Any > outArgs; + Sequence< sal_Int16 > outIndex; + try + { + Any aResult = xInv->invoke( "Renamable", args, outIndex, outArgs ); + Reference< browse::XBrowseNode > newNode( aResult, UNO_QUERY ); + aChildNode = newNode; + + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", "Caught exception trying to Rename" ); + } + } + if ( aChildNode.is() ) + { + m_xScriptsBox->set_text(rEntry, aChildNode->getName()); + m_xScriptsBox->set_cursor(rEntry); + m_xScriptsBox->select(rEntry); + + } + else + { + //ISSUE L10N & message from exception? + OUString aError( m_renameErrStr ); + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, aError)); + xErrorBox->set_title(m_renameErrTitleStr); + xErrorBox->run(); + } +} + +void SvxScriptOrgDialog::deleteEntry(const weld::TreeIter& rEntry) +{ + bool result = false; + Reference< browse::XBrowseNode > node = getBrowseNode(rEntry); + // ISSUE L10N string & can we center list? + OUString aQuery = m_delQueryStr + getListOfChildren( node, 0 ); + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Question, VclButtonsType::YesNo, aQuery)); + xQueryBox->set_title(m_delQueryTitleStr); + if (xQueryBox->run() == RET_NO) + { + return; + } + + Reference< script::XInvocation > xInv( node, UNO_QUERY ); + if ( xInv.is() ) + { + Sequence< Any > args( 0 ); + Sequence< Any > outArgs( 0 ); + Sequence< sal_Int16 > outIndex; + try + { + Any aResult = xInv->invoke( "Deletable", args, outIndex, outArgs ); + aResult >>= result; // or do we just assume true if no exception ? + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", "Caught exception trying to delete" ); + } + } + + if ( result ) + { + deleteTree(rEntry); + m_xScriptsBox->remove(rEntry); + } + else + { + //ISSUE L10N & message from exception? + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, m_delErrStr)); + xErrorBox->set_title(m_delErrTitleStr); + xErrorBox->run(); + } + +} + +bool SvxScriptOrgDialog::getBoolProperty( Reference< beans::XPropertySet > const & xProps, + OUString const & propName ) +{ + bool result = false; + try + { + xProps->getPropertyValue( propName ) >>= result; + } + catch ( Exception& ) + { + return result; + } + return result; +} + +OUString SvxScriptOrgDialog::getListOfChildren( const Reference< browse::XBrowseNode >& node, int depth ) +{ + OUStringBuffer result = "\n"; + for( int i=0;i<=depth;i++ ) + { + result.append("\t"); + } + result.append(node->getName()); + + try + { + if ( node->hasChildNodes() ) + { + const Sequence< Reference< browse::XBrowseNode > > children + = node->getChildNodes(); + for( const Reference< browse::XBrowseNode >& n : children ) + { + result.append( getListOfChildren( n , depth+1 ) ); + } + } + } + catch ( Exception& ) + { + // ignore, will return an empty string + } + + return result.makeStringAndClear(); +} + +Selection_hash SvxScriptOrgDialog::m_lastSelection; + +void SvxScriptOrgDialog::StoreCurrentSelection() +{ + std::unique_ptr<weld::TreeIter> xIter = m_xScriptsBox->make_iterator(); + if (!m_xScriptsBox->get_selected(xIter.get())) + return; + OUString aDescription; + bool bEntry; + do + { + aDescription = m_xScriptsBox->get_text(*xIter) + aDescription; + bEntry = m_xScriptsBox->iter_parent(*xIter); + if (bEntry) + aDescription = ";" + aDescription; + } + while (bEntry); + OUString sDesc( aDescription ); + m_lastSelection[ m_sLanguage ] = sDesc; +} + +void SvxScriptOrgDialog::RestorePreviousSelection() +{ + OUString aStoredEntry = m_lastSelection[ m_sLanguage ]; + if( aStoredEntry.isEmpty() ) + return; + std::unique_ptr<weld::TreeIter> xEntry; + std::unique_ptr<weld::TreeIter> xTmpEntry(m_xScriptsBox->make_iterator()); + sal_Int32 nIndex = 0; + while (nIndex != -1) + { + std::u16string_view aTmp( o3tl::getToken(aStoredEntry, 0, ';', nIndex ) ); + + bool bTmpEntry; + if (!xEntry) + { + xEntry = m_xScriptsBox->make_iterator(); + bTmpEntry = m_xScriptsBox->get_iter_first(*xEntry); + m_xScriptsBox->copy_iterator(*xEntry, *xTmpEntry); + } + else + { + m_xScriptsBox->copy_iterator(*xEntry, *xTmpEntry); + bTmpEntry = m_xScriptsBox->iter_children(*xTmpEntry); + } + + while (bTmpEntry) + { + if (m_xScriptsBox->get_text(*xTmpEntry) == aTmp) + { + m_xScriptsBox->copy_iterator(*xTmpEntry, *xEntry); + break; + } + bTmpEntry = m_xScriptsBox->iter_next_sibling(*xTmpEntry); + } + + if (!bTmpEntry) + break; + + m_xScriptsBox->expand_row(*xEntry); + } + + if (xEntry) + { + m_xScriptsBox->set_cursor(*xEntry); + ScriptSelectHdl(*m_xScriptsBox); + } +} + +namespace { + +OUString ReplaceString( + const OUString& source, + const OUString& token, + std::u16string_view value ) +{ + sal_Int32 pos = source.indexOf( token ); + + if ( pos != -1 && !value.empty() ) + { + return source.replaceAt( pos, token.getLength(), value ); + } + else + { + return source; + } +} + +OUString FormatErrorString( + const OUString& unformatted, + std::u16string_view language, + std::u16string_view script, + std::u16string_view line, + std::u16string_view type, + std::u16string_view message ) +{ + OUString result = unformatted; + + result = ReplaceString(result, "%LANGUAGENAME", language ); + result = ReplaceString(result, "%SCRIPTNAME", script ); + result = ReplaceString(result, "%LINENUMBER", line ); + + if ( !type.empty() ) + { + result += "\n\n" + CuiResId(RID_CUISTR_ERROR_TYPE_LABEL) + " " + type; + } + + if ( !message.empty() ) + { + result += "\n\n" + CuiResId(RID_CUISTR_ERROR_MESSAGE_LABEL) + " " + message; + } + + return result; +} + +OUString GetErrorMessage( + const provider::ScriptErrorRaisedException& eScriptError ) +{ + OUString unformatted = CuiResId( RID_CUISTR_ERROR_AT_LINE ); + + OUString unknown("UNKNOWN"); + OUString language = unknown; + OUString script = unknown; + OUString line = unknown; + OUString message = eScriptError.Message; + + if ( !eScriptError.language.isEmpty() ) + { + language = eScriptError.language; + } + + if ( !eScriptError.scriptName.isEmpty() ) + { + script = eScriptError.scriptName; + } + + if ( !eScriptError.Message.isEmpty() ) + { + message = eScriptError.Message; + } + if ( eScriptError.lineNum != -1 ) + { + line = OUString::number( eScriptError.lineNum ); + unformatted = CuiResId( RID_CUISTR_ERROR_AT_LINE ); + } + else + { + unformatted = CuiResId( RID_CUISTR_ERROR_RUNNING ); + } + + return FormatErrorString( + unformatted, language, script, line, u"", message ); +} + +OUString GetErrorMessage( + const provider::ScriptExceptionRaisedException& eScriptException ) +{ + OUString unformatted = CuiResId( RID_CUISTR_EXCEPTION_AT_LINE ); + + OUString unknown("UNKNOWN"); + OUString language = unknown; + OUString script = unknown; + OUString line = unknown; + OUString type = unknown; + OUString message = eScriptException.Message; + + if ( !eScriptException.language.isEmpty() ) + { + language = eScriptException.language; + } + if ( !eScriptException.scriptName.isEmpty() ) + { + script = eScriptException.scriptName; + } + + if ( !eScriptException.Message.isEmpty() ) + { + message = eScriptException.Message; + } + + if ( eScriptException.lineNum != -1 ) + { + line = OUString::number( eScriptException.lineNum ); + unformatted = CuiResId( RID_CUISTR_EXCEPTION_AT_LINE ); + } + else + { + unformatted = CuiResId( RID_CUISTR_EXCEPTION_RUNNING ); + } + + if ( !eScriptException.exceptionType.isEmpty() ) + { + type = eScriptException.exceptionType; + } + + return FormatErrorString( + unformatted, language, script, line, type, message ); + +} +OUString GetErrorMessage( + const provider::ScriptFrameworkErrorException& sError ) +{ + OUString unformatted = CuiResId( RID_CUISTR_FRAMEWORK_ERROR_RUNNING ); + + OUString language("UNKNOWN"); + + OUString script("UNKNOWN"); + + OUString message; + + if ( !sError.scriptName.isEmpty() ) + { + script = sError.scriptName; + } + if ( !sError.language.isEmpty() ) + { + language = sError.language; + } + if ( sError.errorType == provider::ScriptFrameworkErrorType::NOTSUPPORTED ) + { + message = CuiResId(RID_CUISTR_ERROR_LANG_NOT_SUPPORTED); + message = ReplaceString(message, "%LANGUAGENAME", language ); + + } + else + { + message = sError.Message; + } + return FormatErrorString( + unformatted, language, script, u"", std::u16string_view(), message ); +} + +OUString GetErrorMessage( const css::uno::Any& aException ) +{ + if ( aException.getValueType() == + cppu::UnoType<reflection::InvocationTargetException>::get()) + { + reflection::InvocationTargetException ite; + aException >>= ite; + if ( ite.TargetException.getValueType() == cppu::UnoType<provider::ScriptErrorRaisedException>::get()) + { + // Error raised by script + provider::ScriptErrorRaisedException scriptError; + ite.TargetException >>= scriptError; + return GetErrorMessage( scriptError ); + } + else if ( ite.TargetException.getValueType() == cppu::UnoType<provider::ScriptExceptionRaisedException>::get()) + { + // Exception raised by script + provider::ScriptExceptionRaisedException scriptException; + ite.TargetException >>= scriptException; + return GetErrorMessage( scriptException ); + } + else + { + // Unknown error, shouldn't happen + // OSL_ASSERT(...) + } + + } + else if ( aException.getValueType() == cppu::UnoType<provider::ScriptFrameworkErrorException>::get()) + { + // A Script Framework error has occurred + provider::ScriptFrameworkErrorException sfe; + aException >>= sfe; + return GetErrorMessage( sfe ); + + } + // unknown exception + auto msg = aException.getValueTypeName(); + Exception e; + if ( (aException >>= e) && !e.Message.isEmpty() ) + { + msg += ": " + e.Message; + } + return msg; +} + +} + +// Show Error dialog asynchronously +void SvxScriptErrorDialog::ShowAsyncErrorDialog( weld::Window* pParent, css::uno::Any const & aException ) +{ + SolarMutexGuard aGuard; + OUString sMessage = GetErrorMessage( aException ); + + // Pass a copy of the message to the ShowDialog method as the + // SvxScriptErrorDialog may be deleted before ShowDialog is called + DialogData* pData = new DialogData; + pData->sMessage = sMessage; + pData->pParent = pParent; + Application::PostUserEvent( + LINK( nullptr, SvxScriptErrorDialog, ShowDialog ), + pData ); +} + +IMPL_STATIC_LINK( SvxScriptErrorDialog, ShowDialog, void*, p, void ) +{ + std::unique_ptr<DialogData> xData(static_cast<DialogData*>(p)); + OUString message = xData->sMessage; + + if ( message.isEmpty() ) + message = CuiResId( RID_CUISTR_ERROR_TITLE ); + + std::shared_ptr<weld::MessageDialog> xBox; + xBox.reset(Application::CreateMessageDialog( + xData->pParent, + VclMessageType::Warning, + VclButtonsType::Ok, + message, + comphelper::LibreOfficeKit::isActive())); + + xBox->set_title(CuiResId(RID_CUISTR_ERROR_TITLE)); + + xBox->runAsync(xBox, [](sal_Int32 /*nResult*/) {}); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/sdrcelldlg.cxx b/cui/source/dialogs/sdrcelldlg.cxx new file mode 100644 index 000000000..fda8b4ce1 --- /dev/null +++ b/cui/source/dialogs/sdrcelldlg.cxx @@ -0,0 +1,69 @@ +/* -*- 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 <sdrcelldlg.hxx> +#include <cuitabarea.hxx> +#include <svx/svdmodel.hxx> +#include <border.hxx> +#include <svx/dialogs.hrc> + +SvxFormatCellsDialog::SvxFormatCellsDialog(weld::Window* pParent, const SfxItemSet* pAttr, const SdrModel& rModel) + : SfxTabDialogController(pParent, "cui/ui/formatcellsdialog.ui", "FormatCellsDialog", pAttr) + , mrOutAttrs(*pAttr) + , mpColorTab(rModel.GetColorList()) + , mnColorTabState ( ChangeType::NONE ) + , mpGradientList(rModel.GetGradientList()) + , mpHatchingList(rModel.GetHatchList()) + , mpBitmapList(rModel.GetBitmapList()) + , mpPatternList(rModel.GetPatternList()) +{ + AddTabPage("name", RID_SVXPAGE_CHAR_NAME); + AddTabPage("effects", RID_SVXPAGE_CHAR_EFFECTS); + AddTabPage("border", RID_SVXPAGE_BORDER ); + AddTabPage("area", RID_SVXPAGE_AREA); + AddTabPage("shadow", SvxShadowTabPage::Create, nullptr); +} + +void SvxFormatCellsDialog::PageCreated(const OString& rId, SfxTabPage &rPage) +{ + if (rId == "area") + { + SvxAreaTabPage& rAreaPage = static_cast<SvxAreaTabPage&>(rPage); + rAreaPage.SetColorList( mpColorTab ); + rAreaPage.SetGradientList( mpGradientList ); + rAreaPage.SetHatchingList( mpHatchingList ); + rAreaPage.SetBitmapList( mpBitmapList ); + rAreaPage.SetPatternList( mpPatternList ); + rAreaPage.ActivatePage( mrOutAttrs ); + } + else if (rId == "border") + { + SvxBorderTabPage& rBorderPage = static_cast<SvxBorderTabPage&>(rPage); + rBorderPage.SetTableMode(); + } + else if (rId == "shadow") + { + static_cast<SvxShadowTabPage&>(rPage).SetColorList( mpColorTab ); + static_cast<SvxShadowTabPage&>(rPage).SetColorChgd( &mnColorTabState ); + } + else + SfxTabDialogController::PageCreated(rId, rPage); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/showcols.cxx b/cui/source/dialogs/showcols.cxx new file mode 100644 index 000000000..1e8485513 --- /dev/null +++ b/cui/source/dialogs/showcols.cxx @@ -0,0 +1,109 @@ +/* -*- 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 <showcols.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <comphelper/types.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> + +constexpr OUStringLiteral CUIFM_PROP_HIDDEN = u"Hidden"; +constexpr OUStringLiteral CUIFM_PROP_LABEL = u"Label"; + +FmShowColsDialog::FmShowColsDialog(weld::Window* pParent) + : GenericDialogController(pParent, "cui/ui/showcoldialog.ui", "ShowColDialog") + , m_xList(m_xBuilder->weld_tree_view("treeview")) + , m_xOK(m_xBuilder->weld_button("ok")) +{ + m_xList->set_size_request(m_xList->get_approximate_digit_width() * 40, + m_xList->get_height_rows(8)); + m_xList->set_selection_mode(SelectionMode::Multiple); + m_xOK->connect_clicked(LINK(this, FmShowColsDialog, OnClickedOk)); +} + +FmShowColsDialog::~FmShowColsDialog() {} + +IMPL_LINK_NOARG(FmShowColsDialog, OnClickedOk, weld::Button&, void) +{ + DBG_ASSERT( + m_xColumns.is(), + "FmShowColsDialog::OnClickedOk : you should call SetColumns before executing the dialog !"); + if (m_xColumns.is()) + { + css::uno::Reference<css::beans::XPropertySet> xCol; + auto nSelectedRows = m_xList->get_selected_rows(); + for (auto i : nSelectedRows) + { + m_xColumns->getByIndex(m_xList->get_id(i).toInt32()) >>= xCol; + if (xCol.is()) + { + try + { + xCol->setPropertyValue(CUIFM_PROP_HIDDEN, css::uno::Any(false)); + } + catch (...) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", + "FmShowColsDialog::OnClickedOk Exception occurred!"); + } + } + } + } + + m_xDialog->response(RET_OK); +} + +void FmShowColsDialog::SetColumns(const css::uno::Reference<css::container::XIndexContainer>& xCols) +{ + DBG_ASSERT(xCols.is(), "FmShowColsDialog::SetColumns : invalid columns !"); + if (!xCols.is()) + return; + m_xColumns = xCols.get(); + + m_xList->clear(); + + css::uno::Reference<css::beans::XPropertySet> xCurCol; + OUString sCurName; + for (sal_Int32 i = 0; i < xCols->getCount(); ++i) + { + sCurName.clear(); + xCurCol.set(xCols->getByIndex(i), css::uno::UNO_QUERY); + bool bIsHidden = false; + try + { + css::uno::Any aHidden = xCurCol->getPropertyValue(CUIFM_PROP_HIDDEN); + bIsHidden = ::comphelper::getBOOL(aHidden); + + OUString sName; + xCurCol->getPropertyValue(CUIFM_PROP_LABEL) >>= sName; + sCurName = sName; + } + catch (...) + { + TOOLS_WARN_EXCEPTION("cui.dialogs", "FmShowColsDialog::SetColumns Exception occurred!"); + } + + // if the col is hidden, put it into the list + if (bIsHidden) + m_xList->append(OUString::number(i), sCurName); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/signature-line-draw.svg b/cui/source/dialogs/signature-line-draw.svg new file mode 100644 index 000000000..b8552c41b --- /dev/null +++ b/cui/source/dialogs/signature-line-draw.svg @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.2" width="90mm" height="45mm" viewBox="0 0 9000 4500" preserveAspectRatio="xMidYMid" xml:space="preserve" id="svg577" inkscape:version="0.92.2 5c3e80d, 2017-08-06" sodipodi:docname="test.svg" stroke-linejoin="round" stroke-width="28.222" fill-rule="evenodd"> + <metadata id="metadata581"> + <rdf:RDF> + <cc:Work rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> + <dc:title/> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" inkscape:window-height="1015" id="namedview579" showgrid="false" inkscape:zoom="3.6100926" inkscape:cx="133.43144" inkscape:cy="97.595557" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="g569" inkscape:pagecheckerboard="false" units="cm"/> + <defs class="ClipPathGroup" id="defs8"> + <clipPath id="presentation_clip_path" clipPathUnits="userSpaceOnUse"> + <rect x="0" y="0" width="9000" height="4500" id="rect2"/> + </clipPath> + </defs> + <defs id="defs49"/> + <defs id="defs86"/> + <defs class="TextShapeIndex" id="defs90"/> + <defs class="EmbeddedBulletChars" id="defs122"/> + <defs class="TextEmbeddedBitmaps" id="defs124"/> + <g id="g129"> + <g id="id2" class="Master_Slide"> + <g id="bg-id2" class="Background"/> + <g id="bo-id2" class="BackgroundObjects"/> + </g> + </g> + <g class="SlideGroup" id="g575"> + <path inkscape:connector-curvature="0" style="fill:none;stroke:#ffcccc;stroke-width:355.69763184;stroke-linecap:round;stroke-linejoin:round" d="M 2677.6009,4322.3785 C 4637.7107,2683.4298 4480.4998,1439.2788 4416.8177,177.68919 4135.9768,2203.3862 5181.4191,2928.0647 6822.3029,3068.6459 5584.8956,2678.4757 4273.5892,2702.5453 2677.6009,4322.3785 Z" id="path8"/> + <g id="g569" class="Page"><text xml:space="preserve" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:493.88882446px;line-height:1.25;font-family:'Liberation Sans', sans-serif;-inkscape-font-specification:'Liberation Sans, sans-serif';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:746.70703125" x="265.50031" y="877.50842" id="text31-3"><tspan sodipodi:role="line" id="tspan29-6" x="265.50031" y="877.50842" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:493.88882446px;font-family:'Liberation Sans', sans-serif;-inkscape-font-specification:'Liberation Sans, sans-serif';stroke-width:746.70703125">[SIGNED_BY]</tspan></text> +<text xml:space="preserve" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:493.88879395px;line-height:1.25;font-family:'Liberation Sans', sans-serif;-inkscape-font-specification:'Liberation Sans, sans-serif';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:746.70697021" x="228.1967" y="2377.5085" id="text31-3-3"><tspan sodipodi:role="line" id="tspan29-6-5" x="228.1967" y="2377.5085" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:493.88879395px;font-family:'Liberation Sans', sans-serif;-inkscape-font-specification:'Liberation Sans, sans-serif';stroke-width:746.70697021">[SIGNER_NAME]</tspan></text> +<text xml:space="preserve" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:493.6913147px;line-height:1.25;font-family:'Liberation Sans', sans-serif;-inkscape-font-specification:'Liberation Sans, sans-serif';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:746.40844727" x="206.12776" y="3879.1411" id="text31-3-3-9" transform="scale(1.0003999,0.99960029)"><tspan sodipodi:role="line" id="tspan29-6-5-1" x="206.12776" y="3879.1411" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:493.6913147px;font-family:'Liberation Sans', sans-serif;-inkscape-font-specification:'Liberation Sans, sans-serif';stroke-width:746.40844727">[DATE]</tspan></text> +</g> + </g> +</svg> diff --git a/cui/source/dialogs/signature-line.svg b/cui/source/dialogs/signature-line.svg new file mode 100644 index 000000000..7ad3fad30 --- /dev/null +++ b/cui/source/dialogs/signature-line.svg @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.2" width="90mm" height="45mm" viewBox="0 0 9000 4500" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xml:space="preserve" id="svg577" inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"> + <metadata id="metadata581"> + <rdf:RDF> + <cc:Work rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> + <dc:title/> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1865" inkscape:window-height="1145" id="namedview579" showgrid="false" inkscape:zoom="3.6100926" inkscape:cx="231.72208" inkscape:cy="122.80267" inkscape:window-x="55" inkscape:window-y="27" inkscape:window-maximized="1" inkscape:current-layer="g575" inkscape:pagecheckerboard="false"/> + <defs class="ClipPathGroup" id="defs8"> + <clipPath id="presentation_clip_path" clipPathUnits="userSpaceOnUse"> + <rect x="0" y="0" width="9000" height="4500" id="rect2"/> + </clipPath> + </defs> + <defs id="defs49"/> + <defs id="defs86"/> + <defs class="TextShapeIndex" id="defs90"/> + <defs class="EmbeddedBulletChars" id="defs122"/> + <defs class="TextEmbeddedBitmaps" id="defs124"/> + <g id="g129"> + <g id="id2" class="Master_Slide"> + <g id="bg-id2" class="Background"/> + <g id="bo-id2" class="BackgroundObjects"/> + </g> + </g> + <g class="SlideGroup" id="g575"><g id="g573"><g id="container-id1"><g id="id1" class="Slide" clip-path="url(#presentation_clip_path)"><g class="Page" id="g569"><g class="com.sun.star.drawing.LineShape" id="g154"><g id="id3"><rect class="BoundingBox" stroke="none" fill="none" x="-27" y="2373" width="9055" height="55" id="rect131"/><desc id="desc133">150</desc><desc id="desc135">139</desc><desc id="desc137">132</desc><desc id="desc139">512: XPATHSTROKE_SEQ_BEGIN</desc><desc id="desc141">132</desc><desc id="desc143">133</desc><desc id="desc145">109</desc><path fill="none" stroke="rgb(0,0,0)" stroke-width="53" stroke-linejoin="round" d="M 0,2400 L 9000,2400" id="path147"/><desc id="desc149">512: XPATHSTROKE_SEQ_END</desc><desc id="desc151">140</desc></g></g><g class="com.sun.star.drawing.ClosedBezierShape" id="g173"><g id="id4"><rect class="BoundingBox" stroke="none" fill="none" x="301" y="1400" width="801" height="801" id="rect156"/><desc id="desc158">150</desc><desc id="desc160">139</desc><desc id="desc162">133</desc><desc id="desc164">132</desc><desc id="desc166">111</desc><path fill="rgb(0,0,0)" stroke="none" d="M 969,2200 C 880,2083 792,1967 704,1850 614,1967 523,2083 433,2200 389,2200 345,2200 301,2200 413,2061 525,1923 637,1784 533,1656 430,1528 327,1400 371,1400 415,1400 459,1400 541,1505 623,1609 704,1714 784,1609 863,1505 943,1400 987,1400 1031,1400 1075,1400 975,1527 874,1653 773,1780 882,1920 992,2060 1101,2200 1057,2200 1013,2200 969,2200 Z" id="path168"/><desc id="desc170">140</desc></g></g><g class="com.sun.star.drawing.TextShape" id="g236"><g id="id5"><rect class="BoundingBox" stroke="none" fill="none" x="1300" y="1500" width="8001" height="925" id="rect175"/><desc id="desc177">150</desc><desc id="desc179">512: XTEXT_PAINTSHAPE_BEGIN</desc><text class="TextShape" id="text233"><desc class="Paragraph" id="desc181"/><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="600px" font-weight="400" id="tspan231"><desc id="desc183">138</desc><desc id="desc185">136</desc><desc id="desc187">135</desc><desc id="desc189">134</desc><desc id="desc191">113</desc><desc class="TextPortion" id="desc193">type: Text; content: [SIGNATURE]; </desc><tspan class="TextPosition" x="1550" y="2171" id="tspan229"><tspan fill="rgb(0,0,0)" stroke="none" id="tspan195">[SIGNATURE]</tspan><desc id="desc197">512: XTEXT_EOC</desc><desc id="desc199">512: XTEXT_EOC</desc><desc id="desc201">512: XTEXT_EOW</desc><desc id="desc203">512: XTEXT_EOC</desc><desc id="desc205">512: XTEXT_EOC</desc><desc id="desc207">512: XTEXT_EOC</desc><desc id="desc209">512: XTEXT_EOC</desc><desc id="desc211">512: XTEXT_EOC</desc><desc id="desc213">512: XTEXT_EOC</desc><desc id="desc215">512: XTEXT_EOC</desc><desc id="desc217">512: XTEXT_EOC</desc><desc id="desc219">512: XTEXT_EOC</desc><desc id="desc221">512: XTEXT_EOW</desc><desc id="desc223">512: XTEXT_EOL</desc><desc id="desc225">512: XTEXT_EOP</desc><desc id="desc227">512: XTEXT_PAINTSHAPE_END</desc></tspan></tspan></text></g></g><g class="com.sun.star.drawing.TextShape" id="g303"><g id="id6"><rect class="BoundingBox" stroke="none" fill="none" x="100" y="2500" width="8901" height="726" id="rect238"/><desc id="desc240">150</desc><desc id="desc242">512: XTEXT_PAINTSHAPE_BEGIN</desc><text class="TextShape" id="text300"><desc class="Paragraph" id="desc244"/><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400" id="tspan298"><desc id="desc246">138</desc><desc id="desc248">136</desc><desc id="desc250">135</desc><desc id="desc252">134</desc><desc id="desc254">113</desc><desc class="TextPortion" id="desc256">type: Text; content: [SIGNER_NAME]; </desc><tspan class="TextPosition" x="350" y="3010" id="tspan296"><tspan fill="rgb(0,0,0)" stroke="none" id="tspan258">[SIGNER_NAME]</tspan><desc id="desc260">512: XTEXT_EOC</desc><desc id="desc262">512: XTEXT_EOC</desc><desc id="desc264">512: XTEXT_EOW</desc><desc id="desc266">512: XTEXT_EOC</desc><desc id="desc268">512: XTEXT_EOC</desc><desc id="desc270">512: XTEXT_EOC</desc><desc id="desc272">512: XTEXT_EOC</desc><desc id="desc274">512: XTEXT_EOC</desc><desc id="desc276">512: XTEXT_EOC</desc><desc id="desc278">512: XTEXT_EOC</desc><desc id="desc280">512: XTEXT_EOC</desc><desc id="desc282">512: XTEXT_EOC</desc><desc id="desc284">512: XTEXT_EOC</desc><desc id="desc286">512: XTEXT_EOC</desc><desc id="desc288">512: XTEXT_EOW</desc><desc id="desc290">512: XTEXT_EOL</desc><desc id="desc292">512: XTEXT_EOP</desc><desc id="desc294">512: XTEXT_PAINTSHAPE_END</desc></tspan></tspan></text></g></g><g class="com.sun.star.drawing.TextShape" id="g372"><g id="id7"><rect class="BoundingBox" stroke="none" fill="none" x="100" y="3075" width="8901" height="726" id="rect305"/><desc id="desc307">150</desc><desc id="desc309">512: XTEXT_PAINTSHAPE_BEGIN</desc><text class="TextShape" id="text369"><desc class="Paragraph" id="desc311"/><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400" id="tspan367"><desc id="desc313">138</desc><desc id="desc315">136</desc><desc id="desc317">135</desc><desc id="desc319">134</desc><desc id="desc321">113</desc><desc class="TextPortion" id="desc323">type: Text; content: [SIGNER_TITLE]; </desc><tspan class="TextPosition" x="350" y="3585" id="tspan365"><tspan fill="rgb(0,0,0)" stroke="none" id="tspan325">[SIGNER_TITLE]</tspan><desc id="desc327">512: XTEXT_EOC</desc><desc id="desc329">512: XTEXT_EOC</desc><desc id="desc331">512: XTEXT_EOW</desc><desc id="desc333">512: XTEXT_EOC</desc><desc id="desc335">512: XTEXT_EOC</desc><desc id="desc337">512: XTEXT_EOC</desc><desc id="desc339">512: XTEXT_EOC</desc><desc id="desc341">512: XTEXT_EOC</desc><desc id="desc343">512: XTEXT_EOC</desc><desc id="desc345">512: XTEXT_EOC</desc><desc id="desc347">512: XTEXT_EOC</desc><desc id="desc349">512: XTEXT_EOC</desc><desc id="desc351">512: XTEXT_EOC</desc><desc id="desc353">512: XTEXT_EOC</desc><desc id="desc355">512: XTEXT_EOC</desc><desc id="desc357">512: XTEXT_EOW</desc><desc id="desc359">512: XTEXT_EOL</desc><desc id="desc361">512: XTEXT_EOP</desc><desc id="desc363">512: XTEXT_PAINTSHAPE_END</desc></tspan></tspan></text></g></g><g class="com.sun.star.drawing.TextShape" id="g435"><g id="id8"><rect class="BoundingBox" stroke="none" fill="none" x="100" y="3660" width="8901" height="726" id="rect374"/><desc id="desc376">150</desc><desc id="desc378">512: XTEXT_PAINTSHAPE_BEGIN</desc><text class="TextShape" id="text432"><desc class="Paragraph" id="desc380"/><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400" id="tspan430"><desc id="desc382">138</desc><desc id="desc384">136</desc><desc id="desc386">135</desc><desc id="desc388">134</desc><desc id="desc390">113</desc><desc class="TextPortion" id="desc392">type: Text; content: [SIGNED_BY]; </desc><tspan class="TextPosition" x="350" y="4170" id="tspan428"><tspan fill="rgb(0,0,0)" stroke="none" id="tspan394">[SIGNED_BY]</tspan><desc id="desc396">512: XTEXT_EOC</desc><desc id="desc398">512: XTEXT_EOC</desc><desc id="desc400">512: XTEXT_EOW</desc><desc id="desc402">512: XTEXT_EOC</desc><desc id="desc404">512: XTEXT_EOC</desc><desc id="desc406">512: XTEXT_EOC</desc><desc id="desc408">512: XTEXT_EOC</desc><desc id="desc410">512: XTEXT_EOC</desc><desc id="desc412">512: XTEXT_EOC</desc><desc id="desc414">512: XTEXT_EOC</desc><desc id="desc416">512: XTEXT_EOC</desc><desc id="desc418">512: XTEXT_EOC</desc><desc id="desc420">512: XTEXT_EOW</desc><desc id="desc422">512: XTEXT_EOL</desc><desc id="desc424">512: XTEXT_EOP</desc><desc id="desc426">512: XTEXT_PAINTSHAPE_END</desc></tspan></tspan></text></g></g><g class="com.sun.star.drawing.TextShape" id="g488"><g id="id9"><rect class="BoundingBox" stroke="none" fill="none" x="4800" y="0" width="4201" height="726" id="rect437"/><desc id="desc439">150</desc><desc id="desc441">512: XTEXT_PAINTSHAPE_BEGIN</desc><text class="TextShape" id="text485" x="2648.345" y="0" style="text-align:end;text-anchor:end"><desc class="Paragraph" id="desc443"/><tspan class="TextParagraph" font-size="423px" font-weight="400" id="tspan483" style="font-weight:400;font-size:423px;font-family:'Liberation Sans', sans-serif;text-align:end;text-anchor:end"><desc id="desc445">138</desc><desc id="desc447">136</desc><desc id="desc449">135</desc><desc id="desc451">134</desc><desc id="desc453">113</desc><desc class="TextPortion" id="desc455">type: Text; content: [DATE]; </desc><tspan class="TextPosition" x="8792.835" y="510" id="tspan481" style="text-align:end;text-anchor:end"><tspan id="tspan457" style="text-align:end;text-anchor:end;fill:#000000;stroke:none">[DATE]</tspan><desc id="desc459">512: XTEXT_EOC</desc><desc id="desc461">512: XTEXT_EOC</desc><desc id="desc463">512: XTEXT_EOW</desc><desc id="desc465">512: XTEXT_EOC</desc><desc id="desc467">512: XTEXT_EOC</desc><desc id="desc469">512: XTEXT_EOC</desc><desc id="desc471">512: XTEXT_EOC</desc><desc id="desc473">512: XTEXT_EOW</desc><desc id="desc475">512: XTEXT_EOL</desc><desc id="desc477">512: XTEXT_EOP</desc><desc id="desc479">512: XTEXT_PAINTSHAPE_END</desc></tspan></tspan></text></g></g><g class="com.sun.star.drawing.TextShape" id="g567"><g id="id10"><rect class="BoundingBox" stroke="none" fill="none" x="0" y="1" width="9001" height="726" id="rect490"/><desc id="desc492">150</desc><desc id="desc494">512: XTEXT_PAINTSHAPE_BEGIN</desc><text class="TextShape" id="text564"><desc class="Paragraph" id="desc496"/><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="700" id="tspan562"><desc id="desc498">138</desc><desc id="desc500">136</desc><desc id="desc502">135</desc><desc id="desc504">134</desc><desc id="desc506">113</desc><desc class="TextPortion" id="desc508">type: Text; content: [INVALID_SIGNATURE]; </desc><tspan class="TextPosition" x="2180" y="511" id="tspan560"><tspan fill="rgb(239,65,61)" stroke="none" id="tspan510">[INVALID_SIGNATURE]</tspan><desc id="desc512">512: XTEXT_EOC</desc><desc id="desc514">512: XTEXT_EOC</desc><desc id="desc516">512: XTEXT_EOW</desc><desc id="desc518">512: XTEXT_EOC</desc><desc id="desc520">512: XTEXT_EOC</desc><desc id="desc522">512: XTEXT_EOC</desc><desc id="desc524">512: XTEXT_EOC</desc><desc id="desc526">512: XTEXT_EOC</desc><desc id="desc528">512: XTEXT_EOC</desc><desc id="desc530">512: XTEXT_EOC</desc><desc id="desc532">512: XTEXT_EOC</desc><desc id="desc534">512: XTEXT_EOC</desc><desc id="desc536">512: XTEXT_EOC</desc><desc id="desc538">512: XTEXT_EOC</desc><desc id="desc540">512: XTEXT_EOC</desc><desc id="desc542">512: XTEXT_EOC</desc><desc id="desc544">512: XTEXT_EOC</desc><desc id="desc546">512: XTEXT_EOC</desc><desc id="desc548">512: XTEXT_EOC</desc><desc id="desc550">512: XTEXT_EOC</desc><desc id="desc552">512: XTEXT_EOW</desc><desc id="desc554">512: XTEXT_EOL</desc><desc id="desc556">512: XTEXT_EOP</desc><desc id="desc558">512: XTEXT_PAINTSHAPE_END</desc></tspan></tspan></text></g></g></g></g></g></g>[SIGNATURE_IMAGE]</g> +</svg> diff --git a/cui/source/dialogs/splitcelldlg.cxx b/cui/source/dialogs/splitcelldlg.cxx new file mode 100644 index 000000000..14146b44c --- /dev/null +++ b/cui/source/dialogs/splitcelldlg.cxx @@ -0,0 +1,114 @@ +/* -*- 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 <splitcelldlg.hxx> + +SvxSplitTableDlg::SvxSplitTableDlg(weld::Window *pParent, bool bIsTableVertical, tools::Long nMaxVertical, tools::Long nMaxHorizontal) + : GenericDialogController(pParent, "cui/ui/splitcellsdialog.ui", "SplitCellsDialog") + , m_xCountEdit(m_xBuilder->weld_spin_button("countnf")) + , m_xHorzBox(!bIsTableVertical ? m_xBuilder->weld_radio_button("hori") : m_xBuilder->weld_radio_button("vert")) + , m_xVertBox(!bIsTableVertical ? m_xBuilder->weld_radio_button("vert") : m_xBuilder->weld_radio_button("hori")) + , m_xPropCB(m_xBuilder->weld_check_button("prop")) + , mnMaxVertical(nMaxVertical) + , mnMaxHorizontal(nMaxHorizontal) +{ + m_xHorzBox->connect_toggled(LINK(this, SvxSplitTableDlg, ToggleHdl)); + m_xVertBox->connect_toggled(LINK(this, SvxSplitTableDlg, ToggleHdl)); + + if (mnMaxVertical < 2) + { + if (!bIsTableVertical) + m_xVertBox->set_sensitive(false); + else + m_xHorzBox->set_sensitive(false); + } + + //exchange the meaning of horizontal and vertical for vertical text + if (bIsTableVertical) + { + int nHorzTopAttach = m_xHorzBox->get_grid_top_attach(); + int nVertTopAttach = m_xVertBox->get_grid_top_attach(); + m_xHorzBox->set_grid_top_attach(nVertTopAttach); + m_xVertBox->set_grid_top_attach(nHorzTopAttach); + m_xHorzBox->set_active(m_xVertBox->get_active()); + } +} + +IMPL_LINK(SvxSplitTableDlg, ToggleHdl, weld::Toggleable&, rButton, void) +{ + if (!rButton.get_active()) + return; + const bool bIsVert = m_xVertBox->get_active(); + tools::Long nMax = bIsVert ? mnMaxVertical : mnMaxHorizontal; + m_xPropCB->set_sensitive(!bIsVert); + m_xCountEdit->set_max(nMax); +} + +bool SvxSplitTableDlg::IsHorizontal() const +{ + return m_xHorzBox->get_active(); +} + +bool SvxSplitTableDlg::IsProportional() const +{ + return m_xPropCB->get_active() && m_xHorzBox->get_active(); +} + +tools::Long SvxSplitTableDlg::GetCount() const +{ + return m_xCountEdit->get_value(); +} + +void SvxSplitTableDlg::SetSplitVerticalByDefault() +{ + if( mnMaxVertical >= 2 ) + m_xVertBox->set_active(true); // tdf#60242 +} + +bool SvxAbstractSplitTableDialog_Impl::IsHorizontal() const +{ + return m_xDlg->IsHorizontal(); +} + +bool SvxAbstractSplitTableDialog_Impl::IsProportional() const +{ + return m_xDlg->IsProportional(); +} + +tools::Long SvxAbstractSplitTableDialog_Impl::GetCount() const +{ + return m_xDlg->GetCount(); +} + +void SvxAbstractSplitTableDialog_Impl::SetSplitVerticalByDefault() +{ + m_xDlg->SetSplitVerticalByDefault(); +} + +short SvxAbstractSplitTableDialog_Impl::Execute() +{ + return m_xDlg->run(); +} + +bool SvxAbstractSplitTableDialog_Impl::StartExecuteAsync(AsyncContext& rContext) +{ + return weld::DialogController::runAsync(m_xDlg, rContext.maEndDialogFn); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/srchxtra.cxx b/cui/source/dialogs/srchxtra.cxx new file mode 100644 index 000000000..40c2e369a --- /dev/null +++ b/cui/source/dialogs/srchxtra.cxx @@ -0,0 +1,232 @@ +/* -*- 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 <srchxtra.hxx> +#include <sal/log.hxx> +#include <svl/cjkoptions.hxx> +#include <svl/intitem.hxx> +#include <svl/whiter.hxx> +#include <sfx2/objsh.hxx> +#include <svx/flagsdef.hxx> +#include <svx/strarray.hxx> +#include <editeng/flstitem.hxx> +#include <chardlg.hxx> +#include <paragrph.hxx> +#include <backgrnd.hxx> +#include <editeng/editids.hrc> +#include <svx/svxids.hrc> +#include <tools/debug.hxx> +#include <tools/resary.hxx> +#include <vcl/svapp.hxx> + +SvxSearchFormatDialog::SvxSearchFormatDialog(weld::Window* pParent, const SfxItemSet& rSet) + : SfxTabDialogController(pParent, "cui/ui/searchformatdialog.ui", "SearchFormatDialog", &rSet) +{ + AddTabPage("font", SvxCharNamePage::Create, nullptr); + AddTabPage("fonteffects", SvxCharEffectsPage::Create, nullptr); + AddTabPage("position", SvxCharPositionPage::Create, nullptr); + AddTabPage("asianlayout", SvxCharTwoLinesPage::Create, nullptr); + AddTabPage("labelTP_PARA_STD", SvxStdParagraphTabPage::Create, nullptr); + AddTabPage("labelTP_PARA_ALIGN", SvxParaAlignTabPage::Create, nullptr); + AddTabPage("labelTP_PARA_EXT", SvxExtParagraphTabPage::Create, nullptr); + AddTabPage("labelTP_PARA_ASIAN", SvxAsianTabPage::Create, nullptr ); + AddTabPage("background", SvxBkgTabPage::Create, nullptr); + + // remove asian tabpages if necessary + if ( !SvtCJKOptions::IsDoubleLinesEnabled() ) + RemoveTabPage("asianlayout"); + if ( !SvtCJKOptions::IsAsianTypographyEnabled() ) + RemoveTabPage("labelTP_PARA_ASIAN"); +} + +SvxSearchFormatDialog::~SvxSearchFormatDialog() +{ +} + +void SvxSearchFormatDialog::PageCreated(const OString& rId, SfxTabPage& rPage) +{ + if (rId == "font") + { + const FontList* pApm_pFontList = nullptr; + if (SfxObjectShell* pSh = SfxObjectShell::Current()) + { + const SvxFontListItem* pFLItem = static_cast<const SvxFontListItem*>( + pSh->GetItem( SID_ATTR_CHAR_FONTLIST )); + if ( pFLItem ) + pApm_pFontList = pFLItem->GetFontList(); + } + + const FontList* pList = pApm_pFontList; + + if ( !pList ) + { + if ( !m_pFontList ) + m_pFontList.reset(new FontList(Application::GetDefaultDevice())); + pList = m_pFontList.get(); + } + + static_cast<SvxCharNamePage&>(rPage). + SetFontList( SvxFontListItem( pList, SID_ATTR_CHAR_FONTLIST ) ); + static_cast<SvxCharNamePage&>(rPage).EnableSearchMode(); + } + else if (rId == "labelTP_PARA_STD") + { + static_cast<SvxStdParagraphTabPage&>(rPage).EnableAutoFirstLine(); + } + else if (rId == "labelTP_PARA_ALIGN") + { + static_cast<SvxParaAlignTabPage&>(rPage).EnableJustifyExt(); + } + else if (rId == "background") + { + SfxAllItemSet aSet(*(GetInputSetImpl()->GetPool())); + aSet.Put(SfxUInt32Item(SID_FLAG_TYPE,static_cast<sal_uInt32>(SvxBackgroundTabFlags::SHOW_HIGHLIGHTING))); + rPage.PageCreated(aSet); + } +} + +SvxSearchAttributeDialog::SvxSearchAttributeDialog(weld::Window* pParent, + SearchAttrItemList& rLst, const WhichRangesContainer& pWhRanges) + : GenericDialogController(pParent, "cui/ui/searchattrdialog.ui", "SearchAttrDialog") + , rList(rLst) + , m_xAttrLB(m_xBuilder->weld_tree_view("treeview")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) +{ + m_xAttrLB->set_size_request(m_xAttrLB->get_approximate_digit_width() * 50, + m_xAttrLB->get_height_rows(12)); + + m_xAttrLB->enable_toggle_buttons(weld::ColumnToggleType::Check); + + m_xOKBtn->connect_clicked(LINK( this, SvxSearchAttributeDialog, OKHdl)); + + SfxObjectShell* pSh = SfxObjectShell::Current(); + DBG_ASSERT( pSh, "No DocShell" ); + if (pSh) + { + SfxItemPool& rPool = pSh->GetPool(); + SfxItemSet aSet( rPool, pWhRanges ); + SfxWhichIter aIter( aSet ); + sal_uInt16 nWhich = aIter.FirstWhich(); + + while ( nWhich ) + { + sal_uInt16 nSlot = rPool.GetSlotId( nWhich ); + if ( nSlot >= SID_SVX_START ) + { + bool bChecked = false, bFound = false; + for ( sal_uInt16 i = 0; !bFound && i < rList.Count(); ++i ) + { + if ( nSlot == rList[i].nSlot ) + { + bFound = true; + if ( IsInvalidItem( rList[i].pItem ) ) + bChecked = true; + } + } + + // item resources are in svx + sal_uInt32 nId = SvxAttrNameTable::FindIndex(nSlot); + if (RESARRAY_INDEX_NOTFOUND != nId) + { + m_xAttrLB->append(); + const int nRow = m_xAttrLB->n_children() - 1; + m_xAttrLB->set_toggle(nRow, bChecked ? TRISTATE_TRUE : TRISTATE_FALSE); + m_xAttrLB->set_text(nRow, SvxAttrNameTable::GetString(nId), 0); + m_xAttrLB->set_id(nRow, OUString::number(nSlot)); + } + else + SAL_WARN( "cui.dialogs", "no resource for slot id " << static_cast<sal_Int32>(nSlot) ); + } + nWhich = aIter.NextWhich(); + } + } + + m_xAttrLB->make_sorted(); + m_xAttrLB->select(0); +} + +SvxSearchAttributeDialog::~SvxSearchAttributeDialog() +{ +} + +IMPL_LINK_NOARG(SvxSearchAttributeDialog, OKHdl, weld::Button&, void) +{ + SearchAttrItem aInvalidItem; + aInvalidItem.pItem = INVALID_POOL_ITEM; + + for (int i = 0, nCount = m_xAttrLB->n_children(); i < nCount; ++i) + { + sal_uInt16 nSlot = m_xAttrLB->get_id(i).toUInt32(); + bool bChecked = m_xAttrLB->get_toggle(i) == TRISTATE_TRUE; + + sal_uInt16 j; + for ( j = rList.Count(); j; ) + { + SearchAttrItem& rItem = rList[ --j ]; + if( rItem.nSlot == nSlot ) + { + if( bChecked ) + { + if( !IsInvalidItem( rItem.pItem ) ) + delete rItem.pItem; + rItem.pItem = INVALID_POOL_ITEM; + } + else if( IsInvalidItem( rItem.pItem ) ) + rItem.pItem = nullptr; + j = 1; + break; + } + } + + if ( !j && bChecked ) + { + aInvalidItem.nSlot = nSlot; + rList.Insert( aInvalidItem ); + } + } + + // remove invalid items (pItem == NULL) + for ( sal_uInt16 n = rList.Count(); n; ) + if ( !rList[ --n ].pItem ) + rList.Remove( n ); + + m_xDialog->response(RET_OK); +} + +// class SvxSearchSimilarityDialog --------------------------------------- + +SvxSearchSimilarityDialog::SvxSearchSimilarityDialog(weld::Window* pParent, bool bRelax, + sal_uInt16 nOther, sal_uInt16 nShorter, sal_uInt16 nLonger) + : GenericDialogController(pParent, "cui/ui/similaritysearchdialog.ui", "SimilaritySearchDialog") + , m_xOtherFld(m_xBuilder->weld_spin_button("otherfld")) + , m_xLongerFld(m_xBuilder->weld_spin_button("longerfld")) + , m_xShorterFld(m_xBuilder->weld_spin_button("shorterfld")) + , m_xRelaxBox(m_xBuilder->weld_check_button("relaxbox")) +{ + m_xOtherFld->set_value(nOther); + m_xShorterFld->set_value(nShorter); + m_xLongerFld->set_value(nLonger); + m_xRelaxBox->set_active(bRelax); +} + +SvxSearchSimilarityDialog::~SvxSearchSimilarityDialog() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/thesdlg.cxx b/cui/source/dialogs/thesdlg.cxx new file mode 100644 index 000000000..ea98a44a3 --- /dev/null +++ b/cui/source/dialogs/thesdlg.cxx @@ -0,0 +1,352 @@ +/* -*- 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 <thesdlg.hxx> + +#include <tools/debug.hxx> +#include <svl/lngmisc.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <svtools/langtab.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <comphelper/string.hxx> + +#include <stack> +#include <algorithm> + +#include <com/sun/star/linguistic2/XThesaurus.hpp> +#include <com/sun/star/linguistic2/XMeaning.hpp> + +using namespace ::com::sun::star; + +IMPL_LINK_NOARG( SvxThesaurusDialog, ModifyTimer_Hdl, Timer *, void ) +{ + LookUp(m_xWordCB->get_active_text()); + m_aModifyIdle.Stop(); +} + +IMPL_LINK_NOARG(SvxThesaurusDialog, ReplaceEditHdl_Impl, weld::Entry&, void) +{ + m_xReplaceBtn->set_sensitive(!m_xReplaceEdit->get_text().isEmpty()); +} + +IMPL_LINK(SvxThesaurusDialog, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + const vcl::KeyCode& rKey = rKEvt.GetKeyCode(); + + if (rKey.GetCode() == KEY_RETURN) + { + m_xDialog->response(RET_OK); + return true; + } + + return false; +} + +uno::Sequence< uno::Reference< linguistic2::XMeaning > > SvxThesaurusDialog::queryMeanings_Impl( + OUString& rTerm, + const lang::Locale& rLocale, + const beans::PropertyValues& rProperties ) +{ + uno::Sequence< uno::Reference< linguistic2::XMeaning > > aMeanings( + xThesaurus->queryMeanings( rTerm, rLocale, rProperties ) ); + + // text with '.' at the end? + if ( !aMeanings.hasElements() && rTerm.endsWith(".") ) + { + // try again without trailing '.' chars. It may be a word at the + // end of a sentence and not an abbreviation... + OUString aTxt(comphelper::string::stripEnd(rTerm, '.')); + aMeanings = xThesaurus->queryMeanings( aTxt, rLocale, rProperties ); + if (aMeanings.hasElements()) + { + rTerm = aTxt; + } + } + + return aMeanings; +} + +bool SvxThesaurusDialog::UpdateAlternativesBox_Impl() +{ + lang::Locale aLocale( LanguageTag::convertToLocale( nLookUpLanguage ) ); + uno::Sequence< uno::Reference< linguistic2::XMeaning > > aMeanings = queryMeanings_Impl( + aLookUpText, aLocale, uno::Sequence< beans::PropertyValue >() ); + const sal_Int32 nMeanings = aMeanings.getLength(); + const uno::Reference< linguistic2::XMeaning > *pMeanings = aMeanings.getConstArray(); + + m_xAlternativesCT->freeze(); + + m_xAlternativesCT->clear(); + int nRow = 0; + for (sal_Int32 i = 0; i < nMeanings; ++i) + { + OUString rMeaningTxt = pMeanings[i]->getMeaning(); + uno::Sequence< OUString > aSynonyms( pMeanings[i]->querySynonyms() ); + const sal_Int32 nSynonyms = aSynonyms.getLength(); + const OUString *pSynonyms = aSynonyms.getConstArray(); + DBG_ASSERT( !rMeaningTxt.isEmpty(), "meaning with empty text" ); + DBG_ASSERT( nSynonyms > 0, "meaning without synonym" ); + + OUString sHeading = OUString::number(i + 1) + ". " + rMeaningTxt; + m_xAlternativesCT->append_text(sHeading); + m_xAlternativesCT->set_text_emphasis(nRow, true, 0); + ++nRow; + + for (sal_Int32 k = 0; k < nSynonyms; ++k) + { + // GetThesaurusReplaceText will strip the leading spaces + m_xAlternativesCT->append_text(" " + pSynonyms[k]); + m_xAlternativesCT->set_text_emphasis(nRow, false, 0); + ++nRow; + } + } + + m_xAlternativesCT->thaw(); + + return nMeanings > 0; +} + +void SvxThesaurusDialog::LookUp( const OUString &rText ) +{ + if (rText != m_xWordCB->get_active_text()) // avoid moving of the cursor if the text is the same + m_xWordCB->set_entry_text(rText); + LookUp_Impl(); +} + +IMPL_LINK_NOARG(SvxThesaurusDialog, LeftBtnHdl_Impl, weld::Button&, void) +{ + if (aLookUpHistory.size() >= 2) + { + aLookUpHistory.pop(); // remove current look up word from stack + m_xWordCB->set_entry_text(aLookUpHistory.top()); // retrieve previous look up word + aLookUpHistory.pop(); + LookUp_Impl(); + } +} + +IMPL_LINK( SvxThesaurusDialog, LanguageHdl_Impl, weld::ComboBox&, rLB, void ) +{ + OUString aLangText(rLB.get_active_text()); + LanguageType nLang = SvtLanguageTable::GetLanguageType( aLangText ); + DBG_ASSERT( nLang != LANGUAGE_NONE && nLang != LANGUAGE_DONTKNOW, "failed to get language" ); + if (xThesaurus->hasLocale( LanguageTag::convertToLocale( nLang ) )) + nLookUpLanguage = nLang; + SetWindowTitle( nLang ); + LookUp_Impl(); +} + +void SvxThesaurusDialog::LookUp_Impl() +{ + OUString aText(m_xWordCB->get_active_text()); + + aLookUpText = aText; + if (!aLookUpText.isEmpty() && + (aLookUpHistory.empty() || aLookUpText != aLookUpHistory.top())) + aLookUpHistory.push( aLookUpText ); + + m_bWordFound = UpdateAlternativesBox_Impl(); + m_xAlternativesCT->set_visible(m_bWordFound); + m_xNotFound->set_visible(!m_bWordFound); + + if (m_bWordFound && !m_nSelectFirstEvent) + m_nSelectFirstEvent = Application::PostUserEvent(LINK(this, SvxThesaurusDialog, SelectFirstHdl_Impl)); + + if (m_xWordCB->find_text(aText) == -1) + m_xWordCB->append_text(aText); + + m_xReplaceEdit->set_text( OUString() ); + ReplaceEditHdl_Impl(*m_xReplaceEdit); + m_xLeftBtn->set_sensitive( aLookUpHistory.size() > 1 ); +} + +IMPL_LINK_NOARG(SvxThesaurusDialog, WordSelectHdl_Impl, weld::ComboBox&, void) +{ + m_aModifyIdle.Start(); +} + +IMPL_LINK( SvxThesaurusDialog, AlternativesSelectHdl_Impl, weld::TreeView&, rBox, void ) +{ + int nEntry = rBox.get_selected_index(); + if (nEntry != -1) + { + bool bIsHeader = rBox.get_text_emphasis(nEntry, 0); + if (bIsHeader) + { + ++nEntry; + rBox.select(nEntry); + } + OUString aStr = linguistic::GetThesaurusReplaceText(rBox.get_text(nEntry)); + m_xReplaceEdit->set_text(aStr); + ReplaceEditHdl_Impl(*m_xReplaceEdit); + } +} + +IMPL_LINK( SvxThesaurusDialog, AlternativesDoubleClickHdl_Impl, weld::TreeView&, rBox, bool ) +{ + int nEntry = rBox.get_selected_index(); + if (nEntry != -1) + { + bool bIsHeader = rBox.get_text_emphasis(nEntry, 0); + if (bIsHeader) + { + ++nEntry; + rBox.select(nEntry); + } + OUString aStr = linguistic::GetThesaurusReplaceText(rBox.get_text(nEntry)); + m_xWordCB->set_entry_text(aStr); + if (!aStr.isEmpty()) + LookUp_Impl(); + } + + //! workaround to set the selection since calling SelectEntryPos within + //! the double click handler does not work + if (!m_nSelectFirstEvent) + m_nSelectFirstEvent = Application::PostUserEvent(LINK(this, SvxThesaurusDialog, SelectFirstHdl_Impl)); + + return true; +} + +IMPL_LINK_NOARG(SvxThesaurusDialog, SelectFirstHdl_Impl, void *, void) +{ + m_nSelectFirstEvent = nullptr; + if (m_xAlternativesCT->n_children() >= 2) + { + m_xAlternativesCT->select(1); // pos 0 is a 'header' that is not selectable + AlternativesSelectHdl_Impl(*m_xAlternativesCT); + } +} + +// class SvxThesaurusDialog ---------------------------------------------- + +SvxThesaurusDialog::SvxThesaurusDialog( + weld::Widget* pParent, + uno::Reference< linguistic2::XThesaurus > const & xThes, + const OUString &rWord, + LanguageType nLanguage) + : SfxDialogController(pParent, "cui/ui/thesaurus.ui", "ThesaurusDialog") + , m_aModifyIdle("cui SvxThesaurusDialog LookUp Modify") + , nLookUpLanguage(LANGUAGE_NONE) + , m_bWordFound(false) + , m_xLeftBtn(m_xBuilder->weld_button("left")) + , m_xWordCB(m_xBuilder->weld_combo_box("wordcb")) + , m_xAlternativesCT(m_xBuilder->weld_tree_view("alternatives")) + , m_xNotFound(m_xBuilder->weld_label("notfound")) + , m_xReplaceEdit(m_xBuilder->weld_entry("replaceed")) + , m_xLangLB(m_xBuilder->weld_combo_box("langcb")) + , m_xReplaceBtn(m_xBuilder->weld_button("ok")) + , m_nSelectFirstEvent(nullptr) +{ + m_aModifyIdle.SetInvokeHandler( LINK( this, SvxThesaurusDialog, ModifyTimer_Hdl ) ); + m_aModifyIdle.SetPriority( TaskPriority::LOWEST ); + + m_xReplaceEdit->connect_changed( LINK( this, SvxThesaurusDialog, ReplaceEditHdl_Impl ) ); + m_xReplaceBtn->connect_clicked( LINK( this, SvxThesaurusDialog, ReplaceBtnHdl_Impl ) ); + m_xLeftBtn->connect_clicked( LINK( this, SvxThesaurusDialog, LeftBtnHdl_Impl ) ); + m_xWordCB->set_entry_completion(false); + m_xWordCB->connect_changed( LINK( this, SvxThesaurusDialog, WordSelectHdl_Impl ) ); + m_xLangLB->connect_changed( LINK( this, SvxThesaurusDialog, LanguageHdl_Impl ) ); + m_xAlternativesCT->connect_changed( LINK( this, SvxThesaurusDialog, AlternativesSelectHdl_Impl )); + m_xAlternativesCT->connect_row_activated( LINK( this, SvxThesaurusDialog, AlternativesDoubleClickHdl_Impl )); + m_xAlternativesCT->connect_key_press(LINK(this, SvxThesaurusDialog, KeyInputHdl)); + + xThesaurus = xThes; + aLookUpText = rWord; + nLookUpLanguage = nLanguage; + if (!rWord.isEmpty()) + aLookUpHistory.push( rWord ); + + OUString aTmp( rWord ); + (void)linguistic::RemoveHyphens( aTmp ); + (void)linguistic::ReplaceControlChars( aTmp ); + m_xReplaceEdit->set_text( aTmp ); + ReplaceEditHdl_Impl(*m_xReplaceEdit); + m_xWordCB->append_text( aTmp ); + + LookUp( aTmp ); + m_xAlternativesCT->grab_focus(); + m_xLeftBtn->set_sensitive(false); + + // fill language menu button list + uno::Sequence< lang::Locale > aLocales; + if (xThesaurus.is()) + aLocales = xThesaurus->getLocales(); + const sal_Int32 nLocales = aLocales.getLength(); + const lang::Locale *pLocales = aLocales.getConstArray(); + m_xLangLB->clear(); + std::vector< OUString > aLangVec; + for (sal_Int32 i = 0; i < nLocales; ++i) + { + const LanguageType nLang = LanguageTag::convertToLanguageType( pLocales[i] ); + DBG_ASSERT( nLang != LANGUAGE_NONE && nLang != LANGUAGE_DONTKNOW, "failed to get language" ); + aLangVec.push_back( SvtLanguageTable::GetLanguageString( nLang ) ); + } + std::sort( aLangVec.begin(), aLangVec.end() ); + m_xLangLB->freeze(); + for (const OUString & i : aLangVec) + m_xLangLB->append_text(i); + m_xLangLB->thaw(); + + std::vector< OUString >::iterator aI = std::find(aLangVec.begin(), aLangVec.end(), + SvtLanguageTable::GetLanguageString(nLanguage)); + if (aI != aLangVec.end()) + { + m_xLangLB->set_active_text(*aI); + } + + SetWindowTitle(nLanguage); + + // disable controls if service is missing + if (!xThesaurus.is()) + m_xDialog->set_sensitive(false); + else + m_xWordCB->grab_focus(); +} + +SvxThesaurusDialog::~SvxThesaurusDialog() +{ + if (m_nSelectFirstEvent) + { + Application::RemoveUserEvent(m_nSelectFirstEvent); + m_nSelectFirstEvent = nullptr; + } +} + +IMPL_LINK_NOARG(SvxThesaurusDialog, ReplaceBtnHdl_Impl, weld::Button&, void) +{ + m_xDialog->response(RET_OK); +} + +void SvxThesaurusDialog::SetWindowTitle( LanguageType nLanguage ) +{ + // adjust language + OUString aStr(m_xDialog->get_title()); + sal_Int32 nIndex = aStr.indexOf( '(' ); + if( nIndex != -1 ) + aStr = aStr.copy( 0, nIndex - 1 ); + aStr += " (" + SvtLanguageTable::GetLanguageString( nLanguage ) + ")"; + m_xDialog->set_title(aStr); // set window title +} + +OUString SvxThesaurusDialog::GetWord() const +{ + return m_xReplaceEdit->get_text(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/tipofthedaydlg.cxx b/cui/source/dialogs/tipofthedaydlg.cxx new file mode 100644 index 000000000..2c35c3b4c --- /dev/null +++ b/cui/source/dialogs/tipofthedaydlg.cxx @@ -0,0 +1,254 @@ +/* -*- 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 <sal/config.h> +#include <tipofthedaydlg.hxx> +#include <tipoftheday.hrc> + +#include <sfx2/viewfrm.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/help.hxx> +#include <vcl/window.hxx> + +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/util/URL.hpp> +#include <com/sun/star/util/URLTransformer.hpp> + +#include <comphelper/dispatchcommand.hxx> +#include <dialmgr.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <officecfg/Office/Common.hxx> +#include <osl/file.hxx> +#include <rtl/bootstrap.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <unotools/resmgr.hxx> +#include <unotools/configmgr.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> + +//size of preview +const Size ThumbSize(150, 150); + +TipOfTheDayDialog::TipOfTheDayDialog(weld::Window* pParent) + : GenericDialogController(pParent, "cui/ui/tipofthedaydialog.ui", "TipOfTheDayDialog") + , m_pParent(pParent) + , m_pText(m_xBuilder->weld_label("lbText")) + , m_pShowTip(m_xBuilder->weld_check_button("cbShowTip")) + , m_pNext(m_xBuilder->weld_button("btnNext")) + , m_pLink(m_xBuilder->weld_link_button("btnLink")) + , m_pPreview(new weld::CustomWeld(*m_xBuilder, "imPreview", m_aPreview)) +{ + m_pShowTip->set_active(officecfg::Office::Common::Misc::ShowTipOfTheDay::get()); + m_pNext->connect_clicked(LINK(this, TipOfTheDayDialog, OnNextClick)); + m_nCurrentTip = officecfg::Office::Common::Misc::LastTipOfTheDayID::get(); + m_pPreview->set_size_request(ThumbSize.Width(), ThumbSize.Height()); + + if (pParent != nullptr) + { + css::uno::Reference<css::awt::XWindow> xWindow = pParent->GetXWindow(); + if (xWindow.is()) + { + VclPtr<vcl::Window> xVclWin(VCLUnoHelper::GetWindow(xWindow)); + if (xVclWin != nullptr) + xVclWin->AddEventListener(LINK(this, TipOfTheDayDialog, Terminated)); + } + } + + const auto t0 = std::chrono::system_clock::now().time_since_epoch(); + sal_Int32 nDay = std::chrono::duration_cast<std::chrono::hours>(t0).count() / 24; + //show next tip after one day + if (nDay > officecfg::Office::Common::Misc::LastTipOfTheDayShown::get()) + m_nCurrentTip++; + + // save this time to the config now instead of in the dtor otherwise we + // end up with multiple copies of this dialog every time we open a new + // document if the first one isn't closed + std::shared_ptr<comphelper::ConfigurationChanges> xChanges( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Misc::LastTipOfTheDayShown::set(nDay, xChanges); + xChanges->commit(); + + UpdateTip(); +} + +IMPL_LINK(TipOfTheDayDialog, Terminated, VclWindowEvent&, rEvent, void) +{ + if (rEvent.GetId() == VclEventId::ObjectDying) + { + m_pParent = nullptr; + TipOfTheDayDialog::response(RET_OK); + } +} + +TipOfTheDayDialog::~TipOfTheDayDialog() +{ + std::shared_ptr<comphelper::ConfigurationChanges> xChanges( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Misc::LastTipOfTheDayID::set(m_nCurrentTip, xChanges); + officecfg::Office::Common::Misc::ShowTipOfTheDay::set(m_pShowTip->get_active(), xChanges); + xChanges->commit(); + + if (m_pParent != nullptr) + { + css::uno::Reference<css::awt::XWindow> xWindow = m_pParent->GetXWindow(); + if (xWindow.is()) + { + VclPtr<vcl::Window> xVclWin(VCLUnoHelper::GetWindow(xWindow)); + if (xVclWin != nullptr) + xVclWin->RemoveEventListener(LINK(this, TipOfTheDayDialog, Terminated)); + } + } +} + +static bool file_exists(const OUString& fileName) +{ + ::osl::File aFile(fileName); + return aFile.open(osl_File_OpenFlag_Read) == osl::FileBase::E_None; +} + +void TipOfTheDayDialog::UpdateTip() +{ + constexpr sal_Int32 nNumberOfTips = SAL_N_ELEMENTS(TIPOFTHEDAY_STRINGARRAY); + + if ((m_nCurrentTip >= nNumberOfTips) || (m_nCurrentTip < 0)) + m_nCurrentTip = 0; + + //title + m_xDialog->set_title(CuiResId(STR_TITLE) + .replaceFirst("%CURRENT", OUString::number(m_nCurrentTip + 1)) + .replaceFirst("%TOTAL", OUString::number(nNumberOfTips))); + + auto[sTip, sLink, sImage] = TIPOFTHEDAY_STRINGARRAY[m_nCurrentTip]; + + // text +//replace MOD1 & MOD2 shortcuts depending on platform +#ifdef MACOSX + const OUString aMOD1 = CuiResId(STR_CMD); + const OUString aMOD2 = CuiResId(STR_Option); +#else + const OUString aMOD1 = CuiResId(STR_CTRL); + const OUString aMOD2 = CuiResId(STR_Alt); +#endif + m_pText->set_label(CuiResId(sTip).replaceAll("%MOD1", aMOD1).replaceAll("%MOD2", aMOD2)); + + // hyperlink + if (sLink.isEmpty()) + { + m_pLink->set_visible(false); + } + else if (sLink.startsWith(".uno:")) + { + m_pLink->set_visible(false); + //show the link only if the UNO command is available in the current module + SfxViewFrame* pViewFrame = SfxViewFrame::Current(); + if (pViewFrame) + { + const auto xFrame = pViewFrame->GetFrame().GetFrameInterface(); + const css::uno::Reference<css::frame::XDispatchProvider> xDispatchProvider( + xFrame, css::uno::UNO_QUERY); + if (xDispatchProvider.is()) + { + css::util::URL aCommandURL; + aCommandURL.Complete = sLink; + const css::uno::Reference<css::uno::XComponentContext> xContext + = comphelper::getProcessComponentContext(); + const css::uno::Reference<css::util::XURLTransformer> xParser + = css::util::URLTransformer::create(xContext); + xParser->parseStrict(aCommandURL); + const css::uno::Reference<css::frame::XDispatch> xDisp + = xDispatchProvider->queryDispatch(aCommandURL, OUString(), 0); + if (xDisp.is()) + { + m_pLink->set_label(CuiResId(STR_UNO_LINK)); + m_pLink->set_uri(sLink); + + const OUString aModuleName( + vcl::CommandInfoProvider::GetModuleIdentifier(xFrame)); + const auto aProperties + = vcl::CommandInfoProvider::GetCommandProperties(sLink, aModuleName); + m_pLink->set_tooltip_text( + vcl::CommandInfoProvider::GetTooltipForCommand(sLink, aProperties, xFrame)); + + m_pLink->set_visible(true); + m_pLink->connect_activate_link(LINK(this, TipOfTheDayDialog, OnLinkClick)); + } + } + } + } + else if (sLink.startsWith("http")) + { + // Links may have some %PRODUCTVERSION which need to be expanded + OUString aText = Translate::ExpandVariables(sLink); + OUString aLang = LanguageTag(utl::ConfigManager::getUILocale()).getLanguage(); + if (aLang == "en" || aLang == "pt" || aLang == "zh") //en-US/GB, pt-BR, zh-CH/TW + aLang = LanguageTag(utl::ConfigManager::getUILocale()).getBcp47(); + m_pLink->set_uri(aText.replaceFirst("%LANGUAGENAME", aLang)); + m_pLink->set_label(CuiResId(STR_MORE_LINK)); + m_pLink->set_visible(true); + m_pLink->connect_activate_link(Link<weld::LinkButton&, bool>()); + } + else + { + m_pLink->set_uri(sLink); + m_pLink->set_label(CuiResId(STR_HELP_LINK)); + m_pLink->set_visible(true); + m_pLink->connect_activate_link(LINK(this, TipOfTheDayDialog, OnLinkClick)); + } + // image + OUString aURL("$BRAND_BASE_DIR/$BRAND_SHARE_SUBDIR/tipoftheday/"); + rtl::Bootstrap::expandMacros(aURL); + OUString aImageName = sImage; + // use default image if none is available with the number + if (aImageName.isEmpty() || !file_exists(aURL + aImageName)) + aImageName = "tipoftheday.png"; + Graphic aGraphic; + GraphicFilter::LoadGraphic(aURL + aImageName, OUString(), aGraphic); + + if (!aGraphic.IsAnimated()) + { + BitmapEx aBmpEx(aGraphic.GetBitmapEx()); + if (aBmpEx.Scale(ThumbSize)) + aGraphic = aBmpEx; + } + m_aPreview.SetPreview(aGraphic); +} + +IMPL_LINK(TipOfTheDayDialog, OnLinkClick, weld::LinkButton&, rButton, bool) +{ + const OUString sLink = rButton.get_uri(); + if (sLink.startsWith(".uno:")) + { + comphelper::dispatchCommand(sLink, {}); + TipOfTheDayDialog::response(RET_OK); + } + else + { + Application::GetHelp()->Start(sLink, static_cast<weld::Widget*>(nullptr)); + } + return true; +} + +IMPL_LINK_NOARG(TipOfTheDayDialog, OnNextClick, weld::Button&, void) +{ + m_nCurrentTip++; //zeroed at updatetip when out of range + UpdateTip(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/source/dialogs/toolbarmodedlg.cxx b/cui/source/dialogs/toolbarmodedlg.cxx new file mode 100644 index 000000000..f1dbdeb10 --- /dev/null +++ b/cui/source/dialogs/toolbarmodedlg.cxx @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> + +#include <toolbarmodedlg.hxx> +#include <toolbarmode.hrc> + +#include <com/sun/star/frame/ModuleManager.hpp> +#include <comphelper/dispatchcommand.hxx> +#include <comphelper/types.hxx> +#include <dialmgr.hxx> +#include <officecfg/Office/Common.hxx> +#include <officecfg/Office/UI/ToolbarMode.hxx> +#include <osl/file.hxx> +#include <rtl/bootstrap.hxx> +#include <sfx2/viewfrm.hxx> +#include <strings.hrc> +#include <unotools/confignode.hxx> +#include <vcl/virdev.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/EnumContext.hxx> +#include <vcl/weld.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> + +static OUString GetCurrentApp() +{ + OUString sResult; + if (SfxViewFrame* pViewFrame = SfxViewFrame::Current()) + { + const auto xCurrentFrame = pViewFrame->GetFrame().GetFrameInterface(); + const auto xContext = comphelper::getProcessComponentContext(); + const auto xModuleManager = css::frame::ModuleManager::create(xContext); + switch (vcl::EnumContext::GetApplicationEnum(xModuleManager->identify(xCurrentFrame))) + { + case vcl::EnumContext::Application::Writer: + sResult = "Writer"; + break; + case vcl::EnumContext::Application::Calc: + sResult = "Calc"; + break; + case vcl::EnumContext::Application::Impress: + sResult = "Impress"; + break; + case vcl::EnumContext::Application::Draw: + sResult = "Draw"; + break; + case vcl::EnumContext::Application::Formula: + sResult = "Formula"; + break; + case vcl::EnumContext::Application::Base: + sResult = "Base"; + break; + default: + sResult = "Unsupported"; + } + } + return sResult; +} + +static OUString GetCurrentMode() +{ + OUString sResult; + if (SfxViewFrame::Current()) + { + const auto xContext = comphelper::getProcessComponentContext(); + const utl::OConfigurationTreeRoot aAppNode( + xContext, "org.openoffice.Office.UI.ToolbarMode/Applications/" + GetCurrentApp(), true); + if (aAppNode.isValid()) + sResult = comphelper::getString(aAppNode.getNodeValue("Active")); + }; + return sResult; +} + +ToolbarmodeDialog::ToolbarmodeDialog(weld::Window* pParent) + : GenericDialogController(pParent, "cui/ui/toolbarmodedialog.ui", "ToolbarmodeDialog") + , m_pImage(m_xBuilder->weld_image("imImage")) + , m_pApply(m_xBuilder->weld_button("btnApply")) + , m_pApplyAll(m_xBuilder->weld_button("btnApplyAll")) + , m_pRadioButtons{ (m_xBuilder->weld_radio_button("rbButton1")), + (m_xBuilder->weld_radio_button("rbButton2")), + (m_xBuilder->weld_radio_button("rbButton3")), + (m_xBuilder->weld_radio_button("rbButton4")), + (m_xBuilder->weld_radio_button("rbButton5")), + (m_xBuilder->weld_radio_button("rbButton6")), + (m_xBuilder->weld_radio_button("rbButton7")), + (m_xBuilder->weld_radio_button("rbButton8")), + (m_xBuilder->weld_radio_button("rbButton9")) } + , m_pInfoLabel(m_xBuilder->weld_label("lbInfo")) +{ + static_assert(SAL_N_ELEMENTS(m_pRadioButtons) == SAL_N_ELEMENTS(TOOLBARMODES_ARRAY)); + + Link<weld::Toggleable&, void> aLink = LINK(this, ToolbarmodeDialog, SelectToolbarmode); + + const OUString sCurrentMode = GetCurrentMode(); + for (tools::ULong i = 0; i < std::size(m_pRadioButtons); i++) + { + m_pRadioButtons[i]->connect_toggled(aLink); + if (sCurrentMode == std::get<1>(TOOLBARMODES_ARRAY[i])) + { + m_pRadioButtons[i]->set_active(true); + UpdateImage(std::get<2>(TOOLBARMODES_ARRAY[i])); + m_pInfoLabel->set_label(CuiResId(std::get<0>(TOOLBARMODES_ARRAY[i]))); + } + } + + m_pApply->set_label(CuiResId(RID_CUISTR_UI_APPLYALL).replaceFirst("%MODULE", GetCurrentApp())); + m_pApply->connect_clicked(LINK(this, ToolbarmodeDialog, OnApplyClick)); + m_pApplyAll->connect_clicked(LINK(this, ToolbarmodeDialog, OnApplyClick)); + + if (!officecfg::Office::Common::Misc::ExperimentalMode::get()) + { + m_pRadioButtons[nGroupedbarFull]->set_visible(false); + m_pRadioButtons[nContextualGroups]->set_visible(false); + } +} + +ToolbarmodeDialog::~ToolbarmodeDialog() = default; + +static bool file_exists(const OUString& fileName) +{ + osl::File aFile(fileName); + return aFile.open(osl_File_OpenFlag_Read) == osl::FileBase::E_None; +} + +int ToolbarmodeDialog::GetActiveRadioButton() +{ + for (tools::ULong i = 0; i < std::size(m_pRadioButtons); i++) + { + if (m_pRadioButtons[i]->get_active()) + return i; + } + return -1; +} + +void ToolbarmodeDialog::UpdateImage(std::u16string_view sFileName) +{ + // load image + OUString aURL("$BRAND_BASE_DIR/$BRAND_SHARE_SUBDIR/toolbarmode/"); + rtl::Bootstrap::expandMacros(aURL); + aURL += sFileName; + if (sFileName.empty() || !file_exists(aURL)) + return; + // draw image + Graphic aGraphic; + if (GraphicFilter::LoadGraphic(aURL, OUString(), aGraphic) == ERRCODE_NONE) + { + ScopedVclPtr<VirtualDevice> m_pVirDev = m_pImage->create_virtual_device(); + m_pVirDev->SetOutputSizePixel(aGraphic.GetSizePixel()); + m_pVirDev->DrawBitmapEx(Point(0, 0), aGraphic.GetBitmapEx()); + m_pImage->set_image(m_pVirDev.get()); + m_pVirDev.disposeAndClear(); + } +} + +IMPL_LINK_NOARG(ToolbarmodeDialog, SelectToolbarmode, weld::Toggleable&, void) +{ + const int i = GetActiveRadioButton(); + if (i > -1) + { + UpdateImage(std::get<2>(TOOLBARMODES_ARRAY[i])); + m_pInfoLabel->set_label(CuiResId(std::get<0>(TOOLBARMODES_ARRAY[i]))); + } +} + +IMPL_LINK(ToolbarmodeDialog, OnApplyClick, weld::Button&, rButton, void) +{ + const int i = GetActiveRadioButton(); + if (i == -1) + return; + const OUString sCmd = std::get<1>(TOOLBARMODES_ARRAY[i]); + //apply to all except current module + if (&rButton == m_pApplyAll.get()) + { + std::shared_ptr<comphelper::ConfigurationChanges> aBatch( + comphelper::ConfigurationChanges::create()); + officecfg::Office::UI::ToolbarMode::ActiveWriter::set(sCmd, aBatch); + officecfg::Office::UI::ToolbarMode::ActiveCalc::set(sCmd, aBatch); + officecfg::Office::UI::ToolbarMode::ActiveImpress::set(sCmd, aBatch); + officecfg::Office::UI::ToolbarMode::ActiveDraw::set(sCmd, aBatch); + aBatch->commit(); + + OUString sCurrentApp = GetCurrentApp(); + if (SfxViewFrame::Current()) + { + const auto xContext = comphelper::getProcessComponentContext(); + const utl::OConfigurationTreeRoot aAppNode( + xContext, "org.openoffice.Office.UI.ToolbarMode/Applications/", true); + if (sCurrentApp != "Writer") + aAppNode.setNodeValue("Writer/Active", css::uno::Any(sCmd)); + if (sCurrentApp != "Calc") + aAppNode.setNodeValue("Calc/Active", css::uno::Any(sCmd)); + if (sCurrentApp != "Impress") + aAppNode.setNodeValue("Impress/Active", css::uno::Any(sCmd)); + if (sCurrentApp != "Draw") + aAppNode.setNodeValue("Draw/Active", css::uno::Any(sCmd)); + aAppNode.commit(); + }; + } + //apply to current module + comphelper::dispatchCommand(".uno:ToolbarMode?Mode:string=" + sCmd, {}); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/cui/source/dialogs/zoom.cxx b/cui/source/dialogs/zoom.cxx new file mode 100644 index 000000000..0d6f44e40 --- /dev/null +++ b/cui/source/dialogs/zoom.cxx @@ -0,0 +1,397 @@ +/* -*- 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 <osl/diagnose.h> +#include <svl/itemset.hxx> +#include <svl/itempool.hxx> +#include <sfx2/objsh.hxx> +#include <zoom.hxx> +#include <sfx2/zoomitem.hxx> +#include <svx/viewlayoutitem.hxx> +#include <svx/zoom_def.hxx> + +namespace +{ +const sal_uInt16 SPECIAL_FACTOR = 0xFFFF; + +} // anonymous namespace + +sal_uInt16 SvxZoomDialog::GetFactor() const +{ + if (m_x100Btn->get_active()) + return 100; + + if (m_xUserBtn->get_active()) + return static_cast<sal_uInt16>(m_xUserEdit->get_value(FieldUnit::PERCENT)); + else + return SPECIAL_FACTOR; +} + +void SvxZoomDialog::SetFactor(sal_uInt16 nNewFactor, ZoomButtonId nButtonId) +{ + m_xUserEdit->set_sensitive(false); + + if (nButtonId == ZoomButtonId::NONE) + { + if (nNewFactor == 100) + { + m_x100Btn->set_active(true); + m_x100Btn->grab_focus(); + } + else + { + m_xUserBtn->set_active(true); + m_xUserEdit->set_sensitive(true); + m_xUserEdit->set_value(nNewFactor, FieldUnit::PERCENT); + m_xUserEdit->grab_focus(); + } + } + else + { + m_xUserEdit->set_value(nNewFactor, FieldUnit::PERCENT); + switch (nButtonId) + { + case ZoomButtonId::OPTIMAL: + { + m_xOptimalBtn->set_active(true); + m_xOptimalBtn->grab_focus(); + break; + } + case ZoomButtonId::PAGEWIDTH: + { + m_xPageWidthBtn->set_active(true); + m_xPageWidthBtn->grab_focus(); + break; + } + case ZoomButtonId::WHOLEPAGE: + { + m_xWholePageBtn->set_active(true); + m_xWholePageBtn->grab_focus(); + break; + } + default: + break; + } + } +} + +void SvxZoomDialog::HideButton(ZoomButtonId nButtonId) +{ + switch (nButtonId) + { + case ZoomButtonId::OPTIMAL: + m_xOptimalBtn->hide(); + break; + + case ZoomButtonId::PAGEWIDTH: + m_xPageWidthBtn->hide(); + break; + + case ZoomButtonId::WHOLEPAGE: + m_xWholePageBtn->hide(); + break; + + default: + OSL_FAIL("Wrong button number!"); + } +} + +void SvxZoomDialog::SetLimits(sal_uInt16 nMin, sal_uInt16 nMax) +{ + DBG_ASSERT(nMin < nMax, "invalid limits"); + m_xUserEdit->set_range(nMin, nMax, FieldUnit::PERCENT); +} + +const SfxItemSet* SvxZoomDialog::GetOutputItemSet() const { return m_pOutSet.get(); } + +SvxZoomDialog::SvxZoomDialog(weld::Window* pParent, const SfxItemSet& rCoreSet) + : SfxDialogController(pParent, "cui/ui/zoomdialog.ui", "ZoomDialog") + , m_rSet(rCoreSet) + , m_bModified(false) + , m_xOptimalBtn(m_xBuilder->weld_radio_button("optimal")) + , m_xWholePageBtn(m_xBuilder->weld_radio_button("fitwandh")) + , m_xPageWidthBtn(m_xBuilder->weld_radio_button("fitw")) + , m_x100Btn(m_xBuilder->weld_radio_button("100pc")) + , m_xUserBtn(m_xBuilder->weld_radio_button("variable")) + , m_xUserEdit(m_xBuilder->weld_metric_spin_button("zoomsb", FieldUnit::PERCENT)) + , m_xViewFrame(m_xBuilder->weld_widget("viewframe")) + , m_xAutomaticBtn(m_xBuilder->weld_radio_button("automatic")) + , m_xSingleBtn(m_xBuilder->weld_radio_button("singlepage")) + , m_xColumnsBtn(m_xBuilder->weld_radio_button("columns")) + , m_xColumnsEdit(m_xBuilder->weld_spin_button("columnssb")) + , m_xBookModeChk(m_xBuilder->weld_check_button("bookmode")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) +{ + Link<weld::Toggleable&, void> aLink = LINK(this, SvxZoomDialog, UserHdl); + m_x100Btn->connect_toggled(aLink); + m_xOptimalBtn->connect_toggled(aLink); + m_xPageWidthBtn->connect_toggled(aLink); + m_xWholePageBtn->connect_toggled(aLink); + m_xUserBtn->connect_toggled(aLink); + + Link<weld::Toggleable&, void> aViewLayoutLink = LINK(this, SvxZoomDialog, ViewLayoutUserHdl); + m_xAutomaticBtn->connect_toggled(aViewLayoutLink); + m_xSingleBtn->connect_toggled(aViewLayoutLink); + m_xColumnsBtn->connect_toggled(aViewLayoutLink); + + Link<weld::SpinButton&, void> aViewLayoutSpinLink + = LINK(this, SvxZoomDialog, ViewLayoutSpinHdl); + m_xColumnsEdit->connect_value_changed(aViewLayoutSpinLink); + + Link<weld::Toggleable&, void> aViewLayoutCheckLink + = LINK(this, SvxZoomDialog, ViewLayoutCheckHdl); + m_xBookModeChk->connect_toggled(aViewLayoutCheckLink); + + m_xOKBtn->connect_clicked(LINK(this, SvxZoomDialog, OKHdl)); + m_xUserEdit->connect_value_changed(LINK(this, SvxZoomDialog, SpinHdl)); + + // default values + sal_uInt16 nValue = 100; + sal_uInt16 nMin = 10; + sal_uInt16 nMax = 1000; + + // maybe get the old value first + const SfxUInt16Item* pOldUserItem = nullptr; + if (SfxObjectShell* pShell = SfxObjectShell::Current()) + pOldUserItem = pShell->GetItem(SID_ATTR_ZOOM_USER); + + if (pOldUserItem) + nValue = pOldUserItem->GetValue(); + + // initialize UserEdit + if (nMin > nValue) + nMin = nValue; + if (nMax < nValue) + nMax = nValue; + + SetLimits(nMin, nMax); + m_xUserEdit->set_value(nValue, FieldUnit::PERCENT); + + const SfxPoolItem& rItem = m_rSet.Get(SID_ATTR_ZOOM); + + if (auto pZoomItem = dynamic_cast<const SvxZoomItem*>(&rItem)) + { + const sal_uInt16 nZoom = pZoomItem->GetValue(); + const SvxZoomType eType = pZoomItem->GetType(); + const SvxZoomEnableFlags nValSet = pZoomItem->GetValueSet(); + ZoomButtonId nButtonId = ZoomButtonId::NONE; + + switch (eType) + { + case SvxZoomType::OPTIMAL: + nButtonId = ZoomButtonId::OPTIMAL; + break; + case SvxZoomType::PAGEWIDTH: + nButtonId = ZoomButtonId::PAGEWIDTH; + break; + case SvxZoomType::WHOLEPAGE: + nButtonId = ZoomButtonId::WHOLEPAGE; + break; + case SvxZoomType::PERCENT: + break; + case SvxZoomType::PAGEWIDTH_NOBORDER: + break; + } + + if (!(SvxZoomEnableFlags::N100 & nValSet)) + m_x100Btn->set_sensitive(false); + if (!(SvxZoomEnableFlags::OPTIMAL & nValSet)) + m_xOptimalBtn->set_sensitive(false); + if (!(SvxZoomEnableFlags::PAGEWIDTH & nValSet)) + m_xPageWidthBtn->set_sensitive(false); + if (!(SvxZoomEnableFlags::WHOLEPAGE & nValSet)) + m_xWholePageBtn->set_sensitive(false); + + SetFactor(nZoom, nButtonId); + } + else + { + const sal_uInt16 nZoom = static_cast<const SfxUInt16Item&>(rItem).GetValue(); + SetFactor(nZoom); + } + + if (const SvxViewLayoutItem* pViewLayoutItem = m_rSet.GetItemIfSet(SID_ATTR_VIEWLAYOUT, false)) + { + const sal_uInt16 nColumns = pViewLayoutItem->GetValue(); + const bool bBookMode = pViewLayoutItem->IsBookMode(); + + if (0 == nColumns) + { + m_xAutomaticBtn->set_active(true); + m_xColumnsEdit->set_value(2); + m_xColumnsEdit->set_sensitive(false); + m_xBookModeChk->set_sensitive(false); + } + else if (1 == nColumns) + { + m_xSingleBtn->set_active(true); + m_xColumnsEdit->set_value(2); + m_xColumnsEdit->set_sensitive(false); + m_xBookModeChk->set_sensitive(false); + } + else + { + m_xColumnsBtn->set_active(true); + if (!bBookMode) + { + m_xColumnsEdit->set_value(nColumns); + if (nColumns % 2 != 0) + m_xBookModeChk->set_sensitive(false); + } + else + { + m_xColumnsEdit->set_value(nColumns); + m_xBookModeChk->set_active(true); + } + } + } + else + { + // hide view layout related controls: + m_xViewFrame->set_sensitive(false); + } +} + +IMPL_LINK_NOARG(SvxZoomDialog, UserHdl, weld::Toggleable&, void) +{ + m_bModified = true; + + if (m_xUserBtn->get_active()) + { + m_xUserEdit->set_sensitive(true); + m_xUserEdit->grab_focus(); + } + else + { + m_xUserEdit->set_sensitive(false); + } +} + +IMPL_LINK_NOARG(SvxZoomDialog, SpinHdl, weld::MetricSpinButton&, void) +{ + if (!m_xUserBtn->get_active()) + return; + + m_bModified = true; +} + +IMPL_LINK_NOARG(SvxZoomDialog, ViewLayoutUserHdl, weld::Toggleable&, void) +{ + m_bModified = true; + + if (m_xAutomaticBtn->get_active() || m_xSingleBtn->get_active()) + { + m_xColumnsEdit->set_sensitive(false); + m_xBookModeChk->set_sensitive(false); + } + else if (m_xColumnsBtn->get_active()) + { + m_xColumnsEdit->set_sensitive(true); + m_xColumnsEdit->grab_focus(); + if (m_xColumnsEdit->get_value() % 2 == 0) + m_xBookModeChk->set_sensitive(true); + } +} + +IMPL_LINK_NOARG(SvxZoomDialog, ViewLayoutSpinHdl, weld::SpinButton&, void) +{ + if (!m_xColumnsBtn->get_active()) + return; + + if (m_xColumnsEdit->get_value() % 2 == 0) + { + m_xBookModeChk->set_sensitive(true); + } + else + { + m_xBookModeChk->set_active(false); + m_xBookModeChk->set_sensitive(false); + } + + m_bModified = true; +} + +IMPL_LINK_NOARG(SvxZoomDialog, ViewLayoutCheckHdl, weld::Toggleable&, void) +{ + if (!m_xColumnsBtn->get_active()) + return; + + m_bModified = true; +} + +IMPL_LINK_NOARG(SvxZoomDialog, OKHdl, weld::Button&, void) +{ + if (m_bModified) + { + SvxZoomItem aZoomItem(SvxZoomType::PERCENT, 0, SID_ATTR_ZOOM); + SvxViewLayoutItem aViewLayoutItem(0, false, SID_ATTR_VIEWLAYOUT); + + sal_uInt16 nFactor = GetFactor(); + + if (SPECIAL_FACTOR == nFactor) + { + if (m_xOptimalBtn->get_active()) + aZoomItem.SetType(SvxZoomType::OPTIMAL); + else if (m_xPageWidthBtn->get_active()) + aZoomItem.SetType(SvxZoomType::PAGEWIDTH); + else if (m_xWholePageBtn->get_active()) + aZoomItem.SetType(SvxZoomType::WHOLEPAGE); + } + else + { + aZoomItem.SetValue(nFactor); + } + + if (m_xAutomaticBtn->get_active()) + { + aViewLayoutItem.SetValue(0); + aViewLayoutItem.SetBookMode(false); + } + if (m_xSingleBtn->get_active()) + { + aViewLayoutItem.SetValue(1); + aViewLayoutItem.SetBookMode(false); + } + else if (m_xColumnsBtn->get_active()) + { + aViewLayoutItem.SetValue(static_cast<sal_uInt16>(m_xColumnsEdit->get_value())); + aViewLayoutItem.SetBookMode(m_xBookModeChk->get_active()); + } + + m_pOutSet.reset(new SfxItemSet(m_rSet)); + m_pOutSet->Put(aZoomItem); + + // don't set attribute in case the whole viewlayout stuff is disabled: + if (m_xViewFrame->get_sensitive()) + m_pOutSet->Put(aViewLayoutItem); + + // memorize value from the UserEdit beyond the dialog + if (SfxObjectShell* pShell = SfxObjectShell::Current()) + { + sal_uInt16 nZoomValue + = static_cast<sal_uInt16>(m_xUserEdit->get_value(FieldUnit::PERCENT)); + pShell->PutItem(SfxUInt16Item(SID_ATTR_ZOOM_USER, nZoomValue)); + } + m_xDialog->response(RET_OK); + } + else + m_xDialog->response(RET_CANCEL); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |