paragraphStylesDialog.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /**
  2. * Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
  3. *
  4. * @licstart
  5. * This file is part of WebODF.
  6. *
  7. * WebODF is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU Affero General Public License (GNU AGPL)
  9. * as published by the Free Software Foundation, either version 3 of
  10. * the License, or (at your option) any later version.
  11. *
  12. * WebODF is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Affero General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public License
  18. * along with WebODF. If not, see <http://www.gnu.org/licenses/>.
  19. * @licend
  20. *
  21. * @source: http://www.webodf.org/
  22. * @source: https://github.com/kogmbh/WebODF/
  23. */
  24. /*global define,require,dojo,dijit */
  25. define("webodf/editor/widgets/paragraphStylesDialog", [], function () {
  26. "use strict";
  27. return function ParagraphStylesDialog(callback) {
  28. var self = this,
  29. editorSession,
  30. dialog,
  31. stylePicker, alignmentPane, fontEffectsPane;
  32. function makeWidget(callback) {
  33. require([
  34. "dijit/Dialog",
  35. "dijit/TooltipDialog",
  36. "dijit/popup",
  37. "dijit/layout/LayoutContainer",
  38. "dijit/layout/TabContainer",
  39. "dijit/layout/ContentPane",
  40. "dijit/form/Button",
  41. "dijit/form/DropDownButton"], function (Dialog, TooltipDialog, popup, LayoutContainer, TabContainer, ContentPane, Button, DropDownButton) {
  42. var i,
  43. tr = runtime.tr,
  44. mainLayoutContainer,
  45. tabContainer,
  46. topBar,
  47. actionBar,
  48. okButton,
  49. cancelButton,
  50. cloneButton,
  51. deleteButton,
  52. cloneTooltip,
  53. cloneDropDown,
  54. /**
  55. * Mapping of the properties from edit pane properties to the attributes of style:text-properties
  56. * @const@type{Array.<!{propertyName:string,attributeName:string,unit:string}>}
  57. */
  58. textPropertyMapping = [
  59. {
  60. propertyName: 'fontSize',
  61. attributeName: 'fo:font-size',
  62. unit: 'pt'
  63. }, {
  64. propertyName: 'fontName',
  65. attributeName: 'style:font-name'
  66. }, {
  67. propertyName: 'color',
  68. attributeName: 'fo:color'
  69. }, {
  70. propertyName: 'backgroundColor',
  71. attributeName: 'fo:background-color'
  72. }, {
  73. propertyName: 'fontWeight',
  74. attributeName: 'fo:font-weight'
  75. }, {
  76. propertyName: 'fontStyle',
  77. attributeName: 'fo:font-style'
  78. }, {
  79. propertyName: 'underline',
  80. attributeName: 'style:text-underline-style'
  81. }, {
  82. propertyName: 'strikethrough',
  83. attributeName: 'style:text-line-through-style'
  84. }],
  85. /**
  86. * Mapping of the properties from edit pane properties to the attributes of style:paragraph-properties
  87. * @const@type{Array.<!{propertyName:string,attributeName:string,unit:string}>}
  88. */
  89. paragraphPropertyMapping = [
  90. {
  91. propertyName: 'topMargin',
  92. attributeName: 'fo:margin-top',
  93. unit: 'mm'
  94. }, {
  95. propertyName: 'bottomMargin',
  96. attributeName: 'fo:margin-bottom',
  97. unit: 'mm'
  98. }, {
  99. propertyName: 'leftMargin',
  100. attributeName: 'fo:margin-left',
  101. unit: 'mm'
  102. }, {
  103. propertyName: 'rightMargin',
  104. attributeName: 'fo:margin-right',
  105. unit: 'mm'
  106. }, {
  107. propertyName: 'textAlign',
  108. attributeName: 'fo:text-align'
  109. }],
  110. originalFontEffectsPaneValue,
  111. originalAlignmentPaneValue;
  112. /**
  113. * Sets attributes of a node by the properties of the object properties,
  114. * based on the mapping defined in propertyMapping.
  115. * @param {!Object} properties
  116. * @param {!Array.<!{propertyName:string,attributeName:string,unit:string}>} propertyMapping
  117. * @return {!Object}
  118. */
  119. function mappedProperties(properties, propertyMapping) {
  120. var i, m, value,
  121. result = {};
  122. for (i = 0; i < propertyMapping.length; i += 1) {
  123. m = propertyMapping[i];
  124. value = properties[m.propertyName];
  125. // Set a value as the attribute of a node, if that value is defined.
  126. // If there is a unit specified, it is suffixed to the value.
  127. if (value !== undefined) {
  128. result[m.attributeName] = (m.unit !== undefined) ? value + m.unit : value;
  129. }
  130. }
  131. return result;
  132. }
  133. /**
  134. * Returns an flat object containing only the key-value mappings
  135. * from the 'new' flat object which are different from the 'old' object's.
  136. * @param {!Object} oldProperties
  137. * @param {!Object} newProperties
  138. * @return {!Object}
  139. */
  140. function updatedProperties(oldProperties, newProperties) {
  141. var properties = {};
  142. Object.keys(newProperties).forEach(function (key) {
  143. if (newProperties[key] !== oldProperties[key]) {
  144. properties[key] = newProperties[key];
  145. }
  146. });
  147. return properties;
  148. }
  149. function accept() {
  150. editorSession.updateParagraphStyle(stylePicker.value(), {
  151. "style:paragraph-properties": mappedProperties(
  152. updatedProperties(originalAlignmentPaneValue, alignmentPane.value()),
  153. paragraphPropertyMapping
  154. ),
  155. "style:text-properties": mappedProperties(
  156. updatedProperties(originalFontEffectsPaneValue, fontEffectsPane.value()),
  157. textPropertyMapping
  158. )
  159. });
  160. dialog.hide();
  161. }
  162. function cancel() {
  163. dialog.hide();
  164. }
  165. function setStyle(value) {
  166. if (value !== stylePicker.value()) {
  167. stylePicker.setValue(value);
  168. }
  169. alignmentPane.setStyle(value);
  170. fontEffectsPane.setStyle(value);
  171. originalAlignmentPaneValue = alignmentPane.value();
  172. originalFontEffectsPaneValue = fontEffectsPane.value();
  173. // If it is a default (nameless) style or is used, make it undeletable.
  174. if (value === "" || editorSession.isStyleUsed(editorSession.getParagraphStyleElement(value))) {
  175. deleteButton.domNode.style.display = 'none';
  176. } else {
  177. deleteButton.domNode.style.display = 'block';
  178. }
  179. }
  180. /**
  181. * Creates and enqueues a paragraph-style cloning operation.
  182. * Remembers the id of the created style in newStyleName, so the
  183. * style picker can be set to it, once the operation has been applied.
  184. * @param {!string} styleName id of the style to clone
  185. * @param {!string} newStyleDisplayName display name of the new style
  186. */
  187. function cloneStyle(styleName, newStyleDisplayName) {
  188. var newStyleName = editorSession.cloneParagraphStyle(styleName, newStyleDisplayName);
  189. setStyle(newStyleName);
  190. }
  191. function deleteStyle(styleName) {
  192. editorSession.deleteStyle(styleName);
  193. }
  194. // Dialog
  195. dialog = new Dialog({
  196. title: tr("Paragraph Styles")
  197. });
  198. mainLayoutContainer = new LayoutContainer({
  199. style: "height: 520px; width: 450px;"
  200. });
  201. topBar = new ContentPane({
  202. region: "top",
  203. style: "margin: 0; padding: 0"
  204. });
  205. mainLayoutContainer.addChild(topBar);
  206. cloneTooltip = new TooltipDialog({
  207. content:
  208. '<h2 style="margin: 0;">'+tr("Clone this Style")+'</h2><br/>' +
  209. '<label for="name">'+tr("New Name:")+'</label> <input data-dojo-type="dijit/form/TextBox" id="name" name="name"><br/><br/>',
  210. style: "width: 300px;"
  211. });
  212. cloneButton = new Button({
  213. label: tr("Create"),
  214. onClick: function () {
  215. cloneStyle(stylePicker.value(), cloneTooltip.get('value').name);
  216. cloneTooltip.reset();
  217. popup.close(cloneTooltip);
  218. }
  219. });
  220. cloneTooltip.addChild(cloneButton);
  221. cloneDropDown = new DropDownButton({
  222. label: tr("Clone"),
  223. showLabel: false,
  224. iconClass: 'dijitEditorIcon dijitEditorIconCopy',
  225. dropDown: cloneTooltip,
  226. style: "float: right; margin-bottom: 5px;"
  227. });
  228. topBar.addChild(cloneDropDown, 1);
  229. deleteButton = new Button({
  230. label: tr("Delete"),
  231. showLabel: false,
  232. iconClass: 'dijitEditorIcon dijitEditorIconDelete',
  233. style: "float: right; margin-bottom: 5px;",
  234. onClick: function () {
  235. deleteStyle(stylePicker.value());
  236. }
  237. });
  238. topBar.addChild(deleteButton, 2);
  239. // Tab Container
  240. tabContainer = new TabContainer({
  241. region: "center"
  242. });
  243. mainLayoutContainer.addChild(tabContainer);
  244. actionBar = dojo.create("div", {
  245. "class": "dijitDialogPaneActionBar"
  246. });
  247. okButton = new dijit.form.Button({
  248. label: tr("OK"),
  249. onClick: accept
  250. }).placeAt(actionBar);
  251. cancelButton = new dijit.form.Button({
  252. label: tr("Cancel"),
  253. onClick: cancel
  254. }).placeAt(actionBar);
  255. dialog.domNode.appendChild(actionBar);
  256. require([
  257. "webodf/editor/widgets/paragraphStyles",
  258. "webodf/editor/widgets/dialogWidgets/alignmentPane",
  259. "webodf/editor/widgets/dialogWidgets/fontEffectsPane"
  260. ], function (ParagraphStyles, AlignmentPane, FontEffectsPane) {
  261. var p, a, f;
  262. p = new ParagraphStyles(function (paragraphStyles) {
  263. stylePicker = paragraphStyles;
  264. stylePicker.widget().startup();
  265. stylePicker.widget().domNode.style.float = "left";
  266. stylePicker.widget().domNode.style.width = "350px";
  267. stylePicker.widget().domNode.style.marginTop = "5px";
  268. topBar.addChild(stylePicker.widget(), 0);
  269. stylePicker.onRemove = function (name) {
  270. // The style picker automatically falls back
  271. // to the first entry if the currently selected
  272. // entry is deleted. So it is safe to simply
  273. // open the new auto-selected entry after removal.
  274. setStyle(stylePicker.value());
  275. };
  276. stylePicker.onChange = setStyle;
  277. stylePicker.setEditorSession(editorSession);
  278. });
  279. a = new AlignmentPane(function (pane) {
  280. alignmentPane = pane;
  281. alignmentPane.widget().startup();
  282. tabContainer.addChild(alignmentPane.widget());
  283. alignmentPane.setEditorSession(editorSession);
  284. });
  285. f = new FontEffectsPane(function (pane) {
  286. fontEffectsPane = pane;
  287. fontEffectsPane.widget().startup();
  288. tabContainer.addChild(fontEffectsPane.widget());
  289. fontEffectsPane.setEditorSession(editorSession);
  290. });
  291. dialog.onShow = function () {
  292. var currentStyle = editorSession.getCurrentParagraphStyle();
  293. setStyle(currentStyle);
  294. };
  295. dialog.onHide = self.onToolDone;
  296. });
  297. dialog.addChild(mainLayoutContainer);
  298. mainLayoutContainer.startup();
  299. return callback(dialog);
  300. });
  301. }
  302. this.setEditorSession = function(session) {
  303. editorSession = session;
  304. if (stylePicker) {
  305. stylePicker.setEditorSession(session);
  306. }
  307. if (alignmentPane) {
  308. alignmentPane.setEditorSession(session);
  309. }
  310. if (fontEffectsPane) {
  311. fontEffectsPane.setEditorSession(session);
  312. }
  313. if (!editorSession && dialog) { // TODO: check show state
  314. dialog.hide();
  315. }
  316. };
  317. this.onToolDone = function () {};
  318. // init
  319. makeWidget(function (dialog) {
  320. return callback(dialog);
  321. });
  322. };
  323. });