paragraphStylesDialog.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  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, runtime */
  25. define("webodf/editor/widgets/paragraphStylesDialog", [
  26. "webodf/editor/widgets/dialogWidgets/idMangler"],
  27. function (IdMangler) {
  28. "use strict";
  29. return function ParagraphStylesDialog(callback) {
  30. var self = this,
  31. idMangler = new IdMangler(),
  32. editorSession,
  33. dialog,
  34. stylePicker, alignmentPane, fontEffectsPane;
  35. function makeWidget(callback) {
  36. require([
  37. "dijit/Dialog",
  38. "dijit/TooltipDialog",
  39. "dijit/popup",
  40. "dijit/layout/LayoutContainer",
  41. "dijit/layout/TabContainer",
  42. "dijit/layout/ContentPane",
  43. "dijit/form/Button",
  44. "dijit/form/DropDownButton"], function (Dialog, TooltipDialog, popup, LayoutContainer, TabContainer, ContentPane, Button, DropDownButton) {
  45. var tr = runtime.tr,
  46. mainLayoutContainer,
  47. tabContainer,
  48. topBar,
  49. actionBar,
  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. propertyName: 'fontSize',
  60. attributeName: 'fo:font-size',
  61. unit: 'pt'
  62. }, {
  63. propertyName: 'fontName',
  64. attributeName: 'style:font-name'
  65. }, {
  66. propertyName: 'color',
  67. attributeName: 'fo:color'
  68. }, {
  69. propertyName: 'backgroundColor',
  70. attributeName: 'fo:background-color'
  71. }, {
  72. propertyName: 'fontWeight',
  73. attributeName: 'fo:font-weight'
  74. }, {
  75. propertyName: 'fontStyle',
  76. attributeName: 'fo:font-style'
  77. }, {
  78. propertyName: 'underline',
  79. attributeName: 'style:text-underline-style'
  80. }, {
  81. propertyName: 'strikethrough',
  82. attributeName: 'style:text-line-through-style'
  83. }],
  84. /**
  85. * Mapping of the properties from edit pane properties to the attributes of style:paragraph-properties
  86. * @const@type{Array.<!{propertyName:string,attributeName:string,unit:string}>}
  87. */
  88. paragraphPropertyMapping = [{
  89. propertyName: 'topMargin',
  90. attributeName: 'fo:margin-top',
  91. unit: 'mm'
  92. }, {
  93. propertyName: 'bottomMargin',
  94. attributeName: 'fo:margin-bottom',
  95. unit: 'mm'
  96. }, {
  97. propertyName: 'leftMargin',
  98. attributeName: 'fo:margin-left',
  99. unit: 'mm'
  100. }, {
  101. propertyName: 'rightMargin',
  102. attributeName: 'fo:margin-right',
  103. unit: 'mm'
  104. }, {
  105. propertyName: 'textAlign',
  106. attributeName: 'fo:text-align'
  107. }],
  108. originalFontEffectsPaneValue,
  109. originalAlignmentPaneValue;
  110. /**
  111. * Sets attributes of a node by the properties of the object properties,
  112. * based on the mapping defined in propertyMapping.
  113. * @param {!Object} properties
  114. * @param {!Array.<!{propertyName:string,attributeName:string,unit:string}>} propertyMapping
  115. * @return {!Object}
  116. */
  117. function mappedProperties(properties, propertyMapping) {
  118. var i, m, value,
  119. result = {};
  120. for (i = 0; i < propertyMapping.length; i += 1) {
  121. m = propertyMapping[i];
  122. value = properties[m.propertyName];
  123. // Set a value as the attribute of a node, if that value is defined.
  124. // If there is a unit specified, it is suffixed to the value.
  125. if (value !== undefined) {
  126. result[m.attributeName] = (m.unit !== undefined) ? value + m.unit : value;
  127. }
  128. }
  129. return result;
  130. }
  131. /**
  132. * Returns an flat object containing only the key-value mappings
  133. * from the 'new' flat object which are different from the 'old' object's.
  134. * @param {!Object} oldProperties
  135. * @param {!Object} newProperties
  136. * @return {!Object}
  137. */
  138. function updatedProperties(oldProperties, newProperties) {
  139. var properties = {};
  140. Object.keys(newProperties).forEach(function (key) {
  141. if (newProperties[key] !== oldProperties[key]) {
  142. properties[key] = newProperties[key];
  143. }
  144. });
  145. return properties;
  146. }
  147. function accept() {
  148. editorSession.updateParagraphStyle(stylePicker.value(), {
  149. "style:paragraph-properties": mappedProperties(
  150. updatedProperties(originalAlignmentPaneValue, alignmentPane.value()),
  151. paragraphPropertyMapping
  152. ),
  153. "style:text-properties": mappedProperties(
  154. updatedProperties(originalFontEffectsPaneValue, fontEffectsPane.value()),
  155. textPropertyMapping
  156. )
  157. });
  158. dialog.hide();
  159. }
  160. function cancel() {
  161. dialog.hide();
  162. }
  163. function setStyle(value) {
  164. if (value !== stylePicker.value()) {
  165. stylePicker.setValue(value);
  166. }
  167. alignmentPane.setStyle(value);
  168. fontEffectsPane.setStyle(value);
  169. originalAlignmentPaneValue = alignmentPane.value();
  170. originalFontEffectsPaneValue = fontEffectsPane.value();
  171. // If it is a default (nameless) style or is used, make it undeletable.
  172. if (value === "" || editorSession.isStyleUsed(editorSession.getParagraphStyleElement(value))) {
  173. deleteButton.domNode.style.display = 'none';
  174. } else {
  175. deleteButton.domNode.style.display = 'block';
  176. }
  177. }
  178. /**
  179. * Creates and enqueues a paragraph-style cloning operation.
  180. * Remembers the id of the created style in newStyleName, so the
  181. * style picker can be set to it, once the operation has been applied.
  182. * @param {!string} styleName id of the style to clone
  183. * @param {!string} newStyleDisplayName display name of the new style
  184. */
  185. function cloneStyle(styleName, newStyleDisplayName) {
  186. var newStyleName = editorSession.cloneParagraphStyle(styleName, newStyleDisplayName);
  187. setStyle(newStyleName);
  188. }
  189. function deleteStyle(styleName) {
  190. editorSession.deleteStyle(styleName);
  191. }
  192. // Dialog
  193. dialog = new Dialog({
  194. title: tr("Paragraph Styles")
  195. });
  196. mainLayoutContainer = new LayoutContainer({
  197. style: "height: 520px; width: 450px;"
  198. });
  199. topBar = new ContentPane({
  200. region: "top",
  201. style: "margin: 0; padding: 0"
  202. });
  203. mainLayoutContainer.addChild(topBar);
  204. cloneTooltip = new TooltipDialog({
  205. content: idMangler.mangleIds(
  206. '<h2 style="margin: 0;">' + tr("Clone this Style") + '</h2><br/>' +
  207. '<label for="name">' + tr("New Name:") + '</label> <input data-dojo-type="dijit/form/TextBox" id="name" name="name"><br/><br/>'),
  208. style: "width: 300px;"
  209. });
  210. cloneButton = new Button({
  211. label: tr("Create"),
  212. onClick: function () {
  213. cloneStyle(stylePicker.value(), cloneTooltip.get('value').name);
  214. cloneTooltip.reset();
  215. popup.close(cloneTooltip);
  216. }
  217. });
  218. cloneTooltip.addChild(cloneButton);
  219. cloneDropDown = new DropDownButton({
  220. label: tr("Clone"),
  221. showLabel: false,
  222. iconClass: 'dijitEditorIcon dijitEditorIconCopy',
  223. dropDown: cloneTooltip,
  224. style: "float: right; margin-bottom: 5px;"
  225. });
  226. topBar.addChild(cloneDropDown, 1);
  227. deleteButton = new Button({
  228. label: tr("Delete"),
  229. showLabel: false,
  230. iconClass: 'dijitEditorIcon dijitEditorIconDelete',
  231. style: "float: right; margin-bottom: 5px;",
  232. onClick: function () {
  233. deleteStyle(stylePicker.value());
  234. }
  235. });
  236. topBar.addChild(deleteButton, 2);
  237. // Tab Container
  238. tabContainer = new TabContainer({
  239. region: "center"
  240. });
  241. mainLayoutContainer.addChild(tabContainer);
  242. actionBar = dojo.create("div", {
  243. "class": "dijitDialogPaneActionBar"
  244. });
  245. new dijit.form.Button({
  246. label: tr("OK"),
  247. onClick: accept
  248. }).placeAt(actionBar);
  249. new dijit.form.Button({
  250. label: tr("Cancel"),
  251. onClick: cancel
  252. }).placeAt(actionBar);
  253. dialog.domNode.appendChild(actionBar);
  254. require([
  255. "webodf/editor/widgets/paragraphStyles",
  256. "webodf/editor/widgets/dialogWidgets/alignmentPane",
  257. "webodf/editor/widgets/dialogWidgets/fontEffectsPane"
  258. ], function (ParagraphStyles, AlignmentPane, FontEffectsPane) {
  259. var p, a, f;
  260. p = new ParagraphStyles(function (paragraphStyles) {
  261. stylePicker = paragraphStyles;
  262. stylePicker.widget().startup();
  263. stylePicker.widget().domNode.style.float = "left";
  264. stylePicker.widget().domNode.style.width = "350px";
  265. stylePicker.widget().domNode.style.marginTop = "5px";
  266. topBar.addChild(stylePicker.widget(), 0);
  267. stylePicker.onRemove = function () {
  268. // The style picker automatically falls back
  269. // to the first entry if the currently selected
  270. // entry is deleted. So it is safe to simply
  271. // open the new auto-selected entry after removal.
  272. setStyle(stylePicker.value());
  273. };
  274. stylePicker.onChange = setStyle;
  275. stylePicker.setEditorSession(editorSession);
  276. });
  277. a = new AlignmentPane(function (pane) {
  278. alignmentPane = pane;
  279. alignmentPane.widget().startup();
  280. tabContainer.addChild(alignmentPane.widget());
  281. alignmentPane.setEditorSession(editorSession);
  282. });
  283. f = new FontEffectsPane(function (pane) {
  284. fontEffectsPane = pane;
  285. fontEffectsPane.widget().startup();
  286. tabContainer.addChild(fontEffectsPane.widget());
  287. fontEffectsPane.setEditorSession(editorSession);
  288. });
  289. dialog.onShow = function () {
  290. var currentStyle = editorSession.getCurrentParagraphStyle();
  291. setStyle(currentStyle);
  292. };
  293. dialog.onHide = self.onToolDone;
  294. // only done to make jslint see the var used
  295. return p || a || f;
  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. /*jslint emptyblock: true*/
  318. this.onToolDone = function () {};
  319. /*jslint emptyblock: false*/
  320. // init
  321. makeWidget(function (dialog) {
  322. return callback(dialog);
  323. });
  324. };
  325. });