DockLayout.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. /*
  2. * $Id$
  3. *
  4. * Copyright (C) 2004 Todd Berman <tberman@off.net>
  5. * Copyright (C) 2004 Jeroen Zwartepoorte <jeroen@xs4all.nl>
  6. * Copyright (C) 2005 John Luke <john.luke@gmail.com>
  7. *
  8. * based on work by:
  9. * Copyright (C) 2002 Gustavo Giráldez <gustavo.giraldez@gmx.net>
  10. *
  11. * This program is free software; you can redistribute it and/or modify
  12. * it under the terms of the GNU General Public License as published by
  13. * the Free Software Foundation; either version 2 of the License, or
  14. * (at your option) any later version.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU General Public License
  22. * along with this program; if not, write to the Free Software
  23. * Foundation, Inc., 59 Temple Place - Suite 330,
  24. * Boston, MA 02111-1307, USA.
  25. */
  26. using System;
  27. using System.Collections;
  28. using System.IO;
  29. using System.Xml;
  30. using Gtk;
  31. namespace Gdl
  32. {
  33. public class DockLayout
  34. {
  35. XmlDocument doc;
  36. XmlNode rootNode;
  37. ListStore itemsModel;
  38. ListStore layoutsModel;
  39. bool dirty = false;
  40. bool idleSavePending = false;
  41. Widget itemsUI, layoutsUI;
  42. DockMaster master = null;
  43. ArrayList layouts;
  44. protected Hashtable placeholders;
  45. bool loading;
  46. CheckButton locked_check;
  47. private const int NAME_COL = 0;
  48. private const int SHOW_COL = 1;
  49. private const int LOCK_COL = 2;
  50. private const int ITEM_COL = 3;
  51. public DockLayout (Dock dock)
  52. {
  53. layouts = new ArrayList ();
  54. this.Attach (dock.Master);
  55. BuildModels ();
  56. }
  57. public Widget ItemsUI {
  58. get {
  59. if (itemsUI == null)
  60. itemsUI = ConstructItemsUI ();
  61. return itemsUI;
  62. }
  63. }
  64. public ArrayList Layouts {
  65. get { return layouts; }
  66. }
  67. public Widget LayoutsUI {
  68. get {
  69. if (layoutsUI == null)
  70. layoutsUI = ConstructLayoutsUI ();
  71. return layoutsUI;
  72. }
  73. }
  74. public DockMaster Master {
  75. get { return master; }
  76. set { master = value; }
  77. }
  78. private XmlNode RootNode {
  79. get {
  80. if (rootNode == null && doc != null)
  81. rootNode = doc.SelectSingleNode ("/dock-layout");
  82. return rootNode;
  83. }
  84. }
  85. public Widget UI {
  86. get { return ConstructUI ();}
  87. }
  88. // true if the layouts have changed and need to be saved to a file
  89. public bool IsDirty {
  90. get { return dirty; }
  91. }
  92. public void Attach (DockMaster master)
  93. {
  94. if (master == null)
  95. return;
  96. master.LayoutChanged -= new EventHandler (OnLayoutChanged);
  97. if (itemsModel != null)
  98. itemsModel.Clear ();
  99. this.master = master;
  100. master.LayoutChanged += new EventHandler (OnLayoutChanged);
  101. UpdateItemsModel ();
  102. }
  103. public void DeleteLayout (string name)
  104. {
  105. // dont allow deletion of default layout
  106. if (name == null || name == "__default__")
  107. return;
  108. XmlNode node = FindLayout (name);
  109. if (node != null) {
  110. this.RootNode.RemoveChild (node);
  111. dirty = true;
  112. // notify dirty
  113. }
  114. }
  115. public void Dump ()
  116. {
  117. XmlTextWriter writer = new XmlTextWriter (Console.Out);
  118. writer.Formatting = Formatting.Indented;
  119. doc.WriteTo (writer);
  120. }
  121. public string[] GetLayouts (bool includeDefault)
  122. {
  123. return Layouts.ToArray (typeof (string)) as string[];
  124. }
  125. public bool LoadFromFile (string file)
  126. {
  127. if (doc != null) {
  128. doc = null;
  129. dirty = false;
  130. // notify dirty
  131. }
  132. if (File.Exists (file))
  133. {
  134. doc = new XmlDocument ();
  135. Stream s = File.OpenRead (file);
  136. try {
  137. doc.Load (s);
  138. } catch {
  139. Console.WriteLine ("WARNING: could not load dock layout XML");
  140. // FIXME: remove corrupt file?
  141. doc = null;
  142. }
  143. s.Close ();
  144. // minimum validation: test root element
  145. if (this.RootNode != null) {
  146. loading = true;
  147. foreach (XmlNode n in this.RootNode.ChildNodes)
  148. {
  149. if (n.Name == "layout")
  150. layouts.Add (n.Attributes["name"].Value);
  151. }
  152. UpdateLayoutsModel ();
  153. loading = false;
  154. return true;
  155. }
  156. else {
  157. doc = null;
  158. }
  159. }
  160. return false;
  161. }
  162. public bool LoadLayout (string name)
  163. {
  164. if (doc == null || master == null)
  165. return false;
  166. if (name == null || name.Length < 1)
  167. name = "__default__";
  168. XmlNode node = FindLayout (name);
  169. if (node == null)
  170. node = FindLayout (null);
  171. if (node == null)
  172. return false;
  173. try {
  174. loading = true;
  175. Load (node);
  176. } finally {
  177. loading = true;
  178. }
  179. return true;
  180. }
  181. public void RunManager ()
  182. {
  183. if (master == null)
  184. return;
  185. Widget container = ConstructUI ();
  186. if (container == null)
  187. return;
  188. Widget parent = master.Controller;
  189. if (parent != null)
  190. parent = parent.Toplevel;
  191. Dialog dialog = new Dialog ();
  192. dialog.Title = "Layout management";
  193. dialog.TransientFor = parent as Window;
  194. dialog.AddButton (Gtk.Stock.Close, Gtk.ResponseType.Close);
  195. dialog.SetDefaultSize (-1, 300);
  196. dialog.VBox.Add (container);
  197. dialog.Run ();
  198. dialog.Destroy ();
  199. }
  200. public void SaveLayout (string name)
  201. {
  202. if (master == null)
  203. return;
  204. if (doc == null)
  205. BuildDoc ();
  206. if (name == null || name.Length < 1)
  207. name = "__default__";
  208. // delete any previous node with the same name
  209. XmlNode node = FindLayout (name);
  210. if (node != null)
  211. this.RootNode.RemoveChild (node);
  212. // create the new node
  213. XmlElement element = doc.CreateElement ("layout");
  214. element.SetAttribute ("name", name);
  215. this.RootNode.AppendChild (element);
  216. // save the layout
  217. Save (element);
  218. dirty = true;
  219. // notify dirty
  220. }
  221. public bool SaveToFile (string file)
  222. {
  223. if (file == null)
  224. return false;
  225. // if there is still no xml doc, create an empty one
  226. if (doc == null)
  227. BuildDoc ();
  228. XmlTextWriter writer = new XmlTextWriter (file, System.Text.Encoding.UTF8);
  229. writer.Formatting = Formatting.Indented;
  230. doc.WriteTo (writer);
  231. writer.Flush ();
  232. writer.Close ();
  233. dirty = false;
  234. // notify dirty
  235. return true;
  236. }
  237. void BuildModels ()
  238. {
  239. // NAME, SHOW, LOCKED, ITEM
  240. itemsModel = new ListStore (typeof (string), typeof (bool), typeof (bool), typeof (DockItem));
  241. itemsModel.SetSortColumnId (NAME_COL, SortType.Ascending);
  242. layoutsModel = new ListStore (typeof (string), typeof (bool));
  243. layoutsModel.SetSortColumnId (NAME_COL, SortType.Ascending);
  244. }
  245. void BuildDoc ()
  246. {
  247. doc = new XmlDocument ();
  248. doc.CreateXmlDeclaration ("1.0", null, null);
  249. doc.AppendChild (doc.CreateElement ("dock-layout"));
  250. }
  251. XmlNode FindLayout (string name)
  252. {
  253. if (doc == null)
  254. return null;
  255. foreach (XmlNode n in doc.SelectNodes ("/dock-layout/layout"))
  256. {
  257. if (n.Attributes["name"].Value == name)
  258. return n;
  259. }
  260. return null;
  261. }
  262. void UpdateItemsModel ()
  263. {
  264. if (itemsModel == null || master == null)
  265. return;
  266. // build items list
  267. ArrayList items = new ArrayList ();
  268. foreach (object o in master.DockObjects) {
  269. if (o is DockItem)
  270. items.Add (o);
  271. }
  272. TreeIter iter;
  273. // update items model data after a layout load
  274. if (itemsModel.GetIterFirst (out iter)) {
  275. bool valid = true;
  276. walk_start:
  277. while (valid) {
  278. DockItem item = itemsModel.GetValue (iter, 3) as DockItem;
  279. if (item != null) {
  280. // look for the object in the items list
  281. for (int i = 0; i < items.Count; i ++)
  282. {
  283. // found, update data
  284. if (item == items[i]) {
  285. UpdateItemData (iter, item);
  286. items.RemoveAt (i);
  287. valid = itemsModel.IterNext (ref iter);
  288. goto walk_start;
  289. }
  290. }
  291. // FIXME: not found, skip it?
  292. valid = itemsModel.IterNext (ref iter);
  293. }
  294. else {
  295. // not a valid row
  296. valid = itemsModel.Remove (ref iter);
  297. }
  298. }
  299. }
  300. // add any remaining objects
  301. foreach (DockItem ditem in items)
  302. itemsModel.AppendValues (ditem.Name, ditem.IsAttached, ditem.Locked, ditem);
  303. }
  304. void UpdateItemData (TreeIter iter, DockItem item)
  305. {
  306. itemsModel.SetValue (iter, NAME_COL, item.Name);
  307. itemsModel.SetValue (iter, SHOW_COL, item.IsAttached);
  308. itemsModel.SetValue (iter, LOCK_COL, item.Locked);
  309. }
  310. void UpdateLayoutsModel ()
  311. {
  312. if (master == null || layoutsModel == null)
  313. return;
  314. // build layouts list
  315. layoutsModel.Clear ();
  316. foreach (string s in this.Layouts) {
  317. if (s != "__default__")
  318. layoutsModel.AppendValues (s, true);
  319. }
  320. }
  321. Notebook ConstructUI ()
  322. {
  323. Notebook notebook = new Notebook ();
  324. notebook.Show ();
  325. Widget child;
  326. child = ConstructItemsUI ();
  327. if (child != null)
  328. notebook.AppendPage (child, new Label ("Items"));
  329. child = ConstructLayoutsUI ();
  330. if (child != null)
  331. notebook.AppendPage (child, new Label ("Layouts"));
  332. notebook.CurrentPage = 0;
  333. return notebook;
  334. }
  335. Widget ConstructItemsUI ()
  336. {
  337. Glade.XML gui = LoadInterface ("items_vbox");
  338. if (gui == null)
  339. return null;
  340. Gtk.VBox container = gui.GetWidget ("items_vbox") as VBox;
  341. locked_check = gui.GetWidget ("locked_check") as CheckButton;
  342. Gtk.TreeView items_list = gui.GetWidget ("items_list") as TreeView;
  343. locked_check.Toggled += new EventHandler (AllLockedToggledCb);
  344. if (master != null) {
  345. master.NotifyLocked += new EventHandler (MasterLockedNotifyCb);
  346. // force update now
  347. MasterLockedNotifyCb (master, EventArgs.Empty);
  348. }
  349. // set models
  350. items_list.Model = itemsModel;
  351. // construct list views
  352. CellRendererToggle renderer = new CellRendererToggle ();
  353. renderer.Toggled += new ToggledHandler (ShowToggledCb);
  354. TreeViewColumn column = new TreeViewColumn ("Visible", renderer, "active", 1);
  355. items_list.AppendColumn (column);
  356. items_list.AppendColumn ("Item", new CellRendererText (), "text", 0);
  357. return container;
  358. }
  359. Widget ConstructLayoutsUI ()
  360. {
  361. Glade.XML gui = LoadInterface ("layouts_vbox");
  362. if (gui == null)
  363. return null;
  364. Gtk.VBox container = gui.GetWidget ("layouts_vbox") as VBox;
  365. Gtk.TreeView layouts_list = gui.GetWidget ("layouts_list") as TreeView;
  366. layouts_list.Model = layoutsModel;
  367. CellRendererText renderer = new CellRendererText ();
  368. renderer.Edited += new EditedHandler (CellEditedCb);
  369. TreeViewColumn column = new TreeViewColumn ("Name", renderer, "text", 0, "editable", 1);
  370. layouts_list.AppendColumn (column);
  371. return container;
  372. }
  373. Glade.XML LoadInterface (string topWidget)
  374. {
  375. return new Glade.XML (null, "layout.glade", topWidget, null);
  376. }
  377. DockObject SetupObject (XmlNode node)
  378. {
  379. DockObject obj = null;
  380. if (node.Name == "notebook") {
  381. DockNotebook dn = new DockNotebook ();
  382. dn.Bind (master);
  383. dn.FromXml (node);
  384. return dn;
  385. }
  386. if (node.Name == "paned") {
  387. DockPaned dp = new DockPaned ();
  388. dp.Bind (master);
  389. dp.FromXml (node);
  390. return dp;
  391. }
  392. string name = null;
  393. if (node.Attributes["name"] != null)
  394. name = node.Attributes["name"].Value;
  395. if (name != null && name.Length > 0) {
  396. obj = master.GetObject (name);
  397. }
  398. else {
  399. Console.WriteLine ("While loading layout: don't know how to create a dock object whose nick is '{0}'", name);
  400. }
  401. if (obj != null)
  402. obj.FromXml (node);
  403. return obj;
  404. }
  405. void RecursiveBuild (XmlNode parentNode, DockObject parent)
  406. {
  407. if (master == null || parentNode == null)
  408. return;
  409. DockObject obj;
  410. // FIXME: if parent is null, we should build toplevels
  411. //if (parent == null)
  412. foreach (XmlNode node in parentNode.ChildNodes)
  413. {
  414. obj = SetupObject (node);
  415. if (obj != null) {
  416. obj.Freeze ();
  417. // recurse here to catch placeholders
  418. RecursiveBuild (node, obj);
  419. // placeholders are later attached to the parent
  420. if (obj is DockPlaceholder)
  421. obj.Detach (false);
  422. // apply "after" parameters
  423. obj.FromXmlAfter (node);
  424. // add the object to the parent
  425. if (parent != null) {
  426. if (obj is DockPlaceholder) {
  427. ((DockPlaceholder) obj).Attach (parent);
  428. }
  429. else if (parent.IsCompound) {
  430. parent.Add (obj);
  431. if (parent.Visible)
  432. obj.Show ();
  433. }
  434. }
  435. else {
  436. if (master.Controller != obj && master.Controller.Visible)
  437. obj.Show ();
  438. }
  439. // call reduce just in case child is missing
  440. if (obj.IsCompound)
  441. obj.Reduce ();
  442. obj.Thaw ();
  443. }
  444. }
  445. }
  446. void ForeachDetach (DockObject obj)
  447. {
  448. obj.Detach (true);
  449. }
  450. void ForeachToplevelDetach (DockObject obj)
  451. {
  452. DockObject child;
  453. foreach (Widget w in obj.Children) {
  454. child = w as DockObject;
  455. if (w != null)
  456. ForeachDetach (child);
  457. }
  458. }
  459. void Load (XmlNode node)
  460. {
  461. if (node == null)
  462. return;
  463. // start by detaching all items from the toplevels
  464. foreach (DockObject o in master.TopLevelDocks)
  465. ForeachToplevelDetach (o);
  466. RecursiveBuild (node, null);
  467. }
  468. void ForeachObjectSave (DockObject obj, XmlNode parent)
  469. {
  470. if (obj == null)
  471. return;
  472. XmlElement element = obj.ToXml (doc);
  473. parent.AppendChild (element);
  474. // FIXME: save placeholders for the object
  475. if (!(obj is DockPlaceholder)) {
  476. //ArrayList list = placeholders[obj] as ArrayList;
  477. //foreach (DockObject child in list)
  478. // ForeachObjectSave (child, element);
  479. }
  480. // recurse the object if appropriate
  481. if (obj.IsCompound) {
  482. DockObject child;
  483. foreach (Widget w in obj.Children)
  484. {
  485. child = w as DockObject;
  486. if (child != null)
  487. ForeachObjectSave (child, element);
  488. }
  489. }
  490. }
  491. void AddPlaceholder (DockObject obj)
  492. {
  493. if (obj is DockPlaceholder) {
  494. // FIXME: add the current placeholder to the list of placeholders for that host
  495. }
  496. }
  497. void Save (XmlNode node)
  498. {
  499. if (master == null || node == null)
  500. return;
  501. // build the placeholder's hash: the hash keeps lists of
  502. // placeholders associated to each object, so that we can save the
  503. // placeholders when we are saving the object (since placeholders
  504. // don't show up in the normal widget hierarchy)
  505. placeholders = new Hashtable ();
  506. foreach (DockObject obj in master.DockObjects)
  507. AddPlaceholder (obj);
  508. // save the layout recursively
  509. foreach (DockObject o in master.TopLevelDocks)
  510. ForeachObjectSave (o, node);
  511. }
  512. bool IdleSave ()
  513. {
  514. SaveLayout (null);
  515. idleSavePending = false;
  516. return false;
  517. }
  518. void OnLayoutChanged (object sender, EventArgs a)
  519. {
  520. if (loading) return;
  521. UpdateItemsModel ();
  522. if (!idleSavePending) {
  523. GLib.Idle.Add (new GLib.IdleHandler (IdleSave));
  524. idleSavePending = true;
  525. }
  526. }
  527. protected void LoadLayoutCb (object sender, EventArgs a)
  528. {
  529. TreeModel model;
  530. TreeIter iter;
  531. if (((TreeView) sender).Selection.GetSelected (out model, out iter))
  532. LoadLayout ((string) model.GetValue (iter, NAME_COL));
  533. }
  534. protected void DeleteLayoutCb (object sender, EventArgs a)
  535. {
  536. TreeModel model;
  537. TreeIter iter;
  538. if (((TreeView) sender).Selection.GetSelected (out model, out iter)) {
  539. DeleteLayout ((string) model.GetValue (iter, NAME_COL));
  540. ((ListStore)model).Remove (ref iter);
  541. }
  542. }
  543. void ShowToggledCb (object sender, ToggledArgs a)
  544. {
  545. TreeIter iter;
  546. if (itemsModel.GetIterFromString (out iter, a.Path)) {
  547. bool show = (bool) itemsModel.GetValue (iter, SHOW_COL);
  548. DockItem item = itemsModel.GetValue (iter, ITEM_COL) as DockItem;
  549. if (show)
  550. item.HideItem ();
  551. else
  552. item.ShowItem ();
  553. UpdateItemsModel ();
  554. }
  555. }
  556. void AllLockedToggledCb (object sender, EventArgs a)
  557. {
  558. bool locked = ((CheckButton) sender).Active;
  559. if (master != null)
  560. master.Locked = locked ? 1 : 0;
  561. }
  562. void MasterLockedNotifyCb (object sender, EventArgs a)
  563. {
  564. if (master.Locked == -1) {
  565. locked_check.Inconsistent = true;
  566. }
  567. else {
  568. locked_check.Inconsistent = false;
  569. locked_check.Active = (master.Locked == 1);
  570. }
  571. }
  572. void CellEditedCb (object sender, EditedArgs a)
  573. {
  574. TreeIter iter;
  575. layoutsModel.GetIterFromString (out iter, a.Path);
  576. string name = (string) layoutsModel.GetValue (iter, NAME_COL);
  577. XmlNode node = FindLayout (name);
  578. if (node == null)
  579. return;
  580. node.Attributes["name"].Value = a.NewText;
  581. layoutsModel.SetValue (iter, NAME_COL, a.NewText);
  582. layoutsModel.SetValue (iter, SHOW_COL, true);
  583. SaveLayout (a.NewText);
  584. }
  585. }
  586. }