1 /////////////////////////////////////////////////////////////////////////////
2 // Author: Vaclav Slavik
5 // Copyright: (c) 2000 Vaclav Slavik
6 // Licence: wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
10 #pragma implementation "editor.h"
11 #pragma implementation "treedt.h"
14 // For compilers that support precompilation, includes "wx/wx.h".
15 #include "wx/wxprec.h"
22 #include "wx/xrc/xml.h"
23 #include "wx/xrc/xmlres.h"
24 #include "wx/splitter.h"
25 #include "wx/config.h"
27 #include "wx/listctrl.h"
28 #include "wx/imaglist.h"
35 #include "propframe.h"
38 void wxXmlRcEditDocument::UpgradeNodeValue(wxXmlNode
*node
)
41 if (n
== NULL
) return;
46 if (n
->GetType() == wxXML_TEXT_NODE
||
47 n
->GetType() == wxXML_CDATA_SECTION_NODE
)
49 wxString str1
= n
->GetContent();
52 for (dt
= str1
.c_str(); *dt
; dt
++)
54 // Remap amp_char to &, map double amp_char to amp_char (for things
55 // like "&File..." -- this is illegal in XML, so we use "_File..."):
59 str1
[size_t(dt
-str1
.c_str()-1)] = '_';
68 void wxXmlRcEditDocument::UpgradeNode(wxXmlNode
*node
)
72 UpgradeNodeValue(node
);
73 UpgradeNode(node
->GetNext());
74 UpgradeNode(node
->GetChildren());
78 void wxXmlRcEditDocument::Upgrade()
82 wxXmlNode
*node
= GetRoot();
83 wxString verstr
= wxT("0.0.0.0");
84 node
->GetPropVal(wxT("version"),verstr
);
85 if (wxSscanf(verstr
.c_str(), wxT("%i.%i.%i.%i"),
86 &v1
, &v2
, &v3
, &v4
) == 4)
87 version
= v1
*256*256*256+v2
*256*256+v3
*256+v4
;
94 node
->DeleteProperty(wxT("version"));
95 node
->AddProperty(wxT("version"), wxT(WX_XMLRES_CURRENT_VERSION_STRING
));
99 class EditorTreeCtrl
: public wxTreeCtrl
102 EditorTreeCtrl(wxWindow
*parent
, int id
, EditorFrame
*frame
)
103 : wxTreeCtrl(parent
, id
), m_EdFrame(frame
) {}
106 EditorFrame
*m_EdFrame
;
108 void OnRightClick(wxMouseEvent
&event
)
111 m_EdFrame
->m_TreeCtrl
->HitTest(event
.GetPosition());
114 m_EdFrame
->m_TreeCtrl
->SelectItem(item
);
115 m_EdFrame
->OnRightClickTree(event
.GetPosition());
118 DECLARE_EVENT_TABLE()
121 BEGIN_EVENT_TABLE(EditorTreeCtrl
, wxTreeCtrl
)
122 EVT_RIGHT_DOWN(EditorTreeCtrl::OnRightClick
)
128 ID_PREVIEW
= wxID_HIGHEST
+ 100,
148 ID_NEWNODE
= wxID_HIGHEST
+ 10000, // safely out of XMLID range :)
149 ID_NEWSYBNODE
= ID_NEWNODE
+ 20000
156 BEGIN_EVENT_TABLE(EditorFrame
, wxFrame
)
157 EVT_TREE_SEL_CHANGED(ID_TREE
, EditorFrame::OnTreeSel
)
158 EVT_TOOL_RANGE(ID_PREVIEW
, ID_EXIT
, EditorFrame::OnToolbar
)
159 EVT_MENU_RANGE(ID_NEWDIALOG
, ID_NEWSYBNODE
+ 1000, EditorFrame::OnNewNode
)
160 EVT_MENU_RANGE(ID_CUT
, ID_COPY
, EditorFrame::OnClipboardAction
)
161 EVT_CLOSE(EditorFrame::OnCloseWindow
)
166 #if defined(__UNIX__)
167 #include "bitmaps/preview.xpm"
168 #include "bitmaps/close.xpm"
169 #include "bitmaps/save.xpm"
170 #include "bitmaps/open.xpm"
172 #include "bitmaps/control.xpm"
173 #include "bitmaps/vsizer.xpm"
174 #include "bitmaps/hsizer.xpm"
175 #include "bitmaps/panel.xpm"
176 #include "bitmaps/gsizer.xpm"
177 #include "bitmaps/resicon.xpm"
182 EditorFrame
*EditorFrame::ms_Instance
= NULL
;
184 EditorFrame::EditorFrame(wxFrame
*parent
, const wxString
& filename
)
185 : wxFrame(parent
, -1, filename
+ _("- wxWindows resources editor"))
192 wxConfigBase
*cfg
= wxConfigBase::Get();
194 SetSize(wxRect(wxPoint(cfg
->Read("editor_x", -1), cfg
->Read("editor_y", -1)),
195 wxSize(cfg
->Read("editor_w", 400), cfg
->Read("editor_h", 400))));
197 m_SelectedNode
= NULL
;
199 m_FileName
= wxEmptyString
;
201 wxMenu
*menuFile
= new wxMenu
;
202 menuFile
->Append(ID_NEW
, "&New");
203 menuFile
->Append(ID_OPEN
, "&Open\tCtrl-O");
204 menuFile
->Append(ID_SAVE
, "&Save\tCtrl-S");
205 menuFile
->Append(ID_SAVEAS
, "Save &as...");
206 menuFile
->AppendSeparator();
207 menuFile
->Append(ID_EXIT
, "E&xit\tAlt-X");
209 wxMenu
*menuEdit
= new wxMenu
;
210 menuEdit
->Append(ID_CUT
, "Cut\tCtrl-X");
211 menuEdit
->Append(ID_COPY
, "Copy\tCtrl-C");
212 menuEdit
->Append(ID_PASTE_SYBLING
, "Paste as sybling\tCtrl-V");
213 menuEdit
->Append(ID_PASTE_CHILD
, "Paste as child");
214 menuEdit
->AppendSeparator();
215 menuEdit
->Append(ID_DELETE_NODE
, "Delete");
217 menuEdit
->Enable(ID_PASTE_SYBLING
, FALSE
);
218 menuEdit
->Enable(ID_PASTE_CHILD
, FALSE
);
220 wxMenuBar
*menuBar
= new wxMenuBar();
221 menuBar
->Append(menuFile
, "&File");
222 menuBar
->Append(menuEdit
, "&Edit");
226 wxToolBar
*toolBar
= CreateToolBar(wxNO_BORDER
| wxTB_HORIZONTAL
| wxTB_FLAT
);
227 toolBar
->SetMargins(2, 2);
228 toolBar
->SetToolBitmapSize(wxSize(24, 24));
229 toolBar
-> AddTool(ID_EXIT
, wxBITMAP(close
), wxNullBitmap
,
230 FALSE
, -1, -1, (wxObject
*) NULL
,
231 _("Quit the editor"));
232 toolBar
-> AddTool(ID_OPEN
, wxBITMAP(open
), wxNullBitmap
,
233 FALSE
, -1, -1, (wxObject
*) NULL
,
234 _("Open XML resource file"));
235 toolBar
-> AddTool(ID_SAVE
, wxBITMAP(save
), wxNullBitmap
,
236 FALSE
, -1, -1, (wxObject
*) NULL
,
238 toolBar
-> AddTool(ID_PREVIEW
, wxBITMAP(preview
), wxNullBitmap
,
239 FALSE
, -1, -1, (wxObject
*) NULL
,
241 toolBar
-> Realize();
243 wxSizer
*sizer
= new wxBoxSizer(wxHORIZONTAL
);
245 // Create tree control:
246 m_TreeCtrl
= new EditorTreeCtrl(this, ID_TREE
, this);
247 wxImageList
*imgList
= new wxImageList(16, 16);
248 imgList
->Add(wxICON(control
));
249 imgList
->Add(wxICON(panel
));
250 imgList
->Add(wxICON(vsizer
));
251 imgList
->Add(wxICON(hsizer
));
252 imgList
->Add(wxICON(gsizer
));
253 imgList
->Add(wxICON(resicon
));
254 m_TreeCtrl
->AssignImageList(imgList
);
255 sizer
->Add(m_TreeCtrl
, 1, wxEXPAND
);
269 EditorFrame::~EditorFrame()
271 PreviewFrame::Get()->Close();
272 PropertiesFrame::Get()->Close();
274 wxConfigBase
*cfg
= wxConfigBase::Get();
276 cfg
->Write(_T("editor_x"), (long)GetPosition().x
);
277 cfg
->Write(_T("editor_y"), (long)GetPosition().y
);
278 cfg
->Write(_T("editor_w"), (long)GetSize().x
);
279 cfg
->Write(_T("editor_h"), (long)GetSize().y
);
287 void EditorFrame::LoadFile(const wxString
& filename
)
289 if (!AskToSave()) return;
293 // create new resource in order to handle version differences properly
294 PreviewFrame::Get()->ResetResource();
297 m_Resource
= new wxXmlRcEditDocument
;
300 if (!m_Resource
->Load(filename
))
305 wxLogError("Error parsing " + filename
);
309 m_FileName
= filename
;
311 // Upgrades old versions
312 m_Resource
->Upgrade();
320 void EditorFrame::SaveFile(const wxString
& filename
)
322 m_FileName
= filename
;
325 if (!m_Resource
->Save(filename
))
326 wxLogError(_("Error saving ") + filename
);
335 void EditorFrame::NewFile()
337 if (!AskToSave()) return;
342 m_Resource
= new wxXmlRcEditDocument
;
343 m_Resource
->SetRoot(new wxXmlNode(wxXML_ELEMENT_NODE
, _("resource")));
352 void EditorFrame::RefreshTitle()
355 if (m_Modified
) s
<< _T("* ");
357 if (m_FileName
!= "")
358 s
<< _T(" - ") << wxFileNameFromPath(m_FileName
);
364 void EditorFrame::RefreshTree()
366 wxXmlNode
*sel
= m_SelectedNode
;
368 m_TreeCtrl
->DeleteAllItems();
369 wxTreeItemId root
= m_TreeCtrl
->AddRoot("Resource: " + wxFileNameFromPath(m_FileName
), 5, 5);
371 wxXmlNode
*n
= m_Resource
->GetRoot()->GetChildren();
374 if (n
->GetType() == wxXML_ELEMENT_NODE
)
375 CreateTreeNode(m_TreeCtrl
, root
, n
);
379 m_TreeCtrl
->Expand(root
);
386 static void RecursivelyExpand(wxTreeCtrl
*t
, wxTreeItemId item
)
390 wxTreeItemId id
= t
->GetFirstChild(item
, cookie
);
393 RecursivelyExpand(t
, id
);
394 id
= t
->GetNextChild(item
, cookie
);
398 bool EditorFrame::SelectNode(wxXmlNode
*node
, wxTreeItemId
*root
)
402 wxTreeItemId rootitem
= m_TreeCtrl
->GetRootItem();
403 return SelectNode(node
, &rootitem
);
411 item
= m_TreeCtrl
->GetFirstChild(*root
, cookie
);
414 dt
= (XmlTreeData
*)(m_TreeCtrl
->GetItemData(item
));
415 nd
= (dt
) ? dt
->Node
: NULL
;
418 RecursivelyExpand(m_TreeCtrl
, *root
);
419 m_TreeCtrl
->SelectItem(item
);
420 m_TreeCtrl
->EnsureVisible(item
);
423 if (m_TreeCtrl
->ItemHasChildren(item
) && SelectNode(node
, &item
))
425 item
= m_TreeCtrl
->GetNextChild(*root
, cookie
);
433 wxTreeItemId
EditorFrame::CreateTreeNode(wxTreeCtrl
*treectrl
, wxTreeItemId parent
, wxXmlNode
*node
)
437 wxTreeItemId invalid
;
441 return NodeHandler::Find(node
)->CreateTreeNode(treectrl
, parent
, node
);
446 void EditorFrame::NotifyChanged(int change_type
)
448 if (change_type
& CHANGED_TREE
)
451 if (change_type
& CHANGED_TREE_SELECTED
)
453 wxTreeItemId sel
= m_TreeCtrl
->GetSelection();
454 m_TreeCtrl
->SetItemText(sel
,
455 NodeHandler::Find(m_SelectedNode
)->GetTreeString(m_SelectedNode
));
458 if (change_type
& CHANGED_TREE_SELECTED_ICON
)
460 wxTreeItemId sel
= m_TreeCtrl
->GetSelection();
461 int icon
= NodeHandler::Find(m_SelectedNode
)->GetTreeIcon(m_SelectedNode
);
462 m_TreeCtrl
->SetItemImage(sel
, icon
);
471 PreviewFrame::Get()->MakeDirty();
476 void EditorFrame::OnTreeSel(wxTreeEvent
& event
)
478 XmlTreeData
*dt
= (XmlTreeData
*)(m_TreeCtrl
->GetItemData(event
.GetItem()));
479 wxXmlNode
*node
= (dt
) ? dt
->Node
: NULL
;
481 m_SelectedNode
= node
;
483 PropertiesFrame::Get()->ShowProps(node
);
485 if (m_TreeCtrl
->GetParent(event
.GetItem()) == m_TreeCtrl
->GetRootItem())
487 wxTreeItemId it
= event
.GetOldItem();
489 if (it
.IsOk() && m_TreeCtrl
->GetRootItem() != it
)
491 while (m_TreeCtrl
->GetParent(it
) != m_TreeCtrl
->GetRootItem())
492 it
= m_TreeCtrl
->GetParent(it
);
493 m_TreeCtrl
->Collapse(it
);
495 RecursivelyExpand(m_TreeCtrl
, event
.GetItem());
497 PreviewFrame::Get()->Preview(node
,m_Resource
->GetRoot()->GetPropVal(
498 wxT("version"), wxT("0.0.0.0")));
504 void EditorFrame::OnToolbar(wxCommandEvent
& event
)
506 switch (event
.GetId())
510 XmlTreeData
* dt
= (XmlTreeData
*)m_TreeCtrl
->GetItemData(m_TreeCtrl
->GetSelection());;
511 if (dt
!= NULL
&& dt
->Node
!= NULL
)
512 PreviewFrame::Get()->Preview(dt
->Node
,m_Resource
->GetRoot()->GetPropVal(
513 wxT("version"), wxT("0.0.0.0")));
527 wxString cwd
= wxGetCwd(); // workaround for 2.2
528 wxString name
= wxFileSelector(_("Open XML resource"), _T(""), _T(""), _T(""), _("XML resources (*.xrc)|*.xrc"), wxOPEN
| wxFILE_MUST_EXIST
);
529 wxSetWorkingDirectory(cwd
);
536 if (m_FileName
!= "") { SaveFile(m_FileName
); break;}
541 wxString cwd
= wxGetCwd(); // workaround for 2.2
542 wxString name
= wxFileSelector(_("Save as"), _T(""), m_FileName
, _T(""), _("XML resources (*.xrc)|*.xrc"), wxSAVE
| wxOVERWRITE_PROMPT
);
543 wxSetWorkingDirectory(cwd
);
545 SaveFile((m_FileName
= name
));
549 case ID_DELETE_NODE
:
551 DeleteSelectedNode();
559 void EditorFrame::DeleteSelectedNode()
561 XmlTreeData
*dt
= (XmlTreeData
*)
562 (m_TreeCtrl
->GetItemData(m_TreeCtrl
->GetParent(m_TreeCtrl
->GetSelection())));
563 wxXmlNode
*n
= (dt
) ? dt
->Node
: NULL
;
565 m_SelectedNode
->GetParent()->RemoveChild(m_SelectedNode
);
566 NotifyChanged(CHANGED_TREE
);
572 void EditorFrame::OnNewNode(wxCommandEvent
& event
)
574 if (event
.GetId() >= ID_NEWSYBNODE
)
577 (XmlTreeData
*)(m_TreeCtrl
->GetItemData(
578 m_TreeCtrl
->GetParent(m_TreeCtrl
->GetSelection())));
580 if (pardt
&& pardt
->Node
&& pardt
->Node
!= m_Resource
->GetRoot())
582 wxXmlNode
*nd
= pardt
->Node
;
584 wxXmlNode
*realnode
= NodeHandler::Find(nd
)->GetRealNode(nd
);
585 NodeHandler
*hnd
= NodeHandler::Find(realnode
);
586 wxString name
= hnd
->GetChildTypes()[event
.GetId()-ID_NEWSYBNODE
];
588 wxXmlNode
*node
= new wxXmlNode(wxXML_ELEMENT_NODE
, _T("object"));
589 node
->AddProperty(_T("class"), name
);
591 hnd
->InsertNode(realnode
, node
, m_SelectedNode
);
592 wxTreeItemId root
= m_TreeCtrl
->GetSelection();
593 SelectNode(node
, &root
);
598 else if (event
.GetId() >= ID_NEWNODE
)
600 wxXmlNode
*realnode
= NodeHandler::Find(m_SelectedNode
)->GetRealNode(m_SelectedNode
);
601 NodeHandler
*hnd
= NodeHandler::Find(realnode
);
602 wxString name
= hnd
->GetChildTypes()[event
.GetId()-ID_NEWNODE
];
604 wxXmlNode
*node
= new wxXmlNode(wxXML_ELEMENT_NODE
, _T("object"));
605 node
->AddProperty(_T("class"), name
);
607 hnd
->InsertNode(realnode
, node
);
608 wxTreeItemId root
= m_TreeCtrl
->GetSelection();
609 SelectNode(node
, &root
);
615 switch (event
.GetId())
617 case ID_NEWDIALOG
: name
= _T("wxDialog"); break;
618 case ID_NEWPANEL
: name
= _T("wxPanel"); break;
619 case ID_NEWMENU
: name
= _T("wxMenu"); break;
620 case ID_NEWMENUBAR
: name
= _T("wxMenuBar"); break;
621 case ID_NEWTOOLBAR
: name
= _T("wxToolBar"); break;
622 default : return; // never occurs
625 wxXmlNode
*node
= new wxXmlNode(wxXML_ELEMENT_NODE
, _T("object"));
626 node
->AddProperty(_T("class"), name
);
627 m_Resource
->GetRoot()->AddChild(node
);
628 NotifyChanged(CHANGED_TREE
);
635 void EditorFrame::OnRightClickTree(wxPoint pos
)
637 wxMenu
*popup
= new wxMenu
;
639 if (m_SelectedNode
== NULL
|| m_SelectedNode
== m_Resource
->GetRoot())
641 popup
->Append(ID_NEWDIALOG
, _("New wxDialog"));
642 popup
->Append(ID_NEWPANEL
, _("New wxPanel"));
643 popup
->Append(ID_NEWMENU
, _("New wxMenu"));
644 popup
->Append(ID_NEWMENUBAR
, _("New wxMenuBar"));
645 popup
->Append(ID_NEWTOOLBAR
, _("New wxToolBar"));
653 NodeHandler::Find(NodeHandler::Find(m_SelectedNode
)->GetRealNode(m_SelectedNode
))->
656 has_children
= !arr
.IsEmpty();
659 wxMenu
*news
= new wxMenu
;
660 wxMenu
*news2
= news
;
661 for (size_t i
= 0; i
< arr
.GetCount(); i
++)
663 news2
->Append(i
+ ID_NEWNODE
, arr
[i
]);
664 #ifdef __WXGTK__ // doesn't support Break
667 wxMenu
*m
= new wxMenu
;
668 news2
->Append(ID_NEWNODE
+arr
.GetCount(), _("More..."), m
);
672 if (i
% 16 == 15) news2
->Break();
675 popup
->Append(ID_NEWNODE
-1, _("New child"), news
);
681 (XmlTreeData
*)(m_TreeCtrl
->GetItemData(
682 m_TreeCtrl
->GetParent(m_TreeCtrl
->GetSelection())));
683 if (pardt
&& pardt
->Node
&& pardt
->Node
!= m_Resource
->GetRoot())
685 wxXmlNode
*nd
= pardt
->Node
;
687 NodeHandler::Find(NodeHandler::Find(nd
)->GetRealNode(nd
))->
692 wxMenu
*news
= new wxMenu
;
693 wxMenu
*news2
= news
;
694 for (size_t i
= 0; i
< arr
.GetCount(); i
++)
696 news2
->Append(i
+ ID_NEWSYBNODE
, arr
[i
]);
697 #ifdef __WXGTK__ // doesn't support Break
700 wxMenu
*m
= new wxMenu
;
701 news2
->Append(ID_NEWSYBNODE
+arr
.GetCount(), _("More..."), m
);
705 if (i
% 16 == 15) news2
->Break();
708 popup
->Append(ID_NEWSYBNODE
-1, _("New sybling"), news
);
713 popup
->AppendSeparator();
714 popup
->Append(ID_CUT
, _("Cut"));
715 popup
->Append(ID_COPY
, _("Copy"));
716 popup
->Append(ID_PASTE_SYBLING
, _("Paste as sybling"));
717 popup
->Append(ID_PASTE_CHILD
, _("Paste as child"));
718 popup
->AppendSeparator();
719 popup
->Append(ID_DELETE_NODE
, _("Delete"));
720 popup
->Enable(ID_PASTE_SYBLING
, m_Clipboard
!= NULL
);
721 popup
->Enable(ID_PASTE_CHILD
, has_children
&& m_Clipboard
!= NULL
);
724 m_TreeCtrl
->PopupMenu(popup
, pos
);
730 void EditorFrame::OnClipboardAction(wxCommandEvent
& event
)
732 switch (event
.GetId())
737 m_Clipboard
= new wxXmlNode(*m_SelectedNode
);
738 GetMenuBar()->Enable(ID_PASTE_SYBLING
, TRUE
);
739 GetMenuBar()->Enable(ID_PASTE_CHILD
, TRUE
);
740 if (event
.GetId() == ID_CUT
) DeleteSelectedNode();
743 case ID_PASTE_SYBLING
:
746 (XmlTreeData
*)(m_TreeCtrl
->GetItemData(
747 m_TreeCtrl
->GetParent(m_TreeCtrl
->GetSelection())));
749 if (pardt
&& pardt
->Node
&& pardt
->Node
!= m_Resource
->GetRoot())
751 wxXmlNode
*nd
= pardt
->Node
;
753 wxXmlNode
*realnode
= NodeHandler::Find(nd
)->GetRealNode(nd
);
754 NodeHandler
*hnd
= NodeHandler::Find(realnode
);
755 wxXmlNode
*node
= new wxXmlNode(*m_Clipboard
);
756 hnd
->InsertNode(realnode
, node
, m_SelectedNode
);
757 wxTreeItemId root
= m_TreeCtrl
->GetSelection();
758 SelectNode(node
, &root
);
764 wxXmlNode
*realnode
= NodeHandler::Find(m_SelectedNode
)->GetRealNode(m_SelectedNode
);
765 NodeHandler
*hnd
= NodeHandler::Find(realnode
);
766 wxXmlNode
*node
= new wxXmlNode(*m_Clipboard
);
767 hnd
->InsertNode(realnode
, node
);
768 wxTreeItemId root
= m_TreeCtrl
->GetSelection();
769 SelectNode(node
, &root
);
777 bool EditorFrame::AskToSave()
778 // asks the user to save current document (if modified)
779 // returns FALSE if user cancelled the action, TRUE of he choosed
782 if (!m_Modified
) return TRUE
;
784 int res
= wxMessageBox(_("File modified. Do you want to save changes?"), _("Save changes"),
785 wxYES_NO
| wxCANCEL
| wxCENTRE
| wxICON_QUESTION
);
787 SaveFile(m_FileName
);
788 return (res
!= wxCANCEL
);
793 void EditorFrame::OnCloseWindow(wxCloseEvent
&)
795 if (!AskToSave()) return;