1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <classes/fwkresid.hxx>
21 #include <services.h>
22 #include <strings.hrc>
23 #include <vcl/svapp.hxx>
24 #include <vcl/window.hxx>
25 #include <vcl/status.hxx>
26 #include <toolkit/helper/convert.hxx>
27 
28 #include <cppuhelper/supportsservice.hxx>
29 #include <com/sun/star/awt/PopupMenu.hpp>
30 #include <com/sun/star/awt/PopupMenuDirection.hpp>
31 #include <svtools/langtab.hxx>
32 #include <svtools/statusbarcontroller.hxx>
33 #include <sal/types.h>
34 #include <sal/log.hxx>
35 #include <com/sun/star/awt/MenuItemStyle.hpp>
36 #include <com/sun/star/document/XDocumentLanguages.hpp>
37 #include <com/sun/star/lang/XServiceInfo.hpp>
38 #include <com/sun/star/frame/ModuleManager.hpp>
39 #include <i18nlangtag/mslangid.hxx>
40 #include <com/sun/star/i18n/ScriptType.hpp>
41 
42 #include <com/sun/star/frame/XFrame.hpp>
43 
44 #include <tools/gen.hxx>
45 #include <com/sun/star/awt/Command.hpp>
46 #include <svl/languageoptions.hxx>
47 
48 #include <helper/mischelper.hxx>
49 
50 #include <rtl/ustrbuf.hxx>
51 #include <rtl/ref.hxx>
52 
53 #include <stdtypes.h>
54 
55 #include <map>
56 #include <set>
57 
58 using namespace ::cppu;
59 using namespace ::com::sun::star;
60 using namespace css::uno;
61 using namespace css::lang;
62 using namespace css::frame;
63 using namespace css::i18n;
64 using namespace css::document;
65 using namespace framework;
66 
67 namespace {
68 
69 class LangSelectionStatusbarController:
70     public svt::StatusbarController
71 {
72 public:
73     explicit LangSelectionStatusbarController( const css::uno::Reference< css::uno::XComponentContext >& xContext );
74     LangSelectionStatusbarController(const LangSelectionStatusbarController&) = delete;
75     LangSelectionStatusbarController& operator=(const LangSelectionStatusbarController&) = delete;
76 
77     // XInitialization
78     virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
79 
80     // XStatusListener
81     virtual void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& Event ) override;
82 
83     // XStatusbarController
84     virtual void SAL_CALL command( const css::awt::Point& aPos,
85                                    ::sal_Int32 nCommand,
86                                    sal_Bool bMouseEvent,
87                                    const css::uno::Any& aData ) override;
88     virtual void SAL_CALL click( const css::awt::Point& aPos ) override;
89 
90 private:
91     virtual ~LangSelectionStatusbarController() override {}
92 
93     bool            m_bShowMenu;        // if the menu is to be displayed or not (depending on the selected object/text)
94     SvtScriptType   m_nScriptType;      // the flags for the different script types available in the selection, LATIN = 0x0001, ASIAN = 0x0002, COMPLEX = 0x0004
95     OUString        m_aCurLang;         // the language of the current selection, "*" if there are more than one languages
96     OUString        m_aKeyboardLang;    // the keyboard language
97     OUString        m_aGuessedTextLang;     // the 'guessed' language for the selection, "" if none could be guessed
98     LanguageGuessingHelper      m_aLangGuessHelper;
99 
100     /// @throws css::uno::RuntimeException
101     void LangMenu( const css::awt::Point& aPos );
102 };
103 
104 LangSelectionStatusbarController::LangSelectionStatusbarController( const uno::Reference< uno::XComponentContext >& xContext ) :
105     svt::StatusbarController( xContext, uno::Reference< frame::XFrame >(), OUString(), 0 ),
106     m_bShowMenu( true ),
107     m_nScriptType( SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX ),
108     m_aLangGuessHelper( xContext )
109 {
110 }
111 
112 void SAL_CALL LangSelectionStatusbarController::initialize( const css::uno::Sequence< css::uno::Any >& aArguments )
113 {
114     SolarMutexGuard aSolarMutexGuard;
115 
116     svt::StatusbarController::initialize( aArguments );
117 
118     if ( m_xStatusbarItem.is() )
119     {
120         m_xStatusbarItem->setText( FwkResId(STR_LANGSTATUS_MULTIPLE_LANGUAGES) );
121         m_xStatusbarItem->setQuickHelpText(FwkResId(STR_LANGSTATUS_HINT));
122     }
123 }
124 
125 void LangSelectionStatusbarController::LangMenu(
126     const css::awt::Point& aPos )
127 {
128     if (!m_bShowMenu)
129         return;
130 
131     const Reference<XServiceInfo> xService(m_xFrame->getController()->getModel(), UNO_QUERY);
132     bool bWriter = xService.is() && xService->supportsService("com.sun.star.text.GenericTextDocument");
133     //add context menu
134     Reference< awt::XPopupMenu > xPopupMenu( awt::PopupMenu::create( m_xContext ) );
135     //sub menu that contains all items except the last two items: Separator + Set Language for Paragraph
136     Reference< awt::XPopupMenu > subPopupMenu( awt::PopupMenu::create( m_xContext ) );
137 
138     // get languages to be displayed in the menu
139     std::set< OUString > aLangItems;
140     FillLangItems( aLangItems, m_xFrame, m_aLangGuessHelper,
141             m_nScriptType, m_aCurLang, m_aKeyboardLang, m_aGuessedTextLang );
142 
143     // add first few entries to main menu
144     sal_Int16 nItemId = static_cast< sal_Int16 >(MID_LANG_SEL_1);
145     const OUString sAsterisk("*");  // multiple languages in current selection
146     const OUString sNone( SvtLanguageTable::GetLanguageString( LANGUAGE_NONE ));
147     std::map< sal_Int16, OUString > aLangMap;
148     for (auto const& langItem : aLangItems)
149     {
150         if ( langItem != sNone &&
151              langItem != sAsterisk &&
152              !langItem.isEmpty()) // 'no language found' from language guessing
153         {
154             SAL_WARN_IF( MID_LANG_SEL_1 > nItemId || nItemId > MID_LANG_SEL_9,
155                     "fwk.uielement", "nItemId outside of expected range!" );
156             xPopupMenu->insertItem( nItemId, langItem, 0, nItemId );
157             if ( langItem == m_aCurLang )
158             {
159                 //make a sign for the current language
160                 xPopupMenu->checkItem( nItemId, true );
161             }
162             aLangMap[ nItemId ] = langItem;
163             ++nItemId;
164         }
165     }
166 
167     if (bWriter)
168     {
169         xPopupMenu->insertItem( MID_LANG_SEL_NONE,  FwkResId(STR_LANGSTATUS_NONE), 0, MID_LANG_SEL_NONE );
170         if ( sNone == m_aCurLang )
171             xPopupMenu->checkItem( MID_LANG_SEL_NONE, true );
172         xPopupMenu->insertItem( MID_LANG_SEL_RESET, FwkResId(STR_RESET_TO_DEFAULT_LANGUAGE), 0, MID_LANG_SEL_RESET );
173         xPopupMenu->insertItem( MID_LANG_SEL_MORE,  FwkResId(STR_LANGSTATUS_MORE), 0, MID_LANG_SEL_MORE );
174 
175         // add entries to submenu ('set language for paragraph')
176         nItemId = static_cast< sal_Int16 >(MID_LANG_PARA_1);
177         for (auto const& langItem : aLangItems)
178         {
179             if( langItem != sNone &&
180                 langItem != sAsterisk &&
181                 !langItem.isEmpty()) // 'no language found' from language guessing
182             {
183                 SAL_WARN_IF( MID_LANG_PARA_1 > nItemId || nItemId > MID_LANG_PARA_9,
184                         "fwk.uielement", "nItemId outside of expected range!" );
185                 subPopupMenu->insertItem( nItemId, langItem, 0, nItemId );
186                 aLangMap[nItemId] = langItem;
187                 ++nItemId;
188             }
189         }
190         subPopupMenu->insertItem( MID_LANG_PARA_NONE,  FwkResId(STR_LANGSTATUS_NONE), 0, MID_LANG_PARA_NONE );
191         subPopupMenu->insertItem( MID_LANG_PARA_RESET, FwkResId(STR_RESET_TO_DEFAULT_LANGUAGE), 0, MID_LANG_PARA_RESET );
192         subPopupMenu->insertItem( MID_LANG_PARA_MORE,  FwkResId(STR_LANGSTATUS_MORE), 0, MID_LANG_PARA_MORE );
193 
194         // add last two entries to main menu
195         xPopupMenu->insertSeparator( MID_LANG_PARA_SEPARATOR );
196         xPopupMenu->insertItem( MID_LANG_PARA_STRING, FwkResId(STR_SET_LANGUAGE_FOR_PARAGRAPH), 0, MID_LANG_PARA_STRING );
197         xPopupMenu->setPopupMenu( MID_LANG_PARA_STRING, subPopupMenu );
198     }
199     else
200     {
201         xPopupMenu->insertItem( MID_LANG_DEF_NONE,  FwkResId(STR_LANGSTATUS_NONE), 0, MID_LANG_DEF_NONE );
202         if ( sNone == m_aCurLang )
203             xPopupMenu->checkItem( MID_LANG_DEF_NONE, true );
204         xPopupMenu->insertItem( MID_LANG_DEF_RESET, FwkResId(STR_RESET_TO_DEFAULT_LANGUAGE), 0, MID_LANG_DEF_RESET );
205         xPopupMenu->insertItem( MID_LANG_DEF_MORE,  FwkResId(STR_LANGSTATUS_MORE), 0, MID_LANG_DEF_MORE );
206     }
207 
208     // now display the popup menu and execute every command ...
209 
210     Reference< awt::XWindowPeer > xParent( m_xParentWindow, UNO_QUERY );
211     css::awt::Rectangle aRect( aPos.X, aPos.Y, 0, 0 );
212     sal_Int16 nId = xPopupMenu->execute( xParent, aRect, css::awt::PopupMenuDirection::EXECUTE_UP+16 );
213     //click "More..."
214     if ( nId && m_xFrame.is() )
215     {
216         OUStringBuffer aBuff;
217         //set selected language as current language for selection
218         const OUString aSelectedLang = aLangMap[nId];
219 
220         if (MID_LANG_SEL_1 <= nId && nId <= MID_LANG_SEL_9)
221         {
222             if (bWriter)
223                 aBuff.append( ".uno:LanguageStatus?Language:string=Current_" );
224             else
225                 aBuff.append( ".uno:LanguageStatus?Language:string=Default_" );
226 
227             aBuff.append( aSelectedLang );
228         }
229         else if (nId == MID_LANG_SEL_NONE)
230         {
231             //set None as current language for selection
232             aBuff.append( ".uno:LanguageStatus?Language:string=Current_LANGUAGE_NONE" );
233         }
234         else if (nId == MID_LANG_SEL_RESET)
235         {
236             // reset language attributes for selection
237             aBuff.append( ".uno:LanguageStatus?Language:string=Current_RESET_LANGUAGES" );
238         }
239         else if (nId == MID_LANG_SEL_MORE)
240         {
241             //open the dialog "format/character" for current selection
242             aBuff.append( ".uno:FontDialog?Page:string=font" );
243         }
244         else if (nId == MID_LANG_DEF_NONE)
245         {
246              aBuff.append( ".uno:LanguageStatus?Language:string=Default_LANGUAGE_NONE" );
247         }
248         else if (nId == MID_LANG_DEF_RESET)
249         {
250              aBuff.append( ".uno:LanguageStatus?Language:string=Default_RESET_LANGUAGES" );
251         }
252         else if (nId == MID_LANG_DEF_MORE)
253         {
254             aBuff.append( ".uno:LanguageStatus?Language:string=*" );
255         }
256         else if (MID_LANG_PARA_1 <= nId && nId <= MID_LANG_PARA_9)
257         {
258             aBuff.append( ".uno:LanguageStatus?Language:string=Paragraph_" );
259             aBuff.append( aSelectedLang );
260         }
261         else if (nId == MID_LANG_PARA_NONE)
262         {
263             //set None as language for current paragraph
264             aBuff.append( ".uno:LanguageStatus?Language:string=Paragraph_LANGUAGE_NONE" );
265         }
266         else if (nId == MID_LANG_PARA_RESET)
267         {
268             // reset language attributes for paragraph
269             aBuff.append( ".uno:LanguageStatus?Language:string=Paragraph_RESET_LANGUAGES" );
270         }
271         else if (nId == MID_LANG_PARA_MORE)
272         {
273             //open the dialog "format/character" for current paragraph
274             aBuff.append( ".uno:FontDialogForParagraph" );
275         }
276 
277         const Sequence< beans::PropertyValue > aDummyArgs;
278         execute( aBuff.makeStringAndClear(), aDummyArgs );
279     }
280 }
281 
282 void SAL_CALL LangSelectionStatusbarController::command(
283     const css::awt::Point& aPos,
284     ::sal_Int32 nCommand,
285     sal_Bool /*bMouseEvent*/,
286     const css::uno::Any& /*aData*/ )
287 {
288     if ( nCommand & ::awt::Command::CONTEXTMENU )
289     {
290         LangMenu( aPos );
291     }
292 }
293 
294 void SAL_CALL LangSelectionStatusbarController::click(
295     const css::awt::Point& aPos )
296 {
297     LangMenu( aPos );
298 }
299 
300 // XStatusListener
301 void SAL_CALL LangSelectionStatusbarController::statusChanged( const FeatureStateEvent& Event )
302 {
303     // This function will be called when observed data changes,
304     // for example the selection or keyboard language.
305     // - It displays the language in use in the status bar
306     // - and it stores the relevant data for creating the menu
307     //   at some later point in the member variables
308     //      m_nScriptType, m_aCurLang, m_aKeyboardLang, m_aGuessedText
309 
310     SolarMutexGuard aSolarMutexGuard;
311 
312     if ( m_bDisposed )
313         return;
314 
315     m_bShowMenu = true;
316     m_nScriptType = SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX;  //set the default value
317 
318     if ( m_xStatusbarItem.is() )
319     {
320         OUString aStrValue;
321         Sequence< OUString > aSeq;
322 
323         if ( Event.State >>= aStrValue )
324         {
325             m_xStatusbarItem->setText( aStrValue );
326             m_aCurLang = aStrValue;
327         }
328         else if ( Event.State >>= aSeq )
329         {
330             if ( aSeq.getLength() == 4 )
331             {
332                 OUString aStatusText = aSeq[0];
333                 if (aStatusText == "*")
334                 {
335                     aStatusText = FwkResId(STR_LANGSTATUS_MULTIPLE_LANGUAGES);
336                 }
337                 m_xStatusbarItem->setText( aStatusText );
338 
339                 // Retrieve all other values from the sequence and
340                 // store it members!
341                 m_aCurLang      = aSeq[0];
342                 m_nScriptType   = static_cast< SvtScriptType >( aSeq[1].toInt32() );
343                 m_aKeyboardLang = aSeq[2];
344                 m_aGuessedTextLang  = aSeq[3];
345             }
346         }
347         else if ( !Event.State.hasValue() )
348         {
349             m_xStatusbarItem->setText( OUString() );
350             m_bShowMenu = false;    // no language -> no menu
351         }
352     }
353 }
354 
355 }
356 
357 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
358 com_sun_star_comp_framework_LangSelectionStatusbarController_get_implementation(
359     css::uno::XComponentContext *context,
360     css::uno::Sequence<css::uno::Any> const &)
361 {
362     return cppu::acquire(new LangSelectionStatusbarController(context));
363 }
364 
365 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
366