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