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 
10 #include <gtk/gtk.h>
11 
12 #include "gtv-application-window.hxx"
13 #include "gtv-main-toolbar.hxx"
14 #include "gtv-signal-handlers.hxx"
15 #include "gtv-helpers.hxx"
16 
17 #include <LibreOfficeKit/LibreOfficeKitGtk.h>
18 
19 #include <fstream>
20 #include <map>
21 #include <memory>
22 #include <vector>
23 
24 namespace {
25 
26 struct GtvMainToolbarPrivateImpl
27 {
28     GtkWidget* toolbar1;
29     GtkWidget* toolbar2;
30 
31     GtkWidget* m_pEnableEditing;
32     GtkWidget* m_pLeftpara;
33     GtkWidget* m_pCenterpara;
34     GtkWidget* m_pRightpara;
35     GtkWidget* m_pJustifypara;
36     GtkWidget* m_pDeleteComment;
37     GtkWidget* m_pPartSelector;
38     GtkWidget* m_pPartModeSelector;
39     GtkWidget* m_pRecentUnoSelector;
40     std::map<std::string, std::string> m_pRecentUnoCommands;
41 
42     /// Sensitivity (enabled or disabled) for each tool item, ignoring edit state
43     std::map<GtkToolItem*, bool> m_aToolItemSensitivities;
44 
GtvMainToolbarPrivateImpl__anon5c5401dc0111::GtvMainToolbarPrivateImpl45     GtvMainToolbarPrivateImpl() :
46         toolbar1(nullptr),
47         toolbar2(nullptr),
48         m_pEnableEditing(nullptr),
49         m_pLeftpara(nullptr),
50         m_pCenterpara(nullptr),
51         m_pRightpara(nullptr),
52         m_pJustifypara(nullptr),
53         m_pDeleteComment(nullptr),
54         m_pPartSelector(nullptr),
55         m_pPartModeSelector(nullptr),
56         m_pRecentUnoSelector(nullptr)
57         { }
58 };
59 
60 struct GtvMainToolbarPrivate
61 {
62     GtvMainToolbarPrivateImpl* m_pImpl;
63 
operator ->__anon5c5401dc0111::GtvMainToolbarPrivate64     GtvMainToolbarPrivateImpl* operator->()
65     {
66         return m_pImpl;
67     }
68 };
69 
70 }
71 
72 #if defined __clang__
73 #if __has_warning("-Wdeprecated-volatile")
74 #pragma clang diagnostic push
75 #pragma clang diagnostic ignored "-Wdeprecated-volatile"
76 #endif
77 #endif
78 G_DEFINE_TYPE_WITH_PRIVATE(GtvMainToolbar, gtv_main_toolbar, GTK_TYPE_BOX);
79 #if defined __clang__
80 #if __has_warning("-Wdeprecated-volatile")
81 #pragma clang diagnostic pop
82 #endif
83 #endif
84 
85 static GtvMainToolbarPrivate&
getPrivate(GtvMainToolbar * toolbar)86 getPrivate(GtvMainToolbar* toolbar)
87 {
88     return *static_cast<GtvMainToolbarPrivate*>(gtv_main_toolbar_get_instance_private(toolbar));
89 }
90 
91 static void
gtv_main_toolbar_init(GtvMainToolbar * toolbar)92 gtv_main_toolbar_init(GtvMainToolbar* toolbar)
93 {
94     GtvMainToolbarPrivate& priv = getPrivate(toolbar);
95     priv.m_pImpl = new GtvMainToolbarPrivateImpl();
96 
97     const std::string uiFilePath = GtvHelpers::getDirPath(__FILE__) + std::string(UI_FILE_NAME);
98     GtvGtkWrapper<GtkBuilder> builder(gtk_builder_new_from_file(uiFilePath.c_str()),
99                                       [](GtkBuilder* pBuilder) {
100                                           g_object_unref(pBuilder);
101                                       });
102 
103     priv->toolbar1 = GTK_WIDGET(gtk_builder_get_object(builder.get(), "toolbar1"));
104     gtk_box_pack_start(GTK_BOX(toolbar), priv->toolbar1, false, false, false);
105     priv->toolbar2 = GTK_WIDGET(gtk_builder_get_object(builder.get(), "toolbar2"));
106     gtk_box_pack_start(GTK_BOX(toolbar), priv->toolbar2, false, false, false);
107 
108     priv->m_pEnableEditing = GTK_WIDGET(gtk_builder_get_object(builder.get(), "btn_editmode"));
109     priv->m_pLeftpara = GTK_WIDGET(gtk_builder_get_object(builder.get(), "btn_justifyleft"));
110     priv->m_pCenterpara = GTK_WIDGET(gtk_builder_get_object(builder.get(), "btn_justifycenter"));
111     priv->m_pRightpara = GTK_WIDGET(gtk_builder_get_object(builder.get(), "btn_justifyright"));
112     priv->m_pJustifypara = GTK_WIDGET(gtk_builder_get_object(builder.get(), "btn_justifyfill"));
113     priv->m_pDeleteComment = GTK_WIDGET(gtk_builder_get_object(builder.get(), "btn_removeannotation"));
114     priv->m_pPartSelector = GTK_WIDGET(gtk_builder_get_object(builder.get(), "combo_partselector"));
115     priv->m_pPartModeSelector = GTK_WIDGET(gtk_builder_get_object(builder.get(), "combo_partsmodeselector"));
116     priv->m_pRecentUnoSelector = GTK_WIDGET(gtk_builder_get_object(builder.get(), "combo_recentunoselector"));
117 
118     toolbar->m_pAddressbar = GTK_WIDGET(gtk_builder_get_object(builder.get(), "addressbar_entry"));
119     toolbar->m_pFormulabar = GTK_WIDGET(gtk_builder_get_object(builder.get(), "formulabar_entry"));
120 
121     // TODO: compile with -rdynamic and get rid of it
122     gtk_builder_add_callback_symbol(builder.get(), "btn_clicked", G_CALLBACK(btn_clicked));
123     gtk_builder_add_callback_symbol(builder.get(), "doCopy", G_CALLBACK(doCopy));
124     gtk_builder_add_callback_symbol(builder.get(), "doPaste", G_CALLBACK(doPaste));
125     gtk_builder_add_callback_symbol(builder.get(), "createView", G_CALLBACK(createView));
126     gtk_builder_add_callback_symbol(builder.get(), "getRulerState", G_CALLBACK(getRulerState));
127     gtk_builder_add_callback_symbol(builder.get(), "recentUnoChanged", G_CALLBACK(recentUnoChanged));
128     gtk_builder_add_callback_symbol(builder.get(), "unoCommandDebugger", G_CALLBACK(unoCommandDebugger));
129     gtk_builder_add_callback_symbol(builder.get(), "toggleEditing", G_CALLBACK(toggleEditing));
130     gtk_builder_add_callback_symbol(builder.get(), "changePartMode", G_CALLBACK(changePartMode));
131     gtk_builder_add_callback_symbol(builder.get(), "changePart", G_CALLBACK(changePart));
132     gtk_builder_add_callback_symbol(builder.get(), "changeZoom", G_CALLBACK(changeZoom));
133     gtk_builder_add_callback_symbol(builder.get(), "toggleFindbar", G_CALLBACK(toggleFindbar));
134     gtk_builder_add_callback_symbol(builder.get(), "documentRedline", G_CALLBACK(documentRedline));
135     gtk_builder_add_callback_symbol(builder.get(), "documentRepair", G_CALLBACK(documentRepair));
136     gtk_builder_add_callback_symbol(builder.get(), "signalAddressbar", G_CALLBACK(signalAddressbar));
137     gtk_builder_add_callback_symbol(builder.get(), "signalFormulabar", G_CALLBACK(signalFormulabar));
138 
139     // find toolbar
140     // Note: These buttons are not the part of GtvMainToolbar
141     gtk_builder_add_callback_symbol(builder.get(), "signalSearchNext", G_CALLBACK(signalSearchNext));
142     gtk_builder_add_callback_symbol(builder.get(), "signalSearchPrev", G_CALLBACK(signalSearchPrev));
143     gtk_builder_add_callback_symbol(builder.get(), "signalFindbar", G_CALLBACK(signalFindbar));
144     gtk_builder_add_callback_symbol(builder.get(), "toggleFindAll", G_CALLBACK(toggleFindAll));
145 
146     gtk_builder_connect_signals(builder.get(), nullptr);
147 
148     gtk_widget_show_all(GTK_WIDGET(toolbar));
149 }
150 
151 static void
gtv_main_toolbar_finalize(GObject * object)152 gtv_main_toolbar_finalize(GObject* object)
153 {
154     GtvMainToolbarPrivate& priv = getPrivate(GTV_MAIN_TOOLBAR(object));
155 
156     delete priv.m_pImpl;
157     priv.m_pImpl = nullptr;
158 
159     G_OBJECT_CLASS (gtv_main_toolbar_parent_class)->finalize (object);
160 }
161 
162 static void
gtv_main_toolbar_class_init(GtvMainToolbarClass * klass)163 gtv_main_toolbar_class_init(GtvMainToolbarClass* klass)
164 {
165     G_OBJECT_CLASS(klass)->finalize = gtv_main_toolbar_finalize;
166 }
167 
populatePartSelector(GtvMainToolbar * toolbar)168 static void populatePartSelector(GtvMainToolbar* toolbar)
169 {
170     GtvMainToolbarPrivate& priv = getPrivate(toolbar);
171     GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbar)));
172     gtv_application_window_set_part_broadcast(window, false);
173     gtk_list_store_clear( GTK_LIST_STORE(
174                               gtk_combo_box_get_model(
175                                   GTK_COMBO_BOX(priv->m_pPartSelector) )) );
176 
177     if (!window->lokdocview)
178     {
179         return;
180     }
181 
182     const int nMaxLength = 50;
183     char sText[nMaxLength];
184 
185     int nParts = lok_doc_view_get_parts(LOK_DOC_VIEW(window->lokdocview));
186     for ( int i = 0; i < nParts; i++ )
187     {
188         char* pName = lok_doc_view_get_part_name(LOK_DOC_VIEW(window->lokdocview), i);
189         assert( pName );
190         snprintf( sText, nMaxLength, "%i (%s)", i+1, pName );
191         free( pName );
192 
193         gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(priv->m_pPartSelector), sText );
194     }
195     gtk_combo_box_set_active(GTK_COMBO_BOX(priv->m_pPartSelector), lok_doc_view_get_part(LOK_DOC_VIEW(window->lokdocview)));
196 
197     gtv_application_window_set_part_broadcast(window, true);
198 }
199 
populateRecentUnoSelector(GtvMainToolbar * toolbar)200 static void populateRecentUnoSelector(GtvMainToolbar* toolbar)
201 {
202     GtvMainToolbarPrivate& priv = getPrivate(toolbar);
203     GtkComboBoxText* pSelector = GTK_COMBO_BOX_TEXT(priv->m_pRecentUnoSelector);
204 
205     unsigned counter = 0;
206     std::ifstream is("/tmp/gtv-recentunos.txt");
207     while (is.good() && counter < 10)
208     {
209         std::string unoCommandStr;
210         std::getline(is, unoCommandStr);
211         std::vector<std::string> aUnoCmd = GtvHelpers::split<std::string>(unoCommandStr, " | ", 2);
212         if (aUnoCmd.size() != 2)
213             continue;
214         auto it = priv->m_pRecentUnoCommands.emplace(aUnoCmd[0], aUnoCmd[1]);
215         if (it.second)
216         {
217             gtk_combo_box_text_append_text(pSelector, aUnoCmd[0].c_str());
218             ++counter;
219         }
220     }
221 }
222 
223 void
gtv_main_toolbar_doc_loaded(GtvMainToolbar * toolbar,LibreOfficeKitDocumentType eDocType,bool bEditMode)224 gtv_main_toolbar_doc_loaded(GtvMainToolbar* toolbar, LibreOfficeKitDocumentType eDocType, bool bEditMode)
225 {
226     GtvMainToolbarPrivate& priv = getPrivate(toolbar);
227     gtk_widget_set_visible(toolbar->m_pAddressbar, false);
228     gtk_widget_set_visible(toolbar->m_pFormulabar, false);
229     if (eDocType == LOK_DOCTYPE_SPREADSHEET)
230     {
231         gtk_tool_button_set_label(GTK_TOOL_BUTTON(priv->m_pLeftpara), ".uno:AlignLeft");
232         gtk_tool_button_set_label(GTK_TOOL_BUTTON(priv->m_pCenterpara), ".uno:AlignHorizontalCenter");
233         gtk_tool_button_set_label(GTK_TOOL_BUTTON(priv->m_pRightpara), ".uno:AlignRight");
234         gtk_widget_hide(priv->m_pJustifypara);
235         gtk_tool_button_set_label(GTK_TOOL_BUTTON(priv->m_pDeleteComment), ".uno:DeleteNote");
236 
237         gtk_widget_set_visible(toolbar->m_pAddressbar, true);
238         gtk_widget_set_visible(toolbar->m_pFormulabar, true);
239     }
240     else if (eDocType == LOK_DOCTYPE_PRESENTATION)
241     {
242         gtk_tool_button_set_label(GTK_TOOL_BUTTON(priv->m_pDeleteComment), ".uno:DeleteAnnotation");
243     }
244 
245     gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->m_pEnableEditing), bEditMode);
246 
247     // populate combo boxes
248     populatePartSelector(toolbar);
249 
250     // populate recent uno selector
251     populateRecentUnoSelector(toolbar);
252 }
253 
254 void
gtv_main_toolbar_add_recent_uno(GtvMainToolbar * toolbar,const std::string & rUnoCmdStr)255 gtv_main_toolbar_add_recent_uno(GtvMainToolbar* toolbar, const std::string& rUnoCmdStr)
256 {
257     GtvMainToolbarPrivate& priv = getPrivate(toolbar);
258     GtkComboBoxText* pSelector = GTK_COMBO_BOX_TEXT(priv->m_pRecentUnoSelector);
259 
260     const std::vector<std::string> aUnoCmd = GtvHelpers::split<std::string>(rUnoCmdStr, " | ", 2);
261     priv->m_pRecentUnoCommands[aUnoCmd[0]] = aUnoCmd[1];
262     // keep placeholder string at the top
263     gtk_combo_box_text_insert_text(pSelector, 1, aUnoCmd[0].c_str());
264     // TODO: Remove other text entries with same key
265 }
266 
267 std::string
gtv_main_toolbar_get_recent_uno_args(GtvMainToolbar * toolbar,const std::string & rUnoCmd)268 gtv_main_toolbar_get_recent_uno_args(GtvMainToolbar* toolbar, const std::string& rUnoCmd)
269 {
270     GtvMainToolbarPrivate& priv = getPrivate(toolbar);
271     auto it = std::find_if(priv->m_pRecentUnoCommands.begin(), priv->m_pRecentUnoCommands.end(),
272                            [&rUnoCmd](const std::pair<std::string, std::string>& pair) {
273                                return rUnoCmd == pair.first;
274                            });
275     std::string ret;
276     if (it != priv->m_pRecentUnoCommands.end())
277         ret = it->second;
278     return ret;
279 }
280 
281 GtkContainer*
gtv_main_toolbar_get_first_toolbar(GtvMainToolbar * toolbar)282 gtv_main_toolbar_get_first_toolbar(GtvMainToolbar* toolbar)
283 {
284     GtvMainToolbarPrivate& priv = getPrivate(toolbar);
285     return GTK_CONTAINER(priv->toolbar1);
286 }
287 
288 GtkContainer*
gtv_main_toolbar_get_second_toolbar(GtvMainToolbar * toolbar)289 gtv_main_toolbar_get_second_toolbar(GtvMainToolbar* toolbar)
290 {
291     GtvMainToolbarPrivate& priv = getPrivate(toolbar);
292     return GTK_CONTAINER(priv->toolbar2);
293 }
294 
295 void
gtv_main_toolbar_set_sensitive_internal(GtvMainToolbar * toolbar,GtkToolItem * pItem,bool isSensitive)296 gtv_main_toolbar_set_sensitive_internal(GtvMainToolbar* toolbar, GtkToolItem* pItem, bool isSensitive)
297 {
298     GtvMainToolbarPrivate& priv = getPrivate(toolbar);
299     priv->m_aToolItemSensitivities[pItem] = isSensitive;
300 }
301 
setSensitiveIfEdit(GtvMainToolbar * toolbar,GtkToolItem * pItem,bool bEdit)302 static void setSensitiveIfEdit(GtvMainToolbar* toolbar, GtkToolItem* pItem, bool bEdit)
303 {
304     GtvMainToolbarPrivate& priv = getPrivate(toolbar);
305     // some buttons remain enabled always
306     const gchar* pIconName = gtk_tool_button_get_icon_name(GTK_TOOL_BUTTON(pItem));
307     if (g_strcmp0(pIconName, "zoom-in-symbolic") != 0 &&
308         g_strcmp0(pIconName, "zoom-original-symbolic") != 0 &&
309         g_strcmp0(pIconName, "zoom-out-symbolic") != 0 &&
310         g_strcmp0(pIconName, "insert-text-symbolic") != 0 &&
311         g_strcmp0(pIconName, "view-continuous-symbolic") != 0 &&
312         g_strcmp0(pIconName, "document-properties") != 0 &&
313         g_strcmp0(pIconName, "system-run") != 0)
314     {
315         bool state = true;
316         if (priv->m_aToolItemSensitivities.find(pItem) != priv->m_aToolItemSensitivities.end())
317             state = priv->m_aToolItemSensitivities[pItem];
318 
319         gtk_widget_set_sensitive(GTK_WIDGET(pItem), bEdit && state);
320     }
321 }
322 
323 void
gtv_main_toolbar_set_edit(GtvMainToolbar * toolbar,gboolean bEdit)324 gtv_main_toolbar_set_edit(GtvMainToolbar* toolbar, gboolean bEdit)
325 {
326     GtvMainToolbarPrivate& priv = getPrivate(toolbar);
327     GtvGtkWrapper<GList> pList(gtk_container_get_children(GTK_CONTAINER(priv->toolbar1)),
328                             [](GList* l)
329                             {
330                                 g_list_free(l);
331                             });
332     for (GList* l = pList.get(); l != nullptr; l = l->next)
333     {
334         if (GTK_IS_TOOL_BUTTON(l->data))
335         {
336             setSensitiveIfEdit(toolbar, GTK_TOOL_ITEM(l->data), bEdit);
337         }
338     }
339 
340     pList.reset(gtk_container_get_children(GTK_CONTAINER(priv->toolbar2)));
341     for (GList* l = pList.get(); l != nullptr; l = l->next)
342     {
343         if (GTK_IS_TOOL_BUTTON(l->data))
344         {
345             setSensitiveIfEdit(toolbar, GTK_TOOL_ITEM(l->data), bEdit);
346         }
347     }
348 }
349 
350 GtkWidget*
gtv_main_toolbar_new()351 gtv_main_toolbar_new()
352 {
353     return GTK_WIDGET(g_object_new(GTV_TYPE_MAIN_TOOLBAR,
354                                    "orientation", GTK_ORIENTATION_VERTICAL,
355                                    nullptr));
356 }
357 
358 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
359