1 /*
2 * This file is part of the Code::Blocks IDE and licensed under the GNU Lesser General Public License, version 3
3 * http://www.gnu.org/licenses/lgpl-3.0.html
4 *
5 * $Revision: 11313 $
6 * $Id: editorbase.cpp 11313 2018-03-10 11:00:49Z fuscated $
7 * $HeadURL: svn://svn.code.sf.net/p/codeblocks/code/branches/release-20.xx/src/sdk/editorbase.cpp $
8 */
9
10 #include "sdk_precomp.h"
11
12 #ifndef CB_PRECOMP
13 #include <wx/filename.h>
14 #include <wx/notebook.h>
15 #include <wx/menu.h>
16 #include "manager.h"
17 #include "editorbase.h"
18 #include "cbeditor.h"
19 #include "editormanager.h"
20 #include "pluginmanager.h"
21 #include "cbproject.h" // FileTreeData
22 #include "projectmanager.h" // ProjectsArray
23 #include <wx/wfstream.h>
24 #endif
25
26 #include "cbauibook.h"
27
28 #include "cbstyledtextctrl.h"
29
30 // needed for initialization of variables
editorbase_RegisterId(int id)31 inline int editorbase_RegisterId(int id)
32 {
33 wxRegisterId(id);
34 return id;
35 }
36
37 struct EditorBaseInternalData
38 {
EditorBaseInternalDataEditorBaseInternalData39 EditorBaseInternalData(EditorBase* owner)
40 : m_pOwner(owner),
41 m_DisplayingPopupMenu(false),
42 m_CloseMe(false)
43 {}
44
45 EditorBase* m_pOwner;
46 bool m_DisplayingPopupMenu;
47 bool m_CloseMe;
48 };
49
50 // The following lines reserve 255 consecutive id's
51 const int EditorMaxSwitchTo = 255;
52 const int idSwitchFile1 = wxNewId();
53 const int idSwitchFileMax = editorbase_RegisterId(idSwitchFile1 + EditorMaxSwitchTo -1);
54
55 const int idCloseMe = wxNewId();
56 const int idCloseAll = wxNewId();
57 const int idCloseAllOthers = wxNewId();
58 const int idSaveMe = wxNewId();
59 const int idSaveAll = wxNewId();
60 const int idSwitchTo = wxNewId();
61
62 // Search for... in the context menu, visible with CTRL + right click
63 const long idGoogle = wxNewId();
64 const long idMsdn = wxNewId();
65 const long idStackOverflow = wxNewId();
66 const long idCodeProject = wxNewId();
67 const long idCPlusPlusCom = wxNewId();
68
BEGIN_EVENT_TABLE(EditorBase,wxPanel)69 BEGIN_EVENT_TABLE(EditorBase, wxPanel)
70 EVT_MENU_RANGE(idSwitchFile1, idSwitchFileMax, EditorBase::OnContextMenuEntry)
71 EVT_MENU(idCloseMe, EditorBase::OnContextMenuEntry)
72 EVT_MENU(idCloseAll, EditorBase::OnContextMenuEntry)
73 EVT_MENU(idCloseAllOthers, EditorBase::OnContextMenuEntry)
74 EVT_MENU(idSaveMe, EditorBase::OnContextMenuEntry)
75 EVT_MENU(idSaveAll, EditorBase::OnContextMenuEntry)
76
77 EVT_MENU(idGoogle, EditorBase::OnContextMenuEntry)
78 EVT_MENU(idMsdn, EditorBase::OnContextMenuEntry)
79 EVT_MENU(idStackOverflow, EditorBase::OnContextMenuEntry)
80 EVT_MENU(idCodeProject, EditorBase::OnContextMenuEntry)
81 EVT_MENU(idCPlusPlusCom, EditorBase::OnContextMenuEntry)
82 END_EVENT_TABLE()
83
84 void EditorBase::InitFilename(const wxString& filename)
85 {
86 if (filename.IsEmpty())
87 m_Filename = realpath(CreateUniqueFilename());
88 else
89 m_Filename = realpath(filename);
90
91 wxFileName fname;
92 fname.Assign(m_Filename);
93 m_Shortname = fname.GetFullName();
94 }
95
CreateUniqueFilename()96 wxString EditorBase::CreateUniqueFilename()
97 {
98 const wxString prefix = _("Untitled");
99 const wxString path = wxGetCwd() + wxFILE_SEP_PATH;
100 wxString tmp;
101 int iter = 0;
102 while (true)
103 {
104 tmp.Clear();
105 tmp << path << prefix << wxString::Format(_T("%d"), iter);
106 if (!Manager::Get()->GetEditorManager()->GetEditor(tmp) &&
107 !wxFileExists(path + tmp))
108 {
109 return tmp;
110 }
111 ++iter;
112 }
113 }
114
EditorBase(wxWindow * parent,const wxString & filename)115 EditorBase::EditorBase(wxWindow* parent, const wxString& filename)
116 : wxPanel(parent, -1),
117 m_IsBuiltinEditor(false),
118 m_Shortname(_T("")),
119 m_Filename(_T("")),
120 m_WinTitle(filename)
121 {
122 m_pData = new EditorBaseInternalData(this);
123
124 Manager::Get()->GetEditorManager()->AddCustomEditor(this);
125 InitFilename(filename);
126 SetTitle(m_Shortname);
127 }
128
~EditorBase()129 EditorBase::~EditorBase()
130 {
131 if (!Manager::Get()->IsAppShuttingDown())
132 {
133 Manager::Get()->GetEditorManager()->RemoveCustomEditor(this);
134
135 CodeBlocksEvent event(cbEVT_EDITOR_CLOSE);
136 event.SetEditor(this);
137 event.SetString(m_Filename);
138
139 Manager::Get()->GetPluginManager()->NotifyPlugins(event);
140 }
141 delete m_pData;
142 }
143
GetTitle()144 const wxString& EditorBase::GetTitle()
145 {
146 return m_WinTitle;
147 }
148
SetTitle(const wxString & newTitle)149 void EditorBase::SetTitle(const wxString& newTitle)
150 {
151 m_WinTitle = newTitle;
152 int mypage = Manager::Get()->GetEditorManager()->FindPageFromEditor(this);
153 if (mypage != -1)
154 Manager::Get()->GetEditorManager()->GetNotebook()->SetPageText(mypage, newTitle);
155
156 // set full filename (including path) as tooltip,
157 // if possible add the appropriate project also
158 wxString toolTip = GetFilename();
159 wxFileName fname(realpath(toolTip));
160 NormalizePath(fname, wxEmptyString);
161 toolTip = UnixFilename(fname.GetFullPath());
162
163 cbProject* prj = Manager::Get()->GetProjectManager()->FindProjectForFile(toolTip, nullptr, false, true);
164 if (prj)
165 toolTip += _("\nProject: ") + prj->GetTitle();
166 cbAuiNotebook* nb = Manager::Get()->GetEditorManager()->GetNotebook();
167 if (nb)
168 {
169 int idx = nb->GetPageIndex(this);
170 nb->SetPageToolTip(idx, toolTip);
171 Manager::Get()->GetEditorManager()->MarkReadOnly(idx, IsReadOnly() || (fname.FileExists() && !wxFile::Access(fname.GetFullPath(), wxFile::write)) );
172 }
173 }
174
Activate()175 void EditorBase::Activate()
176 {
177 Manager::Get()->GetEditorManager()->SetActiveEditor(this);
178 }
179
QueryClose()180 bool EditorBase::QueryClose()
181 {
182 if ( GetModified() )
183 {
184 wxString msg;
185 msg.Printf(_("File %s is modified...\nDo you want to save the changes?"), GetFilename().c_str());
186 switch (cbMessageBox(msg, _("Save file"), wxICON_QUESTION | wxYES_NO | wxCANCEL))
187 {
188 case wxID_YES:
189 if (!Save())
190 return false;
191 break;
192 case wxID_NO:
193 break;
194 case wxID_CANCEL:
195 default:
196 return false;
197 }
198 SetModified(false);
199 }
200 return true;
201 }
202
Close()203 bool EditorBase::Close()
204 {
205 Destroy();
206 return true;
207 }
208
IsBuiltinEditor() const209 bool EditorBase::IsBuiltinEditor() const
210 {
211 return m_IsBuiltinEditor;
212 }
213
ThereAreOthers() const214 bool EditorBase::ThereAreOthers() const
215 {
216 return (Manager::Get()->GetEditorManager()->GetEditorsCount() > 1);
217 }
218
CreateContextSubMenu(long id)219 wxMenu* EditorBase::CreateContextSubMenu(long id) // For context menus
220 {
221 wxMenu* menu = nullptr;
222
223 if (id == idSwitchTo)
224 {
225 menu = new wxMenu;
226 m_SwitchTo.clear();
227 for (int i = 0; i < EditorMaxSwitchTo && i < Manager::Get()->GetEditorManager()->GetEditorsCount(); ++i)
228 {
229 EditorBase* other = Manager::Get()->GetEditorManager()->GetEditor(i);
230 if (!other || other == this)
231 continue;
232 id = idSwitchFile1+i;
233 m_SwitchTo[id] = other;
234 menu->Append(id, (other->GetModified() ? wxT("*") : wxEmptyString) + other->GetShortName());
235 }
236 if (!menu->GetMenuItemCount())
237 {
238 delete menu;
239 menu = nullptr;
240 }
241 }
242 return menu;
243 }
244
BasicAddToContextMenu(wxMenu * popup,ModuleType type)245 void EditorBase::BasicAddToContextMenu(wxMenu* popup, ModuleType type)
246 {
247 if (type == mtOpenFilesList)
248 {
249 popup->Append(idCloseMe, _("Close"));
250 popup->Append(idCloseAll, _("Close all"));
251 popup->Append(idCloseAllOthers, _("Close all others"));
252 popup->AppendSeparator();
253 popup->Append(idSaveMe, _("Save"));
254 popup->Append(idSaveAll, _("Save all"));
255 popup->AppendSeparator();
256 // enable/disable some items, based on state
257 popup->Enable(idSaveMe, GetModified());
258
259 bool hasOthers = ThereAreOthers();
260 popup->Enable(idCloseAll, hasOthers);
261 popup->Enable(idCloseAllOthers, hasOthers);
262 }
263 if (type != mtEditorManager) // no editor
264 {
265 wxMenu* switchto = CreateContextSubMenu(idSwitchTo);
266 if (switchto)
267 popup->Append(idSwitchTo, _("Switch to"), switchto);
268 }
269 }
270
DisplayContextMenu(const wxPoint & position,ModuleType type)271 void EditorBase::DisplayContextMenu(const wxPoint& position, ModuleType type)
272 {
273 bool noeditor = (type != mtEditorManager);
274 // noeditor:
275 // True if context menu belongs to open files tree;
276 // False if belongs to cbEditor
277
278 // inform the editors we 're just about to create a context menu
279 if (!OnBeforeBuildContextMenu(position, type))
280 return;
281
282 wxMenu* popup = new wxMenu;
283
284 PluginManager *pluginManager = Manager::Get()->GetPluginManager();
285 pluginManager->ResetModuleMenu();
286
287 if (!noeditor && wxGetKeyState(WXK_CONTROL))
288 {
289 cbStyledTextCtrl* control = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor()->GetControl();
290 wxString text = control->GetSelectedText();
291 if (text.IsEmpty())
292 {
293 const int pos = control->GetCurrentPos();
294 text = control->GetTextRange(control->WordStartPosition(pos, true), control->WordEndPosition(pos, true));
295 }
296
297 if (!text.IsEmpty())
298 {
299 popup->Append(idGoogle, _("Search the Internet for \"") + text + _T("\""));
300 popup->Append(idMsdn, _("Search MSDN for \"") + text + _T("\""));
301 popup->Append(idStackOverflow, _("Search StackOverflow for \"") + text + _T("\""));
302 popup->Append(idCodeProject, _("Search CodeProject for \"") + text + _T("\""));
303 popup->Append(idCPlusPlusCom, _("Search CplusPlus.com for \"") + text + _T("\""));
304 }
305 lastWord = text;
306
307 wxMenu* switchto = CreateContextSubMenu(idSwitchTo);
308 if (switchto)
309 {
310 if (popup->GetMenuItemCount() > 0)
311 popup->AppendSeparator();
312 popup->Append(idSwitchTo, _("Switch to"), switchto);
313 }
314 }
315 else if (!noeditor && wxGetKeyState(WXK_ALT))
316 { // run a script
317 }
318 else
319 {
320 // Basic functions
321 BasicAddToContextMenu(popup, type);
322
323 // Extended functions, part 1 (virtual)
324 AddToContextMenu(popup, type, false);
325
326 // ask other editors / plugins if they need to add any entries in this menu...
327 FileTreeData* ftd = new FileTreeData(nullptr, FileTreeData::ftdkUndefined);
328 ftd->SetFolder(m_Filename);
329 pluginManager->AskPluginsForModuleMenu(type, popup, ftd);
330 delete ftd;
331
332 popup->AppendSeparator();
333 // Extended functions, part 2 (virtual)
334 AddToContextMenu(popup, type, true);
335 }
336
337 // Check if the last item is a separator and remove it.
338 const wxMenuItemList &popupItems = popup->GetMenuItems();
339 if (popupItems.GetCount() > 0)
340 {
341 wxMenuItem *last = popupItems[popupItems.GetCount() - 1];
342 if (last && last->IsSeparator())
343 popup->Remove(last);
344 }
345
346 // Insert a separator at the end of the "Find XXX" menu group of items.
347 const int lastFind = pluginManager->GetFindMenuItemFirst() + pluginManager->GetFindMenuItemCount();
348 if (lastFind > 0)
349 popup->Insert(lastFind, wxID_SEPARATOR, wxEmptyString);
350
351 // inform the editors we 're done creating a context menu (just about to show it)
352 OnAfterBuildContextMenu(type);
353
354 // display menu
355 wxPoint clientpos;
356 if (position==wxDefaultPosition) // "context menu" key
357 {
358 // obtain the caret point (on the screen) as we assume
359 // that the user wants to work with the keyboard
360 cbStyledTextCtrl* const control = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor()->GetControl();
361 clientpos = control->PointFromPosition(control->GetCurrentPos());
362 }
363 else
364 {
365 clientpos = ScreenToClient(position);
366 }
367
368 m_pData->m_DisplayingPopupMenu = true;
369 PopupMenu(popup, clientpos);
370 delete popup;
371 m_pData->m_DisplayingPopupMenu = false;
372
373 // this code *must* be the last code executed by this function
374 // because it *will* invalidate 'this'
375 if (m_pData->m_CloseMe)
376 Manager::Get()->GetEditorManager()->Close(this);
377 }
378
OnContextMenuEntry(wxCommandEvent & event)379 void EditorBase::OnContextMenuEntry(wxCommandEvent& event)
380 {
381 // we have a single event handler for all popup menu entries
382 // This was ported from cbEditor and used for the basic operations:
383 // Switch to, close, save, etc.
384
385 const int id = event.GetId();
386 m_pData->m_CloseMe = false;
387
388 if (id == idCloseMe)
389 {
390 if (m_pData->m_DisplayingPopupMenu)
391 m_pData->m_CloseMe = true; // defer delete 'this' until after PopupMenu() call returns
392 else
393 Manager::Get()->GetEditorManager()->Close(this);
394 }
395 else if (id == idCloseAll)
396 {
397 if (m_pData->m_DisplayingPopupMenu)
398 {
399 Manager::Get()->GetEditorManager()->CloseAllInTabCtrlExcept(this);
400 m_pData->m_CloseMe = true; // defer delete 'this' until after PopupMenu() call returns
401 }
402 else
403 Manager::Get()->GetEditorManager()->CloseAllInTabCtrl();
404 }
405 else if (id == idCloseAllOthers)
406 {
407 Manager::Get()->GetEditorManager()->CloseAllInTabCtrlExcept(this);
408 }
409 else if (id == idSaveMe)
410 {
411 Save();
412 }
413 else if (id == idSaveAll)
414 {
415 Manager::Get()->GetEditorManager()->SaveAll();
416 }
417 else if (id >= idSwitchFile1 && id <= idSwitchFileMax)
418 {
419 // "Switch to..." item
420 EditorBase* const ed = m_SwitchTo[id];
421 if (ed)
422 Manager::Get()->GetEditorManager()->SetActiveEditor(ed);
423
424 m_SwitchTo.clear();
425 }
426 else
427 {
428 if (id == idGoogle)
429 wxLaunchDefaultBrowser(wxString(_T("http://www.google.com/search?q=")) << URLEncode(lastWord));
430 else if (id == idMsdn)
431 wxLaunchDefaultBrowser(wxString(_T("http://social.msdn.microsoft.com/Search/en-US/?query=")) << URLEncode(lastWord) << _T("&ac=8"));
432 else if (id == idStackOverflow)
433 wxLaunchDefaultBrowser(wxString(_T("http://stackoverflow.com/search?q=")) << URLEncode(lastWord));
434 else if (id == idCodeProject)
435 wxLaunchDefaultBrowser(wxString(_T("http://www.codeproject.com/search.aspx?q=")) << URLEncode(lastWord));
436 else if (id == idCPlusPlusCom)
437 wxLaunchDefaultBrowser(wxString(_T("http://www.cplusplus.com/search.do?q=")) << URLEncode(lastWord));
438 }
439 }
440
IsContextMenuOpened() const441 bool EditorBase::IsContextMenuOpened() const
442 {
443 return m_pData->m_DisplayingPopupMenu;
444 }
445