Index: lams_central/web/includes/javascript/authoring/authoringActivity.js =================================================================== diff -u -r2925642c3d5894f625c81ed5a7c35397af3fd98d -r3cf44886d1cad7009a685cca2a118e2a3f17a730 --- lams_central/web/includes/javascript/authoring/authoringActivity.js (.../authoringActivity.js) (revision 2925642c3d5894f625c81ed5a7c35397af3fd98d) +++ lams_central/web/includes/javascript/authoring/authoringActivity.js (.../authoringActivity.js) (revision 3cf44886d1cad7009a685cca2a118e2a3f17a730) @@ -5,1949 +5,1963 @@ /** * For colouring. See LDEV-5058 * CATEGORY_SYSTEM = 1; - CATEGORY_COLLABORATION = 2; - CATEGORY_ASSESSMENT = 3; - CATEGORY_CONTENT = 4; - CATEGORY_SPLIT = 5; - CATEGORY_RESPONSE = 6; + CATEGORY_COLLABORATION = 2; + CATEGORY_ASSESSMENT = 3; + CATEGORY_CONTENT = 4; + CATEGORY_SPLIT = 5; + CATEGORY_RESPONSE = 6; */ ActivityCategories = { - 'Assessment' : 3, - 'Bbb' : 2, - 'Chat' : 2, - 'Data Collection' : 6, - 'doKumaran' : 2, - 'Forum' : 2, - 'Gmap' : 2, - 'Share imageGallery' : 4, - 'Share commonCartridge' : 4, - 'MCQ' : 3, - 'Question and Answer' : 6, - 'Share resources' : 4, - 'Leaderselection' : 2, - 'Mindmap' : 6, - 'Noticeboard' : 4, - 'Notebook' : 6, - 'Peerreview' : 3, - 'Pixlr' : 4, - 'Submit file' : 3, - 'Scratchie' : 3, - 'Scribe' : 2, - 'Spreadsheet' : 4, - 'Survey' : 6, - 'Share taskList' : 4, - 'Voting' : 6, - 'Whiteboard' : 2, - 'Wiki' : 2, - 'Kaltura' : 2, - 'Zoom' : 2, - 'Resources and Forum' : 5, - 'Chat and Scribe' : 5, - 'Forum and Scribe' : 5, - - 'grouping' : 1, - 'gate' : 1, - 'branching': 1, - 'optional' : 1, - 'floating' : 1 + 'Assessment' : 3, + 'Bbb' : 2, + 'Chat' : 2, + 'Data Collection' : 6, + 'doKumaran' : 2, + 'Forum' : 2, + 'Gmap' : 2, + 'Share imageGallery' : 4, + 'Share commonCartridge' : 4, + 'MCQ' : 3, + 'Question and Answer' : 6, + 'Share resources' : 4, + 'Leaderselection' : 2, + 'Mindmap' : 6, + 'Noticeboard' : 4, + 'Notebook' : 6, + 'Peerreview' : 3, + 'Pixlr' : 4, + 'Submit file' : 3, + 'Scratchie' : 3, + 'Scribe' : 2, + 'Spreadsheet' : 4, + 'Survey' : 6, + 'Share taskList' : 4, + 'Voting' : 6, + 'Whiteboard' : 2, + 'Wiki' : 2, + 'Kaltura' : 2, + 'Zoom' : 2, + 'Resources and Forum' : 5, + 'Chat and Scribe' : 5, + 'Forum and Scribe' : 5, + + 'grouping' : 1, + 'gate' : 1, + 'branching': 1, + 'optional' : 1, + 'floating' : 1 }, -ActivityDefs = { - - /** - * Either branching (start) or converge (end) point. - */ - BranchingEdgeActivity : function(id, uiid, x, y, title, readOnly, branchingType, branchingActivity) { - this.transitions = { - 'from' : [], - 'to' : [] - }; - - if (branchingActivity) { - // branchingActivity already exists, so this is the converge point - this.isStart = false; - branchingActivity.end = this; - } else { - // this is the branching point - this.isStart = true; - branchingActivity = new ActivityDefs.BranchingActivity(id, uiid, this, readOnly); - branchingActivity.branchingType = branchingType || 'chosen'; - branchingActivity.title = title || LABELS.DEFAULT_BRANCHING_TITLE; - } - this.branchingActivity = branchingActivity; - - if (!isReadOnlyMode){ - this.loadPropertiesDialogContent = PropertyDefs.branchingProperties; - } - - this.draw = ActivityDraw.branching; - this.draw(x, y); - }, - - - /** - * Represents a set of branches. It is not displayed on canvas, but holds all the vital data. - */ - BranchingActivity : function(id, uiid, branchingEdgeStart, readOnly, orderedAsc) { - this.id = +id || null; - this.uiid = +uiid || ++layout.ld.maxUIID; - this.start = branchingEdgeStart; - this.readOnly = readOnly; - this.orderedAsc = orderedAsc; - this.branches = []; - // mapping between groups and branches, if applicable - this.groupsToBranches = []; - // mapping between tool output and branches, if applicable - this.conditionsToBranches = []; - - this.minOptions = 0; - this.maxOptions = 0; - }, - - - /** - * Represents a subsequence of activities. It is not displayed on canvas, but is the parent activity for its children. - */ - BranchActivity : function(id, uiid, title, branchingActivity, transitionFrom, defaultBranch) { - this.id = +id || null; - this.uiid = +uiid || ++layout.ld.maxUIID; - this.title = title || (LABELS.DEFAULT_BRANCH_PREFIX + (branchingActivity.branches.length + 1)); - this.branchingActivity = branchingActivity; - this.transitionFrom = transitionFrom; - if (defaultBranch) { - this.defaultBranch = true; - // there can be only one default branch - $.each(branchingActivity.branches, function(){ - this.defaultBranch = false; - }); - } - }, - - - /** - * Constructor for a Floating Activity. - */ - FloatingActivity : function(id, uiid, x, y) { - DecorationDefs.Container.call(this, id, uiid, LABELS.SUPPORT_ACTIVITY_TITLE); - - this.draw = ActivityDraw.floatingActivity; - this.draw(x, y); - - // there can only be one Floating Activity container - layout.floatingActivity = this; - }, - - - /** - * Constructor for a Gate Activity. - */ - GateActivity : function(id, uiid, x, y, title, description, readOnly, gateType, startTimeOffset, - gateActivityCompletionBased, gateStopAtPrecedingActivity, password) { - this.id = +id || null; - this.uiid = +uiid || ++layout.ld.maxUIID; - this.title = title; - this.description = description; - this.readOnly = readOnly; - this.gateType = gateType || 'permission'; - this.gateStopAtPrecedingActivity = gateStopAtPrecedingActivity; - - if (gateType == 'schedule') { - var day = 24*60; - this.offsetDay = Math.floor(startTimeOffset/day); - startTimeOffset -= this.offsetDay * day; - this.offsetHour = Math.floor(startTimeOffset/60); - this.offsetMinute = startTimeOffset - this.offsetHour * 60; - - this.gateActivityCompletionBased = gateActivityCompletionBased; - }; - if (gateType == 'password') { - this.password = password; - } - - // mapping between tool output and gate states ("branches"), if applicable - this.conditionsToBranches = []; - this.transitions = { - 'from' : [], - 'to' : [] - }; - - if (!isReadOnlyMode){ - this.loadPropertiesDialogContent = PropertyDefs.gateProperties; - } - - this.draw = ActivityDraw.gate; - this.draw(x, y); - }, - - - /** - * Constructor for a Grouping Activity. - */ - GroupingActivity : function(id, uiid, x, y, title, readOnly, groupingID, groupingUIID, groupingType, groupDivide, - groupCount, learnerCount, equalSizes, viewLearners, groups) { - this.id = +id || null; - this.groupingID = +groupingID || null; - this.groupingUIID = +groupingUIID || ++layout.ld.maxUIID; - this.uiid = +uiid || ++layout.ld.maxUIID; - this.title = title || LABELS.DEFAULT_GROUPING_TITLE; - this.readOnly = readOnly; - this.groupingType = groupingType || 'monitor'; - this.groupDivide = groupDivide || 'groups'; - this.groupCount = +groupCount || layout.conf.defaultGroupingGroupCount; - if (groups && groups.length > this.groupCount) { - // when opening a run sequence, groups created in monitoring can be more numerous then the original setting - this.groupCount = groups.length; - } - this.learnerCount = +learnerCount || layout.conf.defaultGroupingLearnerCount; - this.equalSizes = equalSizes || false; - this.viewLearners = viewLearners || false; - // either groups are already defined or create them with default names - this.groups = groups || PropertyLib.fillNameAndUIIDList(this.groupCount, [], 'name', LABELS.DEFAULT_GROUP_PREFIX); - this.transitions = { - 'from' : [], - 'to' : [] - }; - - if (!isReadOnlyMode){ - this.loadPropertiesDialogContent = PropertyDefs.groupingProperties; - } - - this.draw = ActivityDraw.grouping; - this.draw(x, y); - }, - - - /** - * Constructor for an Optional Activity. - */ - OptionalActivity : function(id, uiid, x, y, title, readOnly, minOptions, maxOptions) { - DecorationDefs.Container.call(this, id, uiid, title || LABELS.DEFAULT_OPTIONAL_ACTIVITY_TITLE); - - this.id = +id || null; - this.uiid = +uiid || ++layout.ld.maxUIID; - this.readOnly = readOnly; - this.minOptions = minOptions || 0; - this.maxOptions = maxOptions || 0; - this.transitions = { - 'from' : [], - 'to' : [] - }; - - if (!isReadOnlyMode){ - this.loadPropertiesDialogContent = PropertyDefs.optionalActivityProperties; - } - - this.draw = ActivityDraw.optionalActivity; - this.draw(x, y); - }, - - - /** - * Constructor for a Parallel (double) Activity - */ - ParallelActivity : function(id, uiid, learningLibraryID, x, y, title, readOnly, childActivities){ - DecorationDefs.Container.call(this, id, uiid, title); - - this.id = +id || null; - this.uiid = +uiid || ++layout.ld.maxUIID; - this.readOnly = readOnly; - this.learningLibraryID = +learningLibraryID; - this.transitions = { - 'from' : [], - 'to' : [] - }; - if (childActivities){ - this.childActivities = childActivities; - } - - if (!isReadOnlyMode){ - this.loadPropertiesDialogContent = PropertyDefs.parallelProperties; - } - - this.draw = ActivityDraw.parallelActivity; - this.draw(x, y); - }, - - - /** - * Constructor for a Tool Activity. - */ - ToolActivity : function(id, uiid, toolContentID, toolID, learningLibraryID, authorURL, x, y, title, - readOnly, evaluation) { - this.id = +id || null; - this.uiid = +uiid || ++layout.ld.maxUIID; - this.toolContentID = toolContentID; - this.toolID = +toolID; - this.learningLibraryID = +learningLibraryID; - this.authorURL = authorURL; - this.title = title; - this.readOnly = readOnly; - this.requireGrouping = false; - if (evaluation) { - this.gradebookToolOutputDefinitionName = evaluation[0]; - this.gradebookToolOutputWeight = evaluation.length > 1 ? evaluation[1] : null; - } - - this.transitions = { - 'from' : [], - 'to' : [] - }; - - // set Gradebook output name right now - ActivityLib.getOutputDefinitions(this); + ActivityDefs = { - if (!isReadOnlyMode){ - this.loadPropertiesDialogContent = PropertyDefs.toolProperties; - } - - this.draw = ActivityDraw.tool; - this.draw(x, y); - }, - - - /** - * Constructor for a Transition - */ - Transition : function(id, uiid, fromActivity, toActivity, title) { - this.id = +id || null; - this.uiid = +uiid || ++layout.ld.maxUIID; - this.fromActivity = fromActivity; - this.toActivity = toActivity; - if (title) { - // only branches have titles + /** + * Either branching (start) or converge (end) point. + */ + BranchingEdgeActivity : function(id, uiid, x, y, title, readOnly, branchingType, branchingActivity) { + this.transitions = { + 'from' : [], + 'to' : [] + }; + + if (branchingActivity) { + // branchingActivity already exists, so this is the converge point + this.isStart = false; + branchingActivity.end = this; + } else { + // this is the branching point + this.isStart = true; + branchingActivity = new ActivityDefs.BranchingActivity(id, uiid, this, readOnly); + branchingActivity.branchingType = branchingType || 'chosen'; + branchingActivity.title = title || LABELS.DEFAULT_BRANCHING_TITLE; + } + this.branchingActivity = branchingActivity; + + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.branchingProperties; + } + + this.draw = ActivityDraw.branching; + this.draw(x, y); + }, + + + /** + * Represents a set of branches. It is not displayed on canvas, but holds all the vital data. + */ + BranchingActivity : function(id, uiid, branchingEdgeStart, readOnly, orderedAsc) { + this.id = +id || null; + this.uiid = +uiid || ++layout.ld.maxUIID; + this.start = branchingEdgeStart; + this.readOnly = readOnly; + this.orderedAsc = orderedAsc; + this.branches = []; + // mapping between groups and branches, if applicable + this.groupsToBranches = []; + // mapping between tool output and branches, if applicable + this.conditionsToBranches = []; + + this.minOptions = 0; + this.maxOptions = 0; + }, + + + /** + * Represents a subsequence of activities. It is not displayed on canvas, but is the parent activity for its children. + */ + BranchActivity : function(id, uiid, title, branchingActivity, transitionFrom, defaultBranch) { + this.id = +id || null; + this.uiid = +uiid || ++layout.ld.maxUIID; + this.title = title || (LABELS.DEFAULT_BRANCH_PREFIX + (branchingActivity.branches.length + 1)); + this.branchingActivity = branchingActivity; + this.transitionFrom = transitionFrom; + if (defaultBranch) { + this.defaultBranch = true; + // there can be only one default branch + $.each(branchingActivity.branches, function(){ + this.defaultBranch = false; + }); + } + }, + + + /** + * Constructor for a Floating Activity. + */ + FloatingActivity : function(id, uiid, x, y) { + DecorationDefs.Container.call(this, id, uiid, LABELS.SUPPORT_ACTIVITY_TITLE); + + this.draw = ActivityDraw.floatingActivity; + this.draw(x, y); + + // there can only be one Floating Activity container + layout.floatingActivity = this; + }, + + + /** + * Constructor for a Gate Activity. + */ + GateActivity : function(id, uiid, x, y, title, description, readOnly, gateType, startTimeOffset, + gateActivityCompletionBased, gateStopAtPrecedingActivity, password) { + this.id = +id || null; + this.uiid = +uiid || ++layout.ld.maxUIID; this.title = title; - + this.description = description; + this.readOnly = readOnly; + this.gateType = gateType || 'permission'; + this.gateStopAtPrecedingActivity = gateStopAtPrecedingActivity; + + if (gateType == 'schedule') { + var day = 24*60; + this.offsetDay = Math.floor(startTimeOffset/day); + startTimeOffset -= this.offsetDay * day; + this.offsetHour = Math.floor(startTimeOffset/60); + this.offsetMinute = startTimeOffset - this.offsetHour * 60; + + this.gateActivityCompletionBased = gateActivityCompletionBased; + }; + if (gateType == 'password') { + this.password = password; + } + + // mapping between tool output and gate states ("branches"), if applicable + this.conditionsToBranches = []; + this.transitions = { + 'from' : [], + 'to' : [] + }; + if (!isReadOnlyMode){ - this.loadPropertiesDialogContent = PropertyDefs.transitionProperties; + this.loadPropertiesDialogContent = PropertyDefs.gateProperties; } + + this.draw = ActivityDraw.gate; + this.draw(x, y); + }, + + + /** + * Constructor for a Grouping Activity. + */ + GroupingActivity : function(id, uiid, x, y, title, readOnly, groupingID, groupingUIID, groupingType, groupDivide, + groupCount, learnerCount, equalSizes, viewLearners, groups) { + this.id = +id || null; + this.groupingID = +groupingID || null; + this.groupingUIID = +groupingUIID || ++layout.ld.maxUIID; + this.uiid = +uiid || ++layout.ld.maxUIID; + this.title = title || LABELS.DEFAULT_GROUPING_TITLE; + this.readOnly = readOnly; + this.groupingType = groupingType || 'monitor'; + this.groupDivide = groupDivide || 'groups'; + this.groupCount = +groupCount || layout.conf.defaultGroupingGroupCount; + if (groups && groups.length > this.groupCount) { + // when opening a run sequence, groups created in monitoring can be more numerous then the original setting + this.groupCount = groups.length; + } + this.learnerCount = +learnerCount || layout.conf.defaultGroupingLearnerCount; + this.equalSizes = equalSizes || false; + this.viewLearners = viewLearners || false; + // either groups are already defined or create them with default names + this.groups = groups || PropertyLib.fillNameAndUIIDList(this.groupCount, [], 'name', LABELS.DEFAULT_GROUP_PREFIX); + this.transitions = { + 'from' : [], + 'to' : [] + }; + + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.groupingProperties; + } + + this.draw = ActivityDraw.grouping; + this.draw(x, y); + }, + + + /** + * Constructor for an Optional Activity. + */ + OptionalActivity : function(id, uiid, x, y, title, readOnly, minOptions, maxOptions) { + DecorationDefs.Container.call(this, id, uiid, title || LABELS.DEFAULT_OPTIONAL_ACTIVITY_TITLE); + + this.id = +id || null; + this.uiid = +uiid || ++layout.ld.maxUIID; + this.readOnly = readOnly; + this.minOptions = minOptions || 0; + this.maxOptions = maxOptions || 0; + this.transitions = { + 'from' : [], + 'to' : [] + }; + + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.optionalActivityProperties; + } + + this.draw = ActivityDraw.optionalActivity; + this.draw(x, y); + }, + + + /** + * Constructor for a Parallel (double) Activity + */ + ParallelActivity : function(id, uiid, learningLibraryID, x, y, title, readOnly, childActivities){ + DecorationDefs.Container.call(this, id, uiid, title); + + this.id = +id || null; + this.uiid = +uiid || ++layout.ld.maxUIID; + this.readOnly = readOnly; + this.learningLibraryID = +learningLibraryID; + this.transitions = { + 'from' : [], + 'to' : [] + }; + if (childActivities){ + this.childActivities = childActivities; + } + + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.parallelProperties; + } + + this.draw = ActivityDraw.parallelActivity; + this.draw(x, y); + }, + + + /** + * Constructor for a Tool Activity. + */ + ToolActivity : function(id, uiid, toolContentID, toolID, learningLibraryID, authorURL, x, y, title, + readOnly, evaluation) { + this.id = +id || null; + this.uiid = +uiid || ++layout.ld.maxUIID; + this.toolContentID = toolContentID; + this.toolID = +toolID; + this.learningLibraryID = +learningLibraryID; + this.authorURL = authorURL; + this.title = title; + this.readOnly = readOnly; + this.requireGrouping = false; + if (evaluation) { + this.gradebookToolOutputDefinitionName = evaluation[0]; + this.gradebookToolOutputWeight = evaluation.length > 1 ? evaluation[1] : null; + } + + this.transitions = { + 'from' : [], + 'to' : [] + }; + + // set Gradebook output name right now + ActivityLib.getOutputDefinitions(this); + + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.toolProperties; + } + + this.draw = ActivityDraw.tool; + this.draw(x, y); + }, + + + /** + * Constructor for a Transition + */ + Transition : function(id, uiid, fromActivity, toActivity, title) { + this.id = +id || null; + this.uiid = +uiid || ++layout.ld.maxUIID; + this.fromActivity = fromActivity; + this.toActivity = toActivity; + if (title) { + // only branches have titles + this.title = title; + + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.transitionProperties; + } + } + + this.draw = ActivityDraw.transition; + this.draw(); + + // set up references in edge activities + fromActivity.transitions.from.push(this); + toActivity.transitions.to.push(this); } - - this.draw = ActivityDraw.transition; - this.draw(); - - // set up references in edge activities - fromActivity.transitions.from.push(this); - toActivity.transitions.to.push(this); - } -}, + }, -/** - * Mehtods for drawing various kinds of activities. - * They are not defined in constructors so there is a static reference, - * not a separate definition for each object instance. - */ -ActivityDraw = { - /** - * Draws a Branching Activity + * Mehtods for drawing various kinds of activities. + * They are not defined in constructors so there is a static reference, + * not a separate definition for each object instance. */ - branching : function(x, y) { - if (x == undefined || y == undefined) { - // just redraw the activity - x = this.items.getBBox().x; - y = this.items.getBBox().y; - } - - if (this.items) { - this.items.remove(); - } - - // make the icon more centred - x = GeneralLib.snapToGrid(x - 40) + 40; - y = GeneralLib.snapToGrid(y - 20) + 20; + ActivityDraw = { - // create activity SVG elements - var shape = paper.circle(x + 20, y + 20, 20) - .addClass('svg-branching svg-shadow svg-branching-' + (this.isStart ? 'start' : 'end')), - icon = ActivityLib.getActivityIcon(this.isStart ? 'branching' : 'branchingEnd'); - icon.select('svg').attr({ - 'x' : x + 5, - 'y' : y + 5, - 'width' : '30px', - 'height': '30px' - }); - - this.items = paper.g(shape, icon); - this.items.addClass('svg-activity svg-activity-branching'); - if (this.readOnly && !isReadOnlyMode) { - this.items.attr('filter', layout.conf.readOnlyFilter); - } - if (this.isStart) { + /** + * Draws a Branching Activity + */ + branching : function(x, y) { + if (x == undefined || y == undefined) { + // just redraw the activity + x = this.items.getBBox().x; + y = this.items.getBBox().y; + } + + if (this.items) { + this.items.remove(); + } + + // make the icon more centred + x = GeneralLib.snapToGrid(x - 40) + 40; + y = GeneralLib.snapToGrid(y - 20) + 20; + + // create activity SVG elements + var shape = paper.circle(x + 20, y + 20, 20) + .addClass('svg-branching svg-shadow svg-branching-' + (this.isStart ? 'start' : 'end')), + icon = ActivityLib.getActivityIcon(this.isStart ? 'branching' : 'branchingEnd'); + icon.select('svg').attr({ + 'x' : x + 5, + 'y' : y + 5, + 'width' : '30px', + 'height': '30px' + }); + + this.items = paper.g(shape, icon); + this.items.addClass('svg-activity svg-activity-branching'); + if (this.readOnly && !isReadOnlyMode) { + this.items.attr('filter', layout.conf.readOnlyFilter); + } + if (this.isStart) { + // these are needed in monitoring + this.items.attr({ + 'uiid' : this.branchingActivity.uiid, + 'data-x' : x, + 'data-y' : y, + 'data-width' : 40, + 'data-height': 40 + }); + } + this.items.shape = shape; + + ActivityLib.activityHandlersInit(this); + }, + + + /** + * Draws a Floating (support) Activity container + */ + floatingActivity : function(x, y, ignoredParam1, ignoredParam2, childActivities) { + if (x == undefined || y == undefined) { + // if no new coordinates are given, just redraw the activity + x = this.items.getBBox().x; + y = this.items.getBBox().y; + } + + x = GeneralLib.snapToGrid(x); + y = GeneralLib.snapToGrid(y); + + // either check what children are on canvas or use the priovided parameter + if (childActivities) { + this.childActivities = childActivities; + } + + if (this.childActivities && this.childActivities.length > 0) { + // draw one by one, horizontally + var activityX = x + layout.conf.containerActivityPadding, + allElements = Snap.set(), + floatingActivity = this, + box = this.items.getBBox(); + $.each(this.childActivities, function(orderID){ + this.parentActivity = floatingActivity; + this.orderID = orderID; + var childBox = this.items.shape.getBBox(); + this.draw(activityX, y + Math.max(layout.conf.containerActivityPadding + 10, (box.height - childBox.height)/2), true); + childBox = this.items.shape.getBBox(); + activityX = childBox.x2 + layout.conf.containerActivityChildrenPadding; + allElements.push(this.items.shape); + }); + // area containing all drawn child activities + box = allElements.getBBox(); + + this.drawContainer(x, y, + box.x2 + layout.conf.containerActivityPadding, + box.y2 + layout.conf.containerActivityPadding); + } else { + this.drawContainer(x, y, + x + layout.conf.containerActivityEmptyWidth, + y + layout.conf.containerActivityEmptyHeight); + } + + this.items.data('parentObject', this); + this.items.addClass('svg-activity svg-activity-floating svg-shadow'); + }, + + + /** + * Draws a Gate activity + */ + gate : function(x, y) { + + if (x == undefined || y == undefined) { + x = this.items.getBBox().x; + y = this.items.getBBox().y; + } + + x = Math.round(x); + y = Math.round(y); + + if (this.items) { + this.items.remove(); + } + + x = GeneralLib.snapToGrid(x); + // make the icon more centred + y = GeneralLib.snapToGrid(y); + + // create activity SVG elements + var shape = ActivityLib.getActivityIcon('gate'); + shape.select('svg').attr({ + 'x' : x, + 'y' : y, + 'width' : '40px', + 'height': '40px' + }); + + this.items = paper.g(shape); + this.items.addClass('svg-activity svg-activity-gate svg-shadow'); + if (this.readOnly && !isReadOnlyMode) { + this.items.attr('filter', layout.conf.readOnlyFilter); + } // these are needed in monitoring this.items.attr({ - 'uiid' : this.branchingActivity.uiid, + 'uiid' : this.uiid, 'data-x' : x, 'data-y' : y, 'data-width' : 40, 'data-height': 40 }); - } - this.items.shape = shape; - - ActivityLib.activityHandlersInit(this); - }, - - - /** - * Draws a Floating (support) Activity container - */ - floatingActivity : function(x, y, ignoredParam1, ignoredParam2, childActivities) { - if (x == undefined || y == undefined) { - // if no new coordinates are given, just redraw the activity - x = this.items.getBBox().x; - y = this.items.getBBox().y; - } - - x = GeneralLib.snapToGrid(x); - y = GeneralLib.snapToGrid(y); - - // either check what children are on canvas or use the priovided parameter - if (childActivities) { - this.childActivities = childActivities; - } - - if (this.childActivities && this.childActivities.length > 0) { - // draw one by one, horizontally - var activityX = x + layout.conf.containerActivityPadding, - allElements = Snap.set(), - floatingActivity = this, - box = this.items.getBBox(); - $.each(this.childActivities, function(orderID){ - this.parentActivity = floatingActivity; - this.orderID = orderID; - var childBox = this.items.shape.getBBox(); - this.draw(activityX, y + Math.max(layout.conf.containerActivityPadding + 10, (box.height - childBox.height)/2), true); - childBox = this.items.shape.getBBox(); - activityX = childBox.x2 + layout.conf.containerActivityChildrenPadding; - allElements.push(this.items.shape); - }); - // area containing all drawn child activities - box = allElements.getBBox(); - - this.drawContainer(x, y, - box.x2 + layout.conf.containerActivityPadding, - box.y2 + layout.conf.containerActivityPadding); - } else { - this.drawContainer(x, y, - x + layout.conf.containerActivityEmptyWidth, - y + layout.conf.containerActivityEmptyHeight); - } - - this.items.data('parentObject', this); - this.items.addClass('svg-activity svg-activity-floating svg-shadow'); - }, - - - /** - * Draws a Gate activity - */ - gate : function(x, y) { - - if (x == undefined || y == undefined) { - x = this.items.getBBox().x; - y = this.items.getBBox().y; - } - - x = Math.round(x); - y = Math.round(y); + this.items.shape = shape; - if (this.items) { - this.items.remove(); - } - - x = GeneralLib.snapToGrid(x); - // make the icon more centred - y = GeneralLib.snapToGrid(y); - - // create activity SVG elements - var shape = ActivityLib.getActivityIcon('gate'); - shape.select('svg').attr({ - 'x' : x, - 'y' : y, - 'width' : '40px', - 'height': '40px' - }); + ActivityLib.activityHandlersInit(this); + }, - this.items = paper.g(shape); - this.items.addClass('svg-activity svg-activity-gate svg-shadow'); - if (this.readOnly && !isReadOnlyMode) { - this.items.attr('filter', layout.conf.readOnlyFilter); - } - // these are needed in monitoring - this.items.attr({ - 'uiid' : this.uiid, - 'data-x' : x, - 'data-y' : y, - 'data-width' : 40, - 'data-height': 40 - }); - this.items.shape = shape; - - ActivityLib.activityHandlersInit(this); - }, - - - /** - * Draws a Grouping activity - */ - grouping : function(x, y) { - if (x == undefined || y == undefined) { - // if no new coordinates are given, just redraw the activity - x = this.items.getBBox().x; - y = this.items.getBBox().y; - } - - if (this.items) { - this.items.remove(); - } - - x = GeneralLib.snapToGrid(x); - y = GeneralLib.snapToGrid(y); - - // create activity SVG elements - var curve = layout.activity.borderCurve, - width = layout.activity.width, - height = layout.activity.height, - shapePath = ' M ' + (x + curve) + ' ' + y + ' h ' + (width - 2 * curve) + ' q ' + curve + ' 0 ' + curve + ' ' + curve + - ' v ' + (height - 2 * curve) + ' q 0 ' + curve + ' ' + -curve + ' ' + curve + - ' h ' + (-width + 2 * curve) + ' q ' + -curve + ' 0 ' + -curve + ' ' + -curve + - ' v ' + (-height + 2 * curve) + ' q 0 ' + -curve + ' ' + curve + ' ' + -curve, - shape = paper.path(shapePath) - .addClass('svg-tool-activity-background svg-shadow'), - shapeBorder = paper.path(shapePath) - .addClass('svg-tool-activity-border'), - // check for icon in the library - icon = ActivityLib.getActivityIcon('grouping'), - label = ActivityLib.getActivityTitle(this.title, x, y); - - icon.select('svg').attr({ - 'x' : x + 20, - 'y' : y + 15, - 'width' : '50px', - 'height': '50px' - }); - - this.items = paper.g(shape, shapeBorder, label, icon); - this.items.attr({ - 'uiid' : this.uiid, - 'data-x' : x, - 'data-y' : y, - 'data-width' : width, - 'data-height': height - }); - - this.items.addClass('svg-activity svg-activity-grouping'); - if (this.readOnly && !isReadOnlyMode) { - this.items.attr('filter', layout.conf.readOnlyFilter); - } - this.items.shape = shape; - - ActivityLib.activityHandlersInit(this); - }, - - - /** - * Draws an Optional Activity container - */ - optionalActivity : function(x, y, ignoredParam1, ignoredParam2, childActivities) { - if (x == undefined || y == undefined) { - // if no new coordinates are given, just redraw the activity - x = this.items.getBBox().x; - y = this.items.getBBox().y; - } - - x = GeneralLib.snapToGrid(x); - y = GeneralLib.snapToGrid(y); - - // either check what children are on canvas or use the priovided parameter - if (childActivities) { - this.childActivities = childActivities; - } - - var width = null, - height = null; - - if (this.childActivities && this.childActivities.length > 0) { - // draw one by one, vertically - var activityY = y + layout.conf.containerActivityPadding + 10, - allElements = Snap.set(), - optionalActivity = this, - box = this.items.getBBox(), - boxWidth = box.width; - $.each(this.childActivities, function(orderID){ - this.parentActivity = optionalActivity; - this.orderID = orderID + 1; - // for some reason, this.items.getBBox() can't be used here - var childBox = this.items.shape.getBBox(); - this.draw(x + Math.max(layout.conf.containerActivityPadding, (boxWidth - childBox.width)/2), activityY, true); - childBox = this.items.shape.getBBox(); - activityY = childBox.y2 + layout.conf.containerActivityChildrenPadding; - allElements.push(this.items.shape); + + /** + * Draws a Grouping activity + */ + grouping : function(x, y) { + if (x == undefined || y == undefined) { + // if no new coordinates are given, just redraw the activity + x = this.items.getBBox().x; + y = this.items.getBBox().y; + } + + if (this.items) { + this.items.remove(); + } + + x = GeneralLib.snapToGrid(x); + y = GeneralLib.snapToGrid(y); + + // create activity SVG elements + var curve = layout.activity.borderCurve, + width = layout.activity.width, + height = layout.activity.height, + shapePath = ' M ' + (x + curve) + ' ' + y + ' h ' + (width - 2 * curve) + ' q ' + curve + ' 0 ' + curve + ' ' + curve + + ' v ' + (height - 2 * curve) + ' q 0 ' + curve + ' ' + -curve + ' ' + curve + + ' h ' + (-width + 2 * curve) + ' q ' + -curve + ' 0 ' + -curve + ' ' + -curve + + ' v ' + (-height + 2 * curve) + ' q 0 ' + -curve + ' ' + curve + ' ' + -curve, + shape = paper.path(shapePath) + .addClass('svg-tool-activity-background svg-shadow'), + shapeBorder = paper.path(shapePath) + .addClass('svg-tool-activity-border'), + // check for icon in the library + icon = ActivityLib.getActivityIcon('grouping'), + label = ActivityLib.getActivityTitle(this.title, x, y); + + icon.select('svg').attr({ + 'x' : x + 20, + 'y' : y + 15, + 'width' : '50px', + 'height': '50px' }); - // area containing all drawn child activities - box = allElements.getBBox(); - - width = box.x2 + layout.conf.containerActivityPadding - x; - height = box.y2 + layout.conf.containerActivityPadding - y; - - this.drawContainer(x, y, x + width, y + height, - layout.colors.optionalActivity, layout.colors.optionalActivityBorder, 0.5); - } else { - width = layout.conf.containerActivityEmptyWidth; - height = layout.conf.containerActivityEmptyHeight; - - this.drawContainer(x, y, x + width, y + height, - layout.colors.optionalActivity, layout.colors.optionalActivityBorder, 0.5); - } - - if (!isReadOnlyMode){ - // allow transition drawing and other activity behaviour - this.items.unmousedown().mousedown(HandlerActivityLib.activityMousedownHandler); - } - - this.items.data('parentObject', this); - this.items.addClass('svg-activity svg-activity-optional svg-shadow'); - // these are needed in monitoring - this.items.attr({ - 'uiid' : this.uiid, - 'data-x' : x, - 'data-y' : y, - 'data-width' : width, - 'data-height': height - }); - }, - - - /** - * Draws a Parallel (double) Activity container - */ - parallelActivity : function(x, y) { - // if no new coordinates are given, just redraw the activity or give default value - if (x == undefined) { - x = this.items ? this.items.getBBox().x : 0; - } - if (y == undefined) { - y = this.items ? this.items.getBBox().y : 0; - } - - x = GeneralLib.snapToGrid(x); - y = GeneralLib.snapToGrid(y); - - var width = null, - height = null; - - if (this.childActivities && this.childActivities.length > 0) { - // draw one by one, vertically - var activityY = y + layout.conf.containerActivityPadding + 10, - allElements = Snap.set(), - optionalActivity = this; - $.each(this.childActivities, function(orderID){ - this.parentActivity = optionalActivity; - this.orderID = orderID + 1; - this.draw(x + layout.conf.containerActivityPadding, activityY, true); - activityY = this.items.getBBox().y2 + layout.conf.containerActivityChildrenPadding; - allElements.push(this.items.shape); + + this.items = paper.g(shape, shapeBorder, label, icon); + this.items.attr({ + 'uiid' : this.uiid, + 'data-x' : x, + 'data-y' : y, + 'data-width' : width, + 'data-height': height }); - // area containing all drawn child activities - var box = allElements.getBBox(); - - width = box.x2 + layout.conf.containerActivityPadding - x; - height = box.y2 + layout.conf.containerActivityPadding - y; - - this.drawContainer(x, y, x + width, y + height, - layout.colors.optionalActivity, layout.colors.optionalActivityBorder, 0.5); - } else { - width = layout.conf.containerActivityEmptyWidth; - height = layout.conf.containerActivityEmptyHeight; - - this.drawContainer(x, y, x + width, y + height, - layout.colors.optionalActivity, layout.colors.optionalActivityBorder, 0.5); - } - - if (this.grouping) { - ActivityLib.addGroupingEffect(this); - } - - if (!isReadOnlyMode){ - // allow transition drawing and other activity behaviour - this.items.unmousedown().mousedown(HandlerActivityLib.activityMousedownHandler); - } - - this.items.data('parentObject', this); - this.items.addClass('svg-activity svg-activity-parallel svg-shadow'); - // these are needed in monitoring - this.items.attr({ - 'uiid' : this.uiid, - 'data-x' : x, - 'data-y' : y, - 'data-width' : width, - 'data-height': height - }); - }, - - - /** - * Draws a Tool activity - */ - tool : function(x, y, skipSnapToGrid) { - if (x == undefined || y == undefined) { - // if no new coordinates are given, just redraw the activity - x = this.items.getBBox().x; - y = this.items.getBBox().y; - } - - if (this.items) { - this.items.remove(); - } - - if (!skipSnapToGrid) { + + this.items.addClass('svg-activity svg-activity-grouping'); + if (this.readOnly && !isReadOnlyMode) { + this.items.attr('filter', layout.conf.readOnlyFilter); + } + this.items.shape = shape; + + ActivityLib.activityHandlersInit(this); + }, + + + /** + * Draws an Optional Activity container + */ + optionalActivity : function(x, y, ignoredParam1, ignoredParam2, childActivities) { + if (x == undefined || y == undefined) { + // if no new coordinates are given, just redraw the activity + x = this.items.getBBox().x; + y = this.items.getBBox().y; + } + x = GeneralLib.snapToGrid(x); y = GeneralLib.snapToGrid(y); - } - - // create activity SVG elements - var curve = layout.activity.borderCurve, - width = layout.activity.width, - height = layout.activity.height, - bannerPath = 'M ' + (x + curve) + ' ' + (y + height) + ' q ' + -curve + ' 0 ' + -curve + ' ' + -curve + - ' v ' + (-height + 2 * curve) + ' q 0 ' + -curve + ' ' + curve + ' ' + -curve, - // by default the wide banner is displayed, - // but when there are learners in monitoring, the narrow one is shown instead - bannerWidePath = bannerPath + ' h ' + layout.activity.bannerWideWidth + ' v ' + height + ' z', - bannerNarrowPath = bannerPath + ' h ' + layout.activity.bannerNarrowWidth + ' v ' + height + ' z', - bannerWide = paper.path(bannerWidePath) - .addClass('svg-tool-banner-wide svg-tool-activity-category-' + layout.toolMetadata[this.learningLibraryID].activityCategoryID), - bannerNarrow = paper.path(bannerNarrowPath) - .addClass('svg-tool-banner-narrow svg-tool-activity-category-' + layout.toolMetadata[this.learningLibraryID].activityCategoryID), - shapePath = bannerPath + ' h ' + (width - 2 * curve) + ' q ' + curve + ' 0 ' + curve + ' ' + curve + - ' v ' + (height - 2 * curve) + ' q 0 ' + curve + ' ' + -curve + ' ' + curve + ' z', - shape = paper.path(shapePath) - .addClass('svg-tool-activity-background ' + (this.grouping ? '' : 'svg-shadow')), - shapeBorder = paper.path(shapePath) - .addClass('svg-tool-activity-border' + (this.requireGrouping ? '-require-grouping' : '')), - label = ActivityLib.getActivityTitle(this.title, x, y), - icon = ActivityLib.getActivityIcon(this.learningLibraryID); - - $(bannerNarrow.node).hide(); - this.items = paper.g(shape, bannerWide, bannerNarrow, shapeBorder, label); - - if (icon) { - icon.select('svg').attr({ - 'x' : x + 15, - 'y' : y + 20, - 'width' : '40px', - 'height': '40px' + + // either check what children are on canvas or use the priovided parameter + if (childActivities) { + this.childActivities = childActivities; + } + + var width = null, + height = null; + + if (this.childActivities && this.childActivities.length > 0) { + // draw one by one, vertically + var activityY = y + layout.conf.containerActivityPadding + 10, + allElements = Snap.set(), + optionalActivity = this, + box = this.items.getBBox(), + boxWidth = box.width; + $.each(this.childActivities, function(orderID){ + this.parentActivity = optionalActivity; + this.orderID = orderID + 1; + // for some reason, this.items.getBBox() can't be used here + var childBox = this.items.shape.getBBox(); + this.draw(x + Math.max(layout.conf.containerActivityPadding, (boxWidth - childBox.width)/2), activityY, true); + childBox = this.items.shape.getBBox(); + activityY = childBox.y2 + layout.conf.containerActivityChildrenPadding; + allElements.push(this.items.shape); + }); + // area containing all drawn child activities + box = allElements.getBBox(); + + width = box.x2 + layout.conf.containerActivityPadding - x; + height = box.y2 + layout.conf.containerActivityPadding - y; + + this.drawContainer(x, y, x + width, y + height, + layout.colors.optionalActivity, layout.colors.optionalActivityBorder, 0.5); + } else { + width = layout.conf.containerActivityEmptyWidth; + height = layout.conf.containerActivityEmptyHeight; + + this.drawContainer(x, y, x + width, y + height, + layout.colors.optionalActivity, layout.colors.optionalActivityBorder, 0.5); + } + + if (!isReadOnlyMode){ + // allow transition drawing and other activity behaviour + this.items.unmousedown().mousedown(HandlerActivityLib.activityMousedownHandler); + } + + this.items.data('parentObject', this); + this.items.addClass('svg-activity svg-activity-optional svg-shadow'); + // these are needed in monitoring + this.items.attr({ + 'uiid' : this.uiid, + 'data-x' : x, + 'data-y' : y, + 'data-width' : width, + 'data-height': height }); - this.items.add(icon); - } - - if (this.readOnly && !isReadOnlyMode) { - this.items.attr('filter', layout.conf.readOnlyFilter); - } - // these are needed in monitoring - this.items.attr({ - 'uiid' : this.uiid, - 'data-x' : x, - 'data-y' : y, - 'data-width' : width, - 'data-height': height - }); - this.items.addClass('svg-activity svg-activity-tool'); - this.items.shape = shape; - - if (this.grouping) { - ActivityLib.addGroupingEffect(this); - } - - ActivityLib.activityHandlersInit(this); - }, - - - /** - * Draws a Transition - */ - transition : function() { - if (this.items) { - this.items.remove(); - } - this.items = paper.g(); - - var isBranching = (this.fromActivity instanceof ActivityDefs.BranchingEdgeActivity && this.fromActivity.isStart) || - (this.toActivity instanceof ActivityDefs.BranchingEdgeActivity && !this.toActivity.isStart), - points = ActivityLib.findTransitionPoints(this.fromActivity, this.toActivity), - curve = layout.transition.curve, - straightLineThreshold = 2 * curve + 2; - - if (points) { - var path = Snap.format('M {startX} {startY}', points), - horizontalDelta = points.endX - points.startX, - verticalDelta = points.endY - points.startY; - - - // if activities are too close for curves, draw a straight line instead of bezier - if (isBranching || Math.abs(horizontalDelta) < straightLineThreshold || Math.abs(verticalDelta) < straightLineThreshold) { - path += Snap.format(' L {endX} {endY}', points); - points.arrowAngle = 90 + Math.atan2(points.endY - points.startY, points.endX - points.startX) * 180 / Math.PI; + }, + + + /** + * Draws a Parallel (double) Activity container + */ + parallelActivity : function(x, y) { + // if no new coordinates are given, just redraw the activity or give default value + if (x == undefined) { + x = this.items ? this.items.getBBox().x : 0; + } + if (y == undefined) { + y = this.items ? this.items.getBBox().y : 0; + } + + x = GeneralLib.snapToGrid(x); + y = GeneralLib.snapToGrid(y); + + var width = null, + height = null; + + if (this.childActivities && this.childActivities.length > 0) { + // draw one by one, vertically + var activityY = y + layout.conf.containerActivityPadding + 10, + allElements = Snap.set(), + optionalActivity = this; + $.each(this.childActivities, function(orderID){ + this.parentActivity = optionalActivity; + this.orderID = orderID + 1; + this.draw(x + layout.conf.containerActivityPadding, activityY, true); + activityY = this.items.getBBox().y2 + layout.conf.containerActivityChildrenPadding; + allElements.push(this.items.shape); + }); + // area containing all drawn child activities + var box = allElements.getBBox(); + + width = box.x2 + layout.conf.containerActivityPadding - x; + height = box.y2 + layout.conf.containerActivityPadding - y; + + this.drawContainer(x, y, x + width, y + height, + layout.colors.optionalActivity, layout.colors.optionalActivityBorder, 0.5); } else { - // adjust according to whether it is right/left and down/up - var horizontalModifier = horizontalDelta > 0 ? 1 : -1, - verticalModifier = verticalDelta > 0 ? 1 : -1; - - switch (points.direction) { - case 'up' : - case 'down' : - - // go to almost the middle of the activities - path += ' V ' + (points.middleY - verticalModifier * curve); - // first curve - path += ' q 0 ' + verticalModifier * curve + ' '; - path += horizontalModifier * curve + ' ' + verticalModifier * curve; - // straight long line - path += ' l ' + (points.endX - points.startX - 2 * horizontalModifier * curve) + ' 0'; - // second curve - path += ' q ' + horizontalModifier * curve + ' 0 ' + horizontalModifier * curve + ' ' + verticalModifier * curve; - - break; - - case 'left' : - case 'right' : - - path += ' H ' + (points.middleX - horizontalModifier * curve); - path += ' q ' + horizontalModifier * curve + ' 0 '; - path += horizontalModifier * curve + ' ' + verticalModifier * curve; - path += ' l 0 ' + (points.endY - points.startY - 2 * verticalModifier * curve); - path += ' q 0 ' + verticalModifier * curve + ' ' + horizontalModifier * curve + ' ' + verticalModifier * curve; - - break; - } + width = layout.conf.containerActivityEmptyWidth; + height = layout.conf.containerActivityEmptyHeight; - // finish the path - path += Snap.format(' L {endX} {endY}', points); + this.drawContainer(x, y, x + width, y + height, + layout.colors.optionalActivity, layout.colors.optionalActivityBorder, 0.5); } - - this.items.shape = paper.path(path).addClass('svg-transition'); - this.items.append(this.items.shape); - - var dot = paper.circle(points.startX, points.startY, layout.transition.dotRadius).addClass('svg-transition-element'), - side = layout.transition.arrowLength, - triangle = paper.polygon(0, 0, side, 2 * side, -side, 2 * side) - .addClass('svg-transition-element') - .transform(Snap.format('translate({endX} {endY}) rotate({arrowAngle})', points)); - this.items.append(dot); - this.items.append(triangle); - - - - this.items.attr('uiid', this.uiid); - if (this.title) { - // adjust X & Y, so the label does not overlap with the transition; - var label = paper.text(points.middleX, points.middleY, this.title) - .attr(layout.defaultTextAttributes); - labelBox = label.getBBox(), - labelBackground = paper.rect(labelBox.x, labelBox.y, labelBox.width, labelBox.height) - .attr({ - 'stroke' : 'none', - 'fill' : 'white' - }); - label = paper.g(label, labelBackground); - GeneralLib.toBack(labelBackground); - this.items.append(label); + + if (this.grouping) { + ActivityLib.addGroupingEffect(this); } - - GeneralLib.toBack(this.items); - - // region annotations could cover grouping effect - $.each(layout.regions, function(){ - GeneralLib.toBack(this.items); + + if (!isReadOnlyMode){ + // allow transition drawing and other activity behaviour + this.items.unmousedown().mousedown(HandlerActivityLib.activityMousedownHandler); + } + + this.items.data('parentObject', this); + this.items.addClass('svg-activity svg-activity-parallel svg-shadow'); + // these are needed in monitoring + this.items.attr({ + 'uiid' : this.uiid, + 'data-x' : x, + 'data-y' : y, + 'data-width' : width, + 'data-height': height }); - } - - this.items.data('parentObject', this); - - if (!isReadOnlyMode){ - this.items.attr('cursor', 'pointer') - .mousedown(HandlerTransitionLib.transitionMousedownHandler) - .click(HandlerLib.itemClickHandler); - } - } -}, + }, + /** + * Draws a Tool activity + */ + tool : function(x, y, skipSnapToGrid) { + if (x == undefined || y == undefined) { + // if no new coordinates are given, just redraw the activity + x = this.items.getBBox().x; + y = this.items.getBBox().y; + } -/** - * Contains utility methods for Activity manipulation. - */ -ActivityLib = { - - /** - * Make a new activity fully functional on canvas. - */ - activityHandlersInit : function(activity) { - activity.items.data('parentObject', activity); - - if (isReadOnlyMode) { - if (activitiesOnlySelectable) { - activity.items.attr('cursor', 'pointer') - .click(HandlerLib.itemClickHandler); + if (this.items) { + this.items.remove(); } - } else { - // set all the handlers - activity.items.attr('cursor', 'pointer') - .mousedown(HandlerActivityLib.activityMousedownHandler) - .click(HandlerActivityLib.activityClickHandler); - - if (activity instanceof ActivityDefs.BranchingEdgeActivity - && activity.branchingActivity.end) { - // highligh branching edges on hover - - activity.branchingActivity.start.items.hover(HandlerActivityLib.branchingEdgeMouseoverHandler, - HandlerActivityLib.branchingEdgeMouseoutHandler); - activity.branchingActivity.end.items.hover(HandlerActivityLib.branchingEdgeMouseoverHandler, - HandlerActivityLib.branchingEdgeMouseoutHandler); + + if (!skipSnapToGrid) { + x = GeneralLib.snapToGrid(x); + y = GeneralLib.snapToGrid(y); } - } - }, + // create activity SVG elements + var curve = layout.activity.borderCurve, + width = layout.activity.width, + height = layout.activity.height, + bannerPath = 'M ' + (x + curve) + ' ' + (y + height) + ' q ' + -curve + ' 0 ' + -curve + ' ' + -curve + + ' v ' + (-height + 2 * curve) + ' q 0 ' + -curve + ' ' + curve + ' ' + -curve, + // by default the wide banner is displayed, + // but when there are learners in monitoring, the narrow one is shown instead + bannerWidePath = bannerPath + ' h ' + layout.activity.bannerWideWidth + ' v ' + height + ' z', + bannerNarrowPath = bannerPath + ' h ' + layout.activity.bannerNarrowWidth + ' v ' + height + ' z', + bannerWide = paper.path(bannerWidePath) + .addClass('svg-tool-banner-wide svg-tool-activity-category-' + layout.toolMetadata[this.learningLibraryID].activityCategoryID), + bannerNarrow = paper.path(bannerNarrowPath) + .addClass('svg-tool-banner-narrow svg-tool-activity-category-' + layout.toolMetadata[this.learningLibraryID].activityCategoryID), + shapePath = bannerPath + ' h ' + (width - 2 * curve) + ' q ' + curve + ' 0 ' + curve + ' ' + curve + + ' v ' + (height - 2 * curve) + ' q 0 ' + curve + ' ' + -curve + ' ' + curve + ' z', + shape = paper.path(shapePath) + .addClass('svg-tool-activity-background ' + (this.grouping ? '' : 'svg-shadow')), + shapeBorder = paper.path(shapePath) + .addClass('svg-tool-activity-border' + (this.requireGrouping ? '-require-grouping' : '')), + label = ActivityLib.getActivityTitle(this.title, x, y), + icon = ActivityLib.getActivityIcon(this.learningLibraryID); - - - /** - * Adds branching activity when user draws an extra outbout transition from. - */ - addBranching : function(fromActivity, toActivity1) { - // find the other toActivity - var existingTransition = fromActivity.transitions.from[0], - toActivity2 = existingTransition.toActivity, - branchingEdgeStart = null, - branchingEdgeEnd = null, - convergeActivity1 = toActivity1, - convergeActivity2 = toActivity2; - // find converge activity of the new branch - while (convergeActivity1.transitions.from.length > 0) { - convergeActivity1 = convergeActivity1.transitions.from[0].toActivity; - }; - - if (toActivity2 instanceof ActivityDefs.BranchingEdgeActivity && toActivity2.isStart) { - // there is already a branching activity, reuse existing items - branchingEdgeStart = toActivity2; - branchingEdgeEnd = toActivity2.branchingActivity.end; - } else { - // add new branching - ActivityLib.removeTransition(existingTransition); - - // calculate position of branching point - var branchPoints1 = ActivityLib.findTransitionPoints(fromActivity, toActivity1), - branchPoints2 = ActivityLib.findTransitionPoints(fromActivity, toActivity2), - branchEdgeStartX = branchPoints1.middleX + (branchPoints2.middleX - branchPoints1.middleX)/2, - branchEdgeStartY = branchPoints1.middleY + (branchPoints2.middleY - branchPoints1.middleY)/2, - branchingEdgeStart = new ActivityDefs.BranchingEdgeActivity(null, null, branchEdgeStartX, - branchEdgeStartY, null, false, null, null); - layout.activities.push(branchingEdgeStart); - - // find last activities in subsequences and make an converge point between them - while (convergeActivity2.transitions.from.length > 0) { - convergeActivity2 = convergeActivity2.transitions.from[0].toActivity; - }; + $(bannerNarrow.node).hide(); + this.items = paper.g(shape, bannerWide, bannerNarrow, shapeBorder, label); - var convergePoints = ActivityLib.findTransitionPoints(convergeActivity1, convergeActivity2), - branchingEdgeEnd = new ActivityDefs.BranchingEdgeActivity(null, null, convergePoints.middleX, - convergePoints.middleY, null, false, null, branchingEdgeStart.branchingActivity); - layout.activities.push(branchingEdgeEnd); - - // draw all required transitions - ActivityLib.addTransition(fromActivity, branchingEdgeStart); - ActivityLib.addTransition(branchingEdgeStart, toActivity2); - ActivityLib.addTransition(convergeActivity2, branchingEdgeEnd); - } + if (icon) { + icon.select('svg').attr({ + 'x' : x + 15, + 'y' : y + 20, + 'width' : '40px', + 'height': '40px' + }); + this.items.add(icon); + } - ActivityLib.addTransition(branchingEdgeStart, toActivity1); - ActivityLib.addTransition(convergeActivity1, branchingEdgeEnd); - GeneralLib.setModified(true); - }, - + if (this.readOnly && !isReadOnlyMode) { + this.items.attr('filter', layout.conf.readOnlyFilter); + } + // these are needed in monitoring + this.items.attr({ + 'uiid' : this.uiid, + 'data-x' : x, + 'data-y' : y, + 'data-width' : width, + 'data-height': height + }); + this.items.addClass('svg-activity svg-activity-tool'); + this.items.shape = shape; - - /** - * Adds visual grouping effect on an activity. - */ - addGroupingEffect : function(activity) { - // do not draw twice if it already exists - if (!activity.items.groupingEffect) { - var shape = activity.items.shape, - activityBox = activity.items.getBBox(); - - activity.items.groupingEffect = paper.rect( - activityBox.x + layout.conf.groupingEffectPadding, - activityBox.y + layout.conf.groupingEffectPadding, - activityBox.width, - activityBox.height, - 5, 5) - .addClass('svg-tool-activity-border svg-tool-activity-border-grouped svg-shadow'); - - activity.items.prepend(activity.items.groupingEffect); - - // region annotations could cover grouping effect - $.each(layout.regions, function(){ + if (this.grouping) { + ActivityLib.addGroupingEffect(this); + } + + ActivityLib.activityHandlersInit(this); + }, + + + /** + * Draws a Transition + */ + transition : function() { + if (this.items) { + this.items.remove(); + } + this.items = paper.g(); + + var isBranching = (this.fromActivity instanceof ActivityDefs.BranchingEdgeActivity && this.fromActivity.isStart) || + (this.toActivity instanceof ActivityDefs.BranchingEdgeActivity && !this.toActivity.isStart), + points = ActivityLib.findTransitionPoints(this.fromActivity, this.toActivity), + curve = layout.transition.curve, + straightLineThreshold = 2 * curve + 2; + + if (points) { + var path = Snap.format('M {startX} {startY}', points), + horizontalDelta = points.endX - points.startX, + verticalDelta = points.endY - points.startY; + + + // if activities are too close for curves, draw a straight line instead of bezier + if (isBranching || Math.abs(horizontalDelta) < straightLineThreshold || Math.abs(verticalDelta) < straightLineThreshold) { + path += Snap.format(' L {endX} {endY}', points); + points.arrowAngle = 90 + Math.atan2(points.endY - points.startY, points.endX - points.startX) * 180 / Math.PI; + } else { + // adjust according to whether it is right/left and down/up + var horizontalModifier = horizontalDelta > 0 ? 1 : -1, + verticalModifier = verticalDelta > 0 ? 1 : -1; + + switch (points.direction) { + case 'up' : + case 'down' : + + // go to almost the middle of the activities + path += ' V ' + (points.middleY - verticalModifier * curve); + // first curve + path += ' q 0 ' + verticalModifier * curve + ' '; + path += horizontalModifier * curve + ' ' + verticalModifier * curve; + // straight long line + path += ' l ' + (points.endX - points.startX - 2 * horizontalModifier * curve) + ' 0'; + // second curve + path += ' q ' + horizontalModifier * curve + ' 0 ' + horizontalModifier * curve + ' ' + verticalModifier * curve; + + break; + + case 'left' : + case 'right' : + + path += ' H ' + (points.middleX - horizontalModifier * curve); + path += ' q ' + horizontalModifier * curve + ' 0 '; + path += horizontalModifier * curve + ' ' + verticalModifier * curve; + path += ' l 0 ' + (points.endY - points.startY - 2 * verticalModifier * curve); + path += ' q 0 ' + verticalModifier * curve + ' ' + horizontalModifier * curve + ' ' + verticalModifier * curve; + + break; + } + + // finish the path + path += Snap.format(' L {endX} {endY}', points); + } + + this.items.shape = paper.path(path).addClass('svg-transition'); + this.items.append(this.items.shape); + + var dot = paper.circle(points.startX, points.startY, layout.transition.dotRadius).addClass('svg-transition-element'), + side = layout.transition.arrowLength, + triangle = paper.polygon(0, 0, side, 2 * side, -side, 2 * side) + .addClass('svg-transition-element') + .transform(Snap.format('translate({endX} {endY}) rotate({arrowAngle})', points)); + this.items.append(dot); + this.items.append(triangle); + + + + this.items.attr('uiid', this.uiid); + if (this.title) { + // adjust X & Y, so the label does not overlap with the transition; + var label = paper.text(points.middleX, points.middleY, this.title) + .attr(layout.defaultTextAttributes); + labelBox = label.getBBox(), + labelBackground = paper.rect(labelBox.x, labelBox.y, labelBox.width, labelBox.height) + .attr({ + 'stroke' : 'none', + 'fill' : 'white' + }); + label = paper.g(label, labelBackground); + GeneralLib.toBack(labelBackground); + this.items.append(label); + } + GeneralLib.toBack(this.items); - }); + + // region annotations could cover grouping effect + $.each(layout.regions, function(){ + GeneralLib.toBack(this.items); + }); + } + + this.items.data('parentObject', this); + + if (!isReadOnlyMode){ + this.items.attr('cursor', 'pointer') + .mousedown(HandlerTransitionLib.transitionMousedownHandler) + .click(HandlerLib.itemClickHandler); + } } }, - - + + + /** - * Adds visual select effect around an activity. + * Contains utility methods for Activity manipulation. */ - addSelectEffect : function (object, markSelected) { - // do not draw twice - if (!object.items.selectEffect) { - // different effects for different types of objects - if (object instanceof DecorationDefs.Region) { - object.items.shape.attr({ - 'stroke' : layout.colors.selectEffect, - 'stroke-dasharray' : '5,3' - }); - object.items.selectEffect = true; - - if (!isReadOnlyMode) { - object.items.resizeButton.attr('display', null); - GeneralLib.toFront(object.items.resizeButton); - // also select encapsulated activities - var childActivities = DecorationLib.getChildActivities(object.items.shape); - if (childActivities.length > 0) { - object.items.fitButton.attr('display', null); - - $.each(childActivities, function(){ - if (!this.parentActivity || !(this.parentActivity instanceof DecorationDefs.Container)) { - ActivityLib.addSelectEffect(this, false); - } - }); - } + ActivityLib = { + + /** + * Make a new activity fully functional on canvas. + */ + activityHandlersInit : function(activity) { + activity.items.data('parentObject', activity); + + if (isReadOnlyMode) { + if (activitiesOnlySelectable) { + activity.items.attr('cursor', 'pointer') + .click(HandlerLib.itemClickHandler); } - } else if (object instanceof ActivityDefs.Transition) { - // show only if Transition is selectable, i.e. is a branch, has a title - if (object.loadPropertiesDialogContent) { - object.items.attr({ - 'stroke' : layout.colors.selectEffect, - 'fill' : layout.colors.selectEffect - }); - - object.items.selectEffect = true; + } else { + // set all the handlers + activity.items.attr('cursor', 'pointer') + .mousedown(HandlerActivityLib.activityMousedownHandler) + .click(HandlerActivityLib.activityClickHandler); + + if (activity instanceof ActivityDefs.BranchingEdgeActivity + && activity.branchingActivity.end) { + // highligh branching edges on hover + + activity.branchingActivity.start.items.hover(HandlerActivityLib.branchingEdgeMouseoverHandler, + HandlerActivityLib.branchingEdgeMouseoutHandler); + activity.branchingActivity.end.items.hover(HandlerActivityLib.branchingEdgeMouseoverHandler, + HandlerActivityLib.branchingEdgeMouseoutHandler); } + } + + }, + + + + /** + * Adds branching activity when user draws an extra outbout transition from. + */ + addBranching : function(fromActivity, toActivity1) { + // find the other toActivity + var existingTransition = fromActivity.transitions.from[0], + toActivity2 = existingTransition.toActivity, + branchingEdgeStart = null, + branchingEdgeEnd = null, + convergeActivity1 = toActivity1, + convergeActivity2 = toActivity2; + // find converge activity of the new branch + while (convergeActivity1.transitions.from.length > 0) { + convergeActivity1 = convergeActivity1.transitions.from[0].toActivity; + }; + + if (toActivity2 instanceof ActivityDefs.BranchingEdgeActivity && toActivity2.isStart) { + // there is already a branching activity, reuse existing items + branchingEdgeStart = toActivity2; + branchingEdgeEnd = toActivity2.branchingActivity.end; } else { - // this goes for ActivityDefs and Labels - var box = object.items.getBBox(); - - // a simple rectange a bit wider than the actual activity boundaries - object.items.selectEffect = paper.path(Snap.format('M {x} {y} h {width} v {height} h -{width} z', - { - 'x' : box.x - layout.conf.selectEffectPadding, - 'y' : box.y - layout.conf.selectEffectPadding, - 'width' : box.width + 2*layout.conf.selectEffectPadding, - 'height' : box.height + 2*layout.conf.selectEffectPadding - })) - .attr({ - 'stroke' : layout.colors.selectEffect, - 'stroke-dasharray' : '5,3', - 'fill' : 'none' - }); - - // if it's "import part" select children activities - if (activitiesOnlySelectable) { - if (object instanceof ActivityDefs.BranchingEdgeActivity) { - if (object.isStart){ - ActivityLib.addSelectEffect(object.branchingActivity.end); - - $.each(object.branchingActivity.branches, function(){ - var transition = this.transitionFrom; - while (transition) { - var activity = transition.toActivity; - if (activity instanceof ActivityDefs.BranchingEdgeActivity) { - return true; - } - ActivityLib.addSelectEffect(activity); - transition = activity.transitions.from.length > 0 ? activity.transitions.from[0] : null; + // add new branching + ActivityLib.removeTransition(existingTransition); + + // calculate position of branching point + var branchPoints1 = ActivityLib.findTransitionPoints(fromActivity, toActivity1), + branchPoints2 = ActivityLib.findTransitionPoints(fromActivity, toActivity2), + branchEdgeStartX = branchPoints1.middleX + (branchPoints2.middleX - branchPoints1.middleX)/2, + branchEdgeStartY = branchPoints1.middleY + (branchPoints2.middleY - branchPoints1.middleY)/2, + branchingEdgeStart = new ActivityDefs.BranchingEdgeActivity(null, null, branchEdgeStartX, + branchEdgeStartY, null, false, null, null); + layout.activities.push(branchingEdgeStart); + + // find last activities in subsequences and make an converge point between them + while (convergeActivity2.transitions.from.length > 0) { + convergeActivity2 = convergeActivity2.transitions.from[0].toActivity; + }; + + var convergePoints = ActivityLib.findTransitionPoints(convergeActivity1, convergeActivity2), + branchingEdgeEnd = new ActivityDefs.BranchingEdgeActivity(null, null, convergePoints.middleX, + convergePoints.middleY, null, false, null, branchingEdgeStart.branchingActivity); + layout.activities.push(branchingEdgeEnd); + + // draw all required transitions + ActivityLib.addTransition(fromActivity, branchingEdgeStart); + ActivityLib.addTransition(branchingEdgeStart, toActivity2); + ActivityLib.addTransition(convergeActivity2, branchingEdgeEnd); + } + + ActivityLib.addTransition(branchingEdgeStart, toActivity1); + ActivityLib.addTransition(convergeActivity1, branchingEdgeEnd); + GeneralLib.setModified(true); + }, + + + + /** + * Adds visual grouping effect on an activity. + */ + addGroupingEffect : function(activity) { + // do not draw twice if it already exists + if (!activity.items.groupingEffect) { + var shape = activity.items.shape, + activityBox = activity.items.getBBox(); + + activity.items.groupingEffect = paper.rect( + activityBox.x + layout.conf.groupingEffectPadding, + activityBox.y + layout.conf.groupingEffectPadding, + activityBox.width, + activityBox.height, + 5, 5) + .addClass('svg-tool-activity-border svg-tool-activity-border-grouped svg-shadow'); + + activity.items.prepend(activity.items.groupingEffect); + + // region annotations could cover grouping effect + $.each(layout.regions, function(){ + GeneralLib.toBack(this.items); + }); + } + }, + + + /** + * Adds visual select effect around an activity. + */ + addSelectEffect : function (object, markSelected) { + // do not draw twice + if (!object.items.selectEffect) { + // different effects for different types of objects + if (object instanceof DecorationDefs.Region) { + object.items.shape.attr({ + 'stroke' : layout.colors.selectEffect, + 'stroke-dasharray' : '5,3' + }); + object.items.selectEffect = true; + + if (!isReadOnlyMode) { + object.items.resizeButton.attr('display', null); + GeneralLib.toFront(object.items.resizeButton); + // also select encapsulated activities + var childActivities = DecorationLib.getChildActivities(object.items.shape); + if (childActivities.length > 0) { + object.items.fitButton.attr('display', null); + + $.each(childActivities, function(){ + if (!this.parentActivity || !(this.parentActivity instanceof DecorationDefs.Container)) { + ActivityLib.addSelectEffect(this, false); } }); - } else { - ActivityLib.addSelectEffect(object.branchingActivity.start); } - } else if (object instanceof DecorationDefs.Container){ - $.each(object.childActivities, function(){ - ActivityLib.addSelectEffect(this); + } + } else if (object instanceof ActivityDefs.Transition) { + // show only if Transition is selectable, i.e. is a branch, has a title + if (object.loadPropertiesDialogContent) { + object.items.attr({ + 'stroke' : layout.colors.selectEffect, + 'fill' : layout.colors.selectEffect }); + + object.items.selectEffect = true; } - } - } - - // make it officially marked? - if (markSelected && object.items.selectEffect){ - layout.selectedObject = object; - // show the properties dialog for the selected object - if (object.loadPropertiesDialogContent) { - PropertyLib.openPropertiesDialog(object); - } - - /* This will become useful if weights dialog get non-modal - if (object instanceof ActivityDefs.ToolActivity - && object.gradebookToolOutputDefinitionName - && layout.weightsDialog.hasClass('in')) { - $('tbody tr', layout.weightsDialog).each(function(){ - if ($(this).data('activity') == object) { - $(this).addClass('selected'); - } else { - $(this).removeClass('selected'); + } else { + // this goes for ActivityDefs and Labels + var box = object.items.getBBox(); + + // a simple rectange a bit wider than the actual activity boundaries + object.items.selectEffect = paper.path(Snap.format('M {x} {y} h {width} v {height} h -{width} z', + { + 'x' : box.x - layout.conf.selectEffectPadding, + 'y' : box.y - layout.conf.selectEffectPadding, + 'width' : box.width + 2*layout.conf.selectEffectPadding, + 'height' : box.height + 2*layout.conf.selectEffectPadding + })) + .attr({ + 'stroke' : layout.colors.selectEffect, + 'stroke-dasharray' : '5,3', + 'fill' : 'none' + }); + + // if it's "import part" select children activities + if (activitiesOnlySelectable) { + if (object instanceof ActivityDefs.BranchingEdgeActivity) { + if (object.isStart){ + ActivityLib.addSelectEffect(object.branchingActivity.end); + + $.each(object.branchingActivity.branches, function(){ + var transition = this.transitionFrom; + while (transition) { + var activity = transition.toActivity; + if (activity instanceof ActivityDefs.BranchingEdgeActivity) { + return true; + } + ActivityLib.addSelectEffect(activity); + transition = activity.transitions.from.length > 0 ? activity.transitions.from[0] : null; + } + }); + } else { + ActivityLib.addSelectEffect(object.branchingActivity.start); + } + } else if (object instanceof DecorationDefs.Container){ + $.each(object.childActivities, function(){ + ActivityLib.addSelectEffect(this); + }); } - }); + } } - */ + + // make it officially marked? + if (markSelected && object.items.selectEffect){ + layout.selectedObject = object; + // show the properties dialog for the selected object + if (object.loadPropertiesDialogContent) { + PropertyLib.openPropertiesDialog(object); + } + + /* This will become useful if weights dialog get non-modal + if (object instanceof ActivityDefs.ToolActivity + && object.gradebookToolOutputDefinitionName + && layout.weightsDialog.hasClass('in')) { + $('tbody tr', layout.weightsDialog).each(function(){ + if ($(this).data('activity') == object) { + $(this).addClass('selected'); + } else { + $(this).removeClass('selected'); + } + }); + } + */ + } } - } - }, - - - /** - * Draws a transition between two activities. - */ - addTransition : function(fromActivity, toActivity, redraw, id, uiid, branchData) { - // check if a branching's start does not connect with another branching's end - if (fromActivity instanceof ActivityDefs.BranchingEdgeActivity + }, + + + /** + * Draws a transition between two activities. + */ + addTransition : function(fromActivity, toActivity, redraw, id, uiid, branchData) { + // check if a branching's start does not connect with another branching's end + if (fromActivity instanceof ActivityDefs.BranchingEdgeActivity && toActivity instanceof ActivityDefs.BranchingEdgeActivity && fromActivity.isStart && !toActivity.isStart && fromActivity.branchingActivity != toActivity.branchingActivity) { - layout.infoDialog.data('show')(LABELS.CROSS_BRANCHING_ERROR); - return; - } - - // if a child activity was detected, use the parent activity as the target - if (toActivity.parentActivity && toActivity.parentActivity instanceof DecorationDefs.Container){ - toActivity = toActivity.parentActivity; - } - if (fromActivity.parentActivity && fromActivity.parentActivity instanceof DecorationDefs.Container){ - fromActivity = fromActivity.parentActivity; - } - // no transitions to/from support activities - if (toActivity instanceof ActivityDefs.FloatingActivity - || fromActivity instanceof ActivityDefs.FloatingActivity){ - layout.infoDialog.data('show')(LABELS.SUPPORT_TRANSITION_ERROR); - return; - } - - // only converge points are allowed to have few inbound transitions - if (!redraw - && toActivity.transitions.to.length > 0 - && !(toActivity instanceof ActivityDefs.BranchingEdgeActivity && !toActivity.isStart)) { - layout.infoDialog.data('show')(LABELS.TRANSITION_TO_EXISTS_ERROR); - return; - } + layout.infoDialog.data('show')(LABELS.CROSS_BRANCHING_ERROR); + return; + } - // check for circular sequences - var activity = fromActivity; - do { - if (activity.transitions && activity.transitions.to.length > 0) { - activity = activity.transitions.to[0].fromActivity; - } else if (activity.branchingActivity && !activity.isStart) { - activity = activity.branchingActivity.start; - } else { - activity = null; + // if a child activity was detected, use the parent activity as the target + if (toActivity.parentActivity && toActivity.parentActivity instanceof DecorationDefs.Container){ + toActivity = toActivity.parentActivity; } - - if (toActivity == activity) { - layout.infoDialog.data('show')(LABELS.CIRCULAR_SEQUENCE_ERROR); + if (fromActivity.parentActivity && fromActivity.parentActivity instanceof DecorationDefs.Container){ + fromActivity = fromActivity.parentActivity; + } + // no transitions to/from support activities + if (toActivity instanceof ActivityDefs.FloatingActivity + || fromActivity instanceof ActivityDefs.FloatingActivity){ + layout.infoDialog.data('show')(LABELS.SUPPORT_TRANSITION_ERROR); return; } - } while (activity); - // user chose to create outbound transition from an activity that already has one - if (!redraw - && fromActivity.transitions.from.length > 0 - && !(fromActivity instanceof ActivityDefs.BranchingEdgeActivity && fromActivity.isStart)) { - if (confirm(LABELS.BRANCHING_CREATE_CONFIRM)) { - ActivityLib.addBranching(fromActivity, toActivity); + // only converge points are allowed to have few inbound transitions + if (!redraw + && toActivity.transitions.to.length > 0 + && !(toActivity instanceof ActivityDefs.BranchingEdgeActivity && !toActivity.isStart)) { + layout.infoDialog.data('show')(LABELS.TRANSITION_TO_EXISTS_ERROR); + return; } - return; - } - - // start building the transition - - // branchData can be either an existing branch or a title for the new branch - var branch = branchData && branchData instanceof ActivityDefs.BranchActivity ? branchData : null, - transition = null; - // remove the existing transition - $.each(fromActivity.transitions.from, function(index) { - if (this.toActivity == toActivity) { - id = this.id; - uiid = this.uiid; - transition = this; - if (!branch){ - branch = this.branch; + + // check for circular sequences + var activity = fromActivity; + do { + if (activity.transitions && activity.transitions.to.length > 0) { + activity = activity.transitions.to[0].fromActivity; + } else if (activity.branchingActivity && !activity.isStart) { + activity = activity.branchingActivity.start; + } else { + activity = null; } - return false; + if (toActivity == activity) { + layout.infoDialog.data('show')(LABELS.CIRCULAR_SEQUENCE_ERROR); + return; + } + } while (activity); + + // user chose to create outbound transition from an activity that already has one + if (!redraw + && fromActivity.transitions.from.length > 0 + && !(fromActivity instanceof ActivityDefs.BranchingEdgeActivity && fromActivity.isStart)) { + if (confirm(LABELS.BRANCHING_CREATE_CONFIRM)) { + ActivityLib.addBranching(fromActivity, toActivity); + } + return; } - }); - - if (!branch && fromActivity instanceof ActivityDefs.BranchingEdgeActivity && fromActivity.isStart) { - // if a title was provided, try to find the branch based on this information - $.each(fromActivity.branchingActivity.branches, function(){ - if (branchData == this.title) { - branch = this; + + // start building the transition + + // branchData can be either an existing branch or a title for the new branch + var branch = branchData && branchData instanceof ActivityDefs.BranchActivity ? branchData : null, + transition = null; + // remove the existing transition + $.each(fromActivity.transitions.from, function(index) { + if (this.toActivity == toActivity) { + id = this.id; + uiid = this.uiid; + transition = this; + if (!branch){ + branch = this.branch; + } + return false; } }); - if (!branch) { - // create a new branch - branch = new ActivityDefs.BranchActivity(null, null, branchData, fromActivity.branchingActivity, false); + + if (!branch && fromActivity instanceof ActivityDefs.BranchingEdgeActivity && fromActivity.isStart) { + // if a title was provided, try to find the branch based on this information + $.each(fromActivity.branchingActivity.branches, function(){ + if (branchData == this.title) { + branch = this; + return false; + } + }); + if (!branch) { + // create a new branch + branch = new ActivityDefs.BranchActivity(null, null, branchData, fromActivity.branchingActivity, false); + } } - } - - if (transition) { - ActivityLib.removeTransition(transition, redraw); - } - - // finally add the new transition - transition = new ActivityDefs.Transition(id, uiid, fromActivity, toActivity, - branch ? branch.title : null); - if (branch) { - // set the corresponding branch (again) - branch.transitionFrom = transition; - transition.branch = branch; - fromActivity.branchingActivity.branches.push(branch); - if (fromActivity.branchingActivity.branches.length == 1) { - branch.defaultBranch = true; + if (transition) { + ActivityLib.removeTransition(transition, redraw); } - } - - - // after adding the transition, check for self-nested branching - activity = fromActivity; - var branchingActivity = null; - // find the top-most enveloping branching activity, if any - do { - if (activity.transitions && activity.transitions.to.length > 0) { - activity = activity.transitions.to[0].fromActivity; - - if (activity instanceof ActivityDefs.BranchingEdgeActivity) { - if (activity.isStart) { - // found the top branching the activity belongs to - branchingActivity = activity.branchingActivity; - } else { - // jump over nested branching - activity = activity.branchingActivity.start; - } + + // finally add the new transition + transition = new ActivityDefs.Transition(id, uiid, fromActivity, toActivity, + branch ? branch.title : null); + + if (branch) { + // set the corresponding branch (again) + branch.transitionFrom = transition; + transition.branch = branch; + fromActivity.branchingActivity.branches.push(branch); + if (fromActivity.branchingActivity.branches.length == 1) { + branch.defaultBranch = true; } - } else { - activity = null; } - } while (activity); - - - if (branchingActivity) { - // look for all nested branchings - var nestedBranchings = ActivityLib.findNestedBranching(branchingActivity); - // check each of them - $.each(nestedBranchings, function(){ - var branching = this; - // check if one branching's end does not match with another branching's start - $.each(branching.end.transitions.to, function(){ - // crawl from end to start - var activity = this.fromActivity; - while (activity) { - if (activity instanceof ActivityDefs.BranchingEdgeActivity) { - if (activity.isStart) { - // this branching's end matches with its own start, all OK - if (activity.branchingActivity == branching) { - break; - } - // this branching's end does not match with own start, error - layout.infoDialog.data('show')(LABELS.CROSS_BRANCHING_ERROR); - // remove the just added transition - ActivityLib.removeTransition(transition); - // tell the outer iteration loop to quit - transition = null; - return false; - } - // a nested branching encountered when crawling, just jump over it + + + // after adding the transition, check for self-nested branching + activity = fromActivity; + var branchingActivity = null; + // find the top-most enveloping branching activity, if any + do { + if (activity.transitions && activity.transitions.to.length > 0) { + activity = activity.transitions.to[0].fromActivity; + + if (activity instanceof ActivityDefs.BranchingEdgeActivity) { + if (activity.isStart) { + // found the top branching the activity belongs to + branchingActivity = activity.branchingActivity; + } else { + // jump over nested branching activity = activity.branchingActivity.start; } - // keep crawling - if (activity.transitions && activity.transitions.to.length > 0) { - activity = activity.transitions.to[0].fromActivity; - } else { - activity = null; - } } - }); - - if (!transition) { - // there was an error, do not carry on - return false; + } else { + activity = null; } - }); - } - - GeneralLib.setModified(true); - return transition; - }, - - adjustTransitionPoint : function(bottomLimit, topLimit, target) { - bottomLimit = Math.round(bottomLimit); - topLimit = Math.round(topLimit); - target = Math.round(target); - // find a good point inside the grid, then make sure it is within bounds - return Math.max(bottomLimit + layout.transition.adjustStep, Math.min(topLimit - layout.transition.adjustStep, - Math.floor(target / layout.transition.adjustStep) * layout.transition.adjustStep)); - }, + } while (activity); - /** - * It is run from authoringConfirm.jsp - * It closes the dialog with activity authoring - */ - closeActivityAuthoring : function(dialogID){ - $("#" + dialogID).off('hide.bs.modal').on('hide.bs.modal', function(){ - $('iframe', this).attr('src', null); - }).modal('hide'); - }, - - - /** - * Drop the dragged activity on the canvas. - */ - dropActivity : function(activity, x, y) { - if (!(activity instanceof ActivityDefs.OptionalActivity || activity instanceof ActivityDefs.FloatingActivity)) { - // check if it was removed from an Optional or Floating Activity - if (activity.parentActivity && activity.parentActivity instanceof DecorationDefs.Container) { - var existingChildActivities = activity.parentActivity.childActivities, - childActivities = DecorationLib.getChildActivities(activity.parentActivity.items.shape); - if ($.inArray(activity, childActivities) == -1) { - if (activity.readOnly || activity.parentActivity.readOnly) { - // put the activity back - activity.parentActivity.childActivities = existingChildActivities; - - layout.infoDialog.data('show')(LABELS.LIVEEDIT_READONLY_MOVE_PARENT_ERROR); + + if (branchingActivity) { + // look for all nested branchings + var nestedBranchings = ActivityLib.findNestedBranching(branchingActivity); + // check each of them + $.each(nestedBranchings, function(){ + var branching = this; + // check if one branching's end does not match with another branching's start + $.each(branching.end.transitions.to, function(){ + // crawl from end to start + var activity = this.fromActivity; + while (activity) { + if (activity instanceof ActivityDefs.BranchingEdgeActivity) { + if (activity.isStart) { + // this branching's end matches with its own start, all OK + if (activity.branchingActivity == branching) { + break; + } + // this branching's end does not match with own start, error + layout.infoDialog.data('show')(LABELS.CROSS_BRANCHING_ERROR); + // remove the just added transition + ActivityLib.removeTransition(transition); + // tell the outer iteration loop to quit + transition = null; + return false; + } + // a nested branching encountered when crawling, just jump over it + activity = activity.branchingActivity.start; + } + // keep crawling + if (activity.transitions && activity.transitions.to.length > 0) { + activity = activity.transitions.to[0].fromActivity; + } else { + activity = null; + } + } + }); + + if (!transition) { + // there was an error, do not carry on return false; } - - activity.parentActivity.draw(); - ActivityLib.redrawTransitions(activity.parentActivity); - activity.parentActivity = null; - } - } - - // check if it was added to an Optional or Floating Activity - var container = layout.floatingActivity - && Snap.path.isPointInsideBBox(layout.floatingActivity.items.getBBox(),x,y) - ? layout.floatingActivity : null; - if (!container) { - $.each(layout.activities, function(){ - if (this instanceof ActivityDefs.OptionalActivity - && Snap.path.isPointInsideBBox(this.items.getBBox(),x,y)) { - container = this; - return false; - } }); } - if (container) { - // system activities can not be added to optional and support activities - if (activity instanceof ActivityDefs.GateActivity - || activity instanceof ActivityDefs.GroupingActivity - || activity instanceof ActivityDefs.BranchingEdgeActivity){ - layout.infoDialog.data('show')(LABELS.ACTIVITY_IN_CONTAINER_ERROR); - return false; + + GeneralLib.setModified(true); + return transition; + }, + + adjustTransitionPoint : function(bottomLimit, topLimit, target) { + bottomLimit = Math.round(bottomLimit); + topLimit = Math.round(topLimit); + target = Math.round(target); + // find a good point inside the grid, then make sure it is within bounds + return Math.max(bottomLimit + layout.transition.adjustStep, Math.min(topLimit - layout.transition.adjustStep, + Math.floor(target / layout.transition.adjustStep) * layout.transition.adjustStep)); + }, + + /** + * It is run from authoringConfirm.jsp + * It closes the dialog with activity authoring + */ + closeActivityAuthoring : function(dialogID){ + $("#" + dialogID).off('hide.bs.modal').on('hide.bs.modal', function(){ + $('iframe', this).attr('src', null); + }).modal('hide'); + }, + + + /** + * Drop the dragged activity on the canvas. + */ + dropActivity : function(activity, x, y) { + if (!(activity instanceof ActivityDefs.OptionalActivity || activity instanceof ActivityDefs.FloatingActivity)) { + // check if it was removed from an Optional or Floating Activity + if (activity.parentActivity && activity.parentActivity instanceof DecorationDefs.Container) { + var existingChildActivities = activity.parentActivity.childActivities, + childActivities = DecorationLib.getChildActivities(activity.parentActivity.items.shape); + if ($.inArray(activity, childActivities) == -1) { + if (activity.readOnly || activity.parentActivity.readOnly) { + // put the activity back + activity.parentActivity.childActivities = existingChildActivities; + + layout.infoDialog.data('show')(LABELS.LIVEEDIT_READONLY_MOVE_PARENT_ERROR); + return false; + } + + activity.parentActivity.draw(); + ActivityLib.redrawTransitions(activity.parentActivity); + activity.parentActivity = null; + } } - if (activity.readOnly || container.readOnly) { - layout.infoDialog.data('show')(LABELS.LIVEEDIT_READONLY_ACTIVITY_ERROR); - return false; - } - - $.each(activity.transitions.from, function(){ - ActivityLib.removeTransition(this); - }); - $.each(activity.transitions.to, function(){ - ActivityLib.removeTransition(this); - }); - // for properties dialog to reload - ActivityLib.removeSelectEffect(container); - - // check if the activity is already detected by the container - // if not, add it manually - var childActivities = DecorationLib.getChildActivities(container.items.shape); - if ($.inArray(activity, container.childActivities) == -1) { - childActivities.push(activity); + // check if it was added to an Optional or Floating Activity + var container = layout.floatingActivity + && Snap.path.isPointInsideBBox(layout.floatingActivity.items.getBBox(),x,y) + ? layout.floatingActivity : null; + if (!container) { + $.each(layout.activities, function(){ + if (this instanceof ActivityDefs.OptionalActivity + && Snap.path.isPointInsideBBox(this.items.getBBox(),x,y)) { + container = this; + return false; + } + }); } - container.draw(null, null, null, null, childActivities); - ActivityLib.redrawTransitions(container); - } - } - - ActivityLib.redrawTransitions(activity); - - $.each(layout.regions, function(){ - // redraw all annotation regions so they are pushed to back - this.draw(); - }); - - GeneralLib.setModified(true); - return true; - }, - - - findNestedBranching : function(branchingActivity) { - var nestedBranching = []; - $.each(branchingActivity.branches, function(){ - var activity = this.transitionFrom.toActivity; - while (activity) { - if (activity instanceof ActivityDefs.BranchingEdgeActivity) { - if (activity.branchingActivity == branchingActivity){ - break; + + if (container) { + // system activities can not be added to optional and support activities + if (activity instanceof ActivityDefs.GateActivity + || activity instanceof ActivityDefs.GroupingActivity + || activity instanceof ActivityDefs.BranchingEdgeActivity){ + layout.infoDialog.data('show')(LABELS.ACTIVITY_IN_CONTAINER_ERROR); + return false; } - if (nestedBranching.indexOf(activity.branchingActivity) == -1) { - nestedBranching.push(activity.branchingActivity); + if (activity.readOnly || container.readOnly) { + layout.infoDialog.data('show')(LABELS.LIVEEDIT_READONLY_ACTIVITY_ERROR); + return false; } - if (activity.isStart) { - nestedBranching = nestedBranching.concat(ActivityLib.findNestedBranching(activity.branchingActivity)); - activity = activity.branchingActivity.end; + + $.each(activity.transitions.from, function(){ + ActivityLib.removeTransition(this); + }); + $.each(activity.transitions.to, function(){ + ActivityLib.removeTransition(this); + }); + + // for properties dialog to reload + ActivityLib.removeSelectEffect(container); + + // check if the activity is already detected by the container + // if not, add it manually + var childActivities = DecorationLib.getChildActivities(container.items.shape); + if ($.inArray(activity, container.childActivities) == -1) { + childActivities.push(activity); } + container.draw(null, null, null, null, childActivities); + ActivityLib.redrawTransitions(container); + } else if (activity instanceof ActivityDefs.ToolActivity){ + // prevent activity overlapping + let activityBox = activity.items.getBBox(); + $.each(layout.activities, function(){ + if (this.uiid !== activity.uiid + && (this instanceof ActivityDefs.ToolActivity || this instanceof ActivityDefs.GroupingActivity)) { + let thisBox = this.items.getBBox(); + if (Snap.path.isPointInsideBBox(thisBox, activityBox.x, activityBox.y)) { + activity.draw(activityBox.x, thisBox.y2 + 15); + return false; + } + } + }); } - - if (activity.transitions && activity.transitions.from.length > 0) { - activity = activity.transitions.from[0].toActivity; - } else { - activity = null; - } } - }); - - return nestedBranching; - }, - - /** - * Calculates start, middle and end points of a line between two activities. - */ - findTransitionPoints : function(fromActivity, toActivity) { - var fromActivityBox = fromActivity.items.getBBox(), - toActivityBox = toActivity.items.getBBox(), - // vertical direction takes priority - // horizontal is used only if activities are in the same line - direction = (fromActivityBox.y >= toActivityBox.y && fromActivityBox.y <= toActivityBox.y2) - || (fromActivityBox.y2 >= toActivityBox.y && fromActivityBox.y2 <= toActivityBox.y2) - || (toActivityBox.y >= fromActivityBox.y && toActivityBox.y <= fromActivityBox.y2) - || (toActivityBox.y2 >= fromActivityBox.y && toActivityBox.y2 <= fromActivityBox.y2) - ? 'horizontal' : 'vertical', - points = null; - if (direction === 'vertical') { - if (fromActivityBox.cy < toActivityBox.cy) { - points = { + ActivityLib.redrawTransitions(activity); + + $.each(layout.regions, function(){ + // redraw all annotation regions so they are pushed to back + this.draw(); + }); + + GeneralLib.setModified(true); + return true; + }, + + + findNestedBranching : function(branchingActivity) { + var nestedBranching = []; + $.each(branchingActivity.branches, function(){ + var activity = this.transitionFrom.toActivity; + while (activity) { + if (activity instanceof ActivityDefs.BranchingEdgeActivity) { + if (activity.branchingActivity == branchingActivity){ + break; + } + if (nestedBranching.indexOf(activity.branchingActivity) == -1) { + nestedBranching.push(activity.branchingActivity); + } + if (activity.isStart) { + nestedBranching = nestedBranching.concat(ActivityLib.findNestedBranching(activity.branchingActivity)); + activity = activity.branchingActivity.end; + } + } + + if (activity.transitions && activity.transitions.from.length > 0) { + activity = activity.transitions.from[0].toActivity; + } else { + activity = null; + } + } + }); + + return nestedBranching; + }, + + /** + * Calculates start, middle and end points of a line between two activities. + */ + findTransitionPoints : function(fromActivity, toActivity) { + var fromActivityBox = fromActivity.items.getBBox(), + toActivityBox = toActivity.items.getBBox(), + // vertical direction takes priority + // horizontal is used only if activities are in the same line + direction = (fromActivityBox.y >= toActivityBox.y && fromActivityBox.y <= toActivityBox.y2) + || (fromActivityBox.y2 >= toActivityBox.y && fromActivityBox.y2 <= toActivityBox.y2) + || (toActivityBox.y >= fromActivityBox.y && toActivityBox.y <= fromActivityBox.y2) + || (toActivityBox.y2 >= fromActivityBox.y && toActivityBox.y2 <= fromActivityBox.y2) + ? 'horizontal' : 'vertical', + points = null; + + if (direction === 'vertical') { + if (fromActivityBox.cy < toActivityBox.cy) { + points = { 'startX' : ActivityLib.adjustTransitionPoint(fromActivityBox.x, fromActivityBox.x2, toActivityBox.x + toActivityBox.width / 2), 'startY' : fromActivityBox.y2 + layout.transition.dotRadius, 'endY' : toActivityBox.y, 'direction' : 'down', 'arrowAngle': 180 }; - points.endX = ActivityLib.adjustTransitionPoint(toActivityBox.x, toActivityBox.x2, points.startX); - } else { - points = { + points.endX = ActivityLib.adjustTransitionPoint(toActivityBox.x, toActivityBox.x2, points.startX); + } else { + points = { 'startX' : ActivityLib.adjustTransitionPoint(fromActivityBox.x, fromActivityBox.x2, toActivityBox.x + toActivityBox.width / 2), 'startY' : fromActivityBox.y - layout.transition.dotRadius, 'endY' : toActivityBox.y2, 'direction' : 'up', 'arrowAngle': 0 }; - points.endX = ActivityLib.adjustTransitionPoint(toActivityBox.x, toActivityBox.x2, points.startX); - } - } else { - if (fromActivityBox.cx < toActivityBox.cx) { - points = { + points.endX = ActivityLib.adjustTransitionPoint(toActivityBox.x, toActivityBox.x2, points.startX); + } + } else { + if (fromActivityBox.cx < toActivityBox.cx) { + points = { 'startX' : fromActivityBox.x2 + layout.transition.dotRadius, 'startY' : ActivityLib.adjustTransitionPoint(fromActivityBox.y, fromActivityBox.y2, toActivityBox.y + toActivityBox.height / 2), 'endX' : toActivityBox.x, 'direction' : 'right', 'arrowAngle': 90 }; - points.endY = ActivityLib.adjustTransitionPoint(toActivityBox.y, toActivityBox.y2, points.startY); - } else { - // left - points = { + points.endY = ActivityLib.adjustTransitionPoint(toActivityBox.y, toActivityBox.y2, points.startY); + } else { + // left + points = { 'startX' : fromActivityBox.x - layout.transition.dotRadius, 'startY' : ActivityLib.adjustTransitionPoint(fromActivityBox.y, fromActivityBox.y2, toActivityBox.y + toActivityBox.height / 2), 'endX' : toActivityBox.x2, 'direction' : 'left', 'arrowAngle': 270 }; - points.endY = ActivityLib.adjustTransitionPoint(toActivityBox.y, toActivityBox.y2, points.startY); + points.endY = ActivityLib.adjustTransitionPoint(toActivityBox.y, toActivityBox.y2, points.startY); + } } - } - - if (points) { - // middle point of the transition - points.middleX = points.startX + (points.endX - points.startX)/2; - points.middleY = points.startY + (points.endY - points.startY)/2; - } - - return points; - }, - - getActivityIcon : function(activityName) { - // check for icon SVG cache in the library - var iconData = layout.toolMetadata[activityName].iconData; - if (!iconData) { - if (!layout.toolMetadata[activityName].iconPath) { - return; + + if (points) { + // middle point of the transition + points.middleX = points.startX + (points.endX - points.startX)/2; + points.middleY = points.startY + (points.endY - points.startY)/2; } - // if SVG is not cached, get it synchronously - $.ajax({ - url : LAMS_URL + layout.toolMetadata[activityName].iconPath, - async : false, - dataType : 'text', - success : function(response) { - iconData = response; - layout.toolMetadata[activityName].iconData = iconData; + + return points; + }, + + getActivityIcon : function(activityName) { + // check for icon SVG cache in the library + var iconData = layout.toolMetadata[activityName].iconData; + if (!iconData) { + if (!layout.toolMetadata[activityName].iconPath) { + return; } - }); - } - - if (iconData) { - // build a SVG fragment - var fragment = Snap.parse(iconData); - return Snap(fragment.node); - } - }, - - - /** - * Finds activity/region this shape is bound with. - */ - getParentObject : function(item) { - var parentObject = null; - - while (!parentObject) { - parentObject = item.data('parentObject'); - if (!parentObject) { - item = item.parent(); - if (!item || item.attr('id') == 'canvas') { - break; - } - } - } - - return parentObject; - }, - - - /** - * Get output definitions from Tool activity - */ - getOutputDefinitions : function(activity){ - if (!activity.toolID) { - return; - } - $.ajax({ - url : LAMS_URL + 'authoring/getToolOutputDefinitions.do', - data : { - 'toolContentID' : activity.toolContentID - || layout.toolMetadata[activity.learningLibraryID].defaultToolContentID - }, - cache : false, - async: true, - dataType : 'json', - success : function(response) { - activity.outputDefinitions = response; - $.each(activity.outputDefinitions, function() { - if (activity.gradebookToolOutputDefinitionName) { - if (this.name == activity.gradebookToolOutputDefinitionName) { - activity.gradebookToolOutputDefinitionDescription = this.description; - activity.gradebookToolOutputDefinitionWeightable = this.weightable; - return false; - } - } else { - if (this.isDefaultGradebookMark){ - activity.gradebookToolOutputDefinitionName = this.name; - activity.gradebookToolOutputDefinitionDescription = this.description; - activity.gradebookToolOutputDefinitionWeightable = this.weightable; - return false; - } + // if SVG is not cached, get it synchronously + $.ajax({ + url : LAMS_URL + layout.toolMetadata[activityName].iconPath, + async : false, + dataType : 'text', + success : function(response) { + iconData = response; + layout.toolMetadata[activityName].iconData = iconData; } }); } - }); - }, - - - /** - * Open separate window with activity authoring on double click. - */ - openActivityAuthoring : function(activity){ - if (activity.isAuthoringOpening) { - return; - } - - activity.isAuthoringOpening = true; - if (activity.authorURL) { - var dialogID = "dialogActivity" + activity.toolContentID; - showDialog(dialogID, { - 'height' : Math.max(300, $(window).height() - 30), - 'width' : Math.max(380, Math.min(1024, $(window).width() - 60)), - 'draggable' : true, - 'resizable' : true, - 'title' : activity.title + ' ' + LABELS.ACTIVITY_DIALOG_TITLE_SUFFIX, - 'beforeClose' : function(event){ - // ask the user if he really wants to exit before saving his work - var iframe = $('iframe', this); - // if X button was clicked, currentTarget is set - // if it is not the last Re-Edit/Close page, doCancel() exists - if (iframe[0].contentWindow.doCancel) { - iframe[0].contentWindow.doCancel(); - return false; + + if (iconData) { + // build a SVG fragment + var fragment = Snap.parse(iconData); + return Snap(fragment.node); + } + }, + + + /** + * Finds activity/region this shape is bound with. + */ + getParentObject : function(item) { + var parentObject = null; + + while (!parentObject) { + parentObject = item.data('parentObject'); + if (!parentObject) { + item = item.parent(); + if (!item || item.attr('id') == 'canvas') { + break; } - }, - 'close' : function(){ - $(this).remove(); - PropertyLib.validateConditionMappings(activity); - }, - 'open' : function() { - var dialog = $(this); - // load contents after opening the dialog - $('iframe', dialog).attr('id', dialogID).attr('src', activity.authorURL).on('load', function(){ - // override the close function so it works with the dialog, not window - this.contentWindow.closeWindow = function(){ - // detach the 'beforeClose' handler above, attach the standard one and close the dialog - ActivityLib.closeActivityAuthoring(dialogID); - } - }); } - }, true); - - GeneralLib.setModified(true); - activity.isAuthoringOpening = false; - return; - } - - // if there is no authoring URL, fetch it for a Tool Activity - if (activity.toolID) { + } + + return parentObject; + }, + + + /** + * Get output definitions from Tool activity + */ + getOutputDefinitions : function(activity){ + if (!activity.toolID) { + return; + } $.ajax({ - async : true, - cache : false, - url : LAMS_URL + "authoring/createToolContent.do", - dataType : 'json', + url : LAMS_URL + 'authoring/getToolOutputDefinitions.do', data : { - 'toolID' : activity.toolID, - // if toolContentID exists, a new content will not be created, only authorURL will be fetched - 'toolContentID' : activity.toolContentID, - 'contentFolderID' : layout.ld.contentFolderID + 'toolContentID' : activity.toolContentID + || layout.toolMetadata[activity.learningLibraryID].defaultToolContentID }, + cache : false, + async: true, + dataType : 'json', success : function(response) { - // make sure that response contains valid data - if (response.authorURL) { - activity.authorURL = response.authorURL; - activity.toolContentID = response.toolContentID; - // the response should always return a correct content folder ID, - // but just to make sure use it only when it is needed - if (!layout.ld.contentFolderID) { - layout.ld.contentFolderID = response.contentFolderID; + activity.outputDefinitions = response; + $.each(activity.outputDefinitions, function() { + if (activity.gradebookToolOutputDefinitionName) { + if (this.name == activity.gradebookToolOutputDefinitionName) { + activity.gradebookToolOutputDefinitionDescription = this.description; + activity.gradebookToolOutputDefinitionWeightable = this.weightable; + return false; + } + } else { + if (this.isDefaultGradebookMark){ + activity.gradebookToolOutputDefinitionName = this.name; + activity.gradebookToolOutputDefinitionDescription = this.description; + activity.gradebookToolOutputDefinitionWeightable = this.weightable; + return false; + } } - - activity.isAuthoringOpening = false; - // this time open it properly - ActivityLib.openActivityAuthoring(activity); - } - }, - complete : function(){ - activity.isAuthoringOpening = false; + }); } }); - } else { - activity.isAuthoringOpening = false; - } - }, - - - /** - * Draw each of activity's inbound and outbound transitions again. - */ - redrawTransitions : function(activity) { - if (activity.transitions) { - $.each(activity.transitions.from.slice(), function(){ - ActivityLib.addTransition(activity, this.toActivity, true); - }); - $.each(activity.transitions.to.slice(), function(){ - ActivityLib.addTransition(this.fromActivity, activity, true); - }); - } - }, - - /** - * Refresh conditions of complex output definitions from Tool activity - */ - refreshOutputConditions : function(activity){ - if (!activity.toolID) { - return; - } - - $.ajax({ - url : LAMS_URL + 'authoring/getToolOutputDefinitions.do', - data : { - 'toolContentID' : activity.toolContentID - || layout.toolMetadata[activity.learningLibraryID].defaultToolContentID - }, - cache : false, - async: false, - dataType : 'json', - success : function(response) { - // find the matching existing output and replace its conditions - $.each(response, function(){ - var output = this; - $.each(activity.outputDefinitions, function(){ - if (output.name == this.name) { - this.conditions = output.conditions; + }, + + + /** + * Open separate window with activity authoring on double click. + */ + openActivityAuthoring : function(activity){ + if (activity.isAuthoringOpening) { + return; + } + + activity.isAuthoringOpening = true; + if (activity.authorURL) { + var dialogID = "dialogActivity" + activity.toolContentID; + showDialog(dialogID, { + 'height' : Math.max(300, $(window).height() - 30), + 'width' : Math.max(380, Math.min(1024, $(window).width() - 60)), + 'draggable' : true, + 'resizable' : true, + 'title' : activity.title + ' ' + LABELS.ACTIVITY_DIALOG_TITLE_SUFFIX, + 'beforeClose' : function(event){ + // ask the user if he really wants to exit before saving his work + var iframe = $('iframe', this); + // if X button was clicked, currentTarget is set + // if it is not the last Re-Edit/Close page, doCancel() exists + if (iframe[0].contentWindow.doCancel) { + iframe[0].contentWindow.doCancel(); + return false; } - }); + }, + 'close' : function(){ + $(this).remove(); + PropertyLib.validateConditionMappings(activity); + }, + 'open' : function() { + var dialog = $(this); + // load contents after opening the dialog + $('iframe', dialog).attr('id', dialogID).attr('src', activity.authorURL).on('load', function(){ + // override the close function so it works with the dialog, not window + this.contentWindow.closeWindow = function(){ + // detach the 'beforeClose' handler above, attach the standard one and close the dialog + ActivityLib.closeActivityAuthoring(dialogID); + } + }); + } + }, true); + + GeneralLib.setModified(true); + activity.isAuthoringOpening = false; + return; + } + + // if there is no authoring URL, fetch it for a Tool Activity + if (activity.toolID) { + $.ajax({ + async : true, + cache : false, + url : LAMS_URL + "authoring/createToolContent.do", + dataType : 'json', + data : { + 'toolID' : activity.toolID, + // if toolContentID exists, a new content will not be created, only authorURL will be fetched + 'toolContentID' : activity.toolContentID, + 'contentFolderID' : layout.ld.contentFolderID + }, + success : function(response) { + // make sure that response contains valid data + if (response.authorURL) { + activity.authorURL = response.authorURL; + activity.toolContentID = response.toolContentID; + // the response should always return a correct content folder ID, + // but just to make sure use it only when it is needed + if (!layout.ld.contentFolderID) { + layout.ld.contentFolderID = response.contentFolderID; + } + + activity.isAuthoringOpening = false; + // this time open it properly + ActivityLib.openActivityAuthoring(activity); + } + }, + complete : function(){ + activity.isAuthoringOpening = false; + } }); + } else { + activity.isAuthoringOpening = false; } - }); - }, - - - /** - * Deletes the given activity. - */ - removeActivity : function(activity, forceRemove) { - var coreActivity = activity.branchingActivity || activity; - if (!forceRemove && activity instanceof ActivityDefs.BranchingEdgeActivity){ - // user removes one of the branching edges, so remove the whole activity - if (!confirm(LABELS.REMOVE_ACTIVITY_CONFIRM)){ + }, + + + /** + * Draw each of activity's inbound and outbound transitions again. + */ + redrawTransitions : function(activity) { + if (activity.transitions) { + $.each(activity.transitions.from.slice(), function(){ + ActivityLib.addTransition(activity, this.toActivity, true); + }); + $.each(activity.transitions.to.slice(), function(){ + ActivityLib.addTransition(this.fromActivity, activity, true); + }); + } + }, + + /** + * Refresh conditions of complex output definitions from Tool activity + */ + refreshOutputConditions : function(activity){ + if (!activity.toolID) { return; } - var otherEdge = activity.isStart ? coreActivity.end - : coreActivity.start; - ActivityLib.removeActivity(otherEdge, true); - } - - if (activity instanceof ActivityDefs.FloatingActivity) { - layout.floatingActivity = null; - // re-enable the button, as the only possible Floating Activity is gone now - $('.template[learningLibraryId="floating"]').slideDown(); - } else { - // remove the transitions - // need to use slice() to copy the array as it gets modified in removeTransition() - $.each(activity.transitions.from.slice(), function() { - ActivityLib.removeTransition(this); - }); - $.each(activity.transitions.to.slice(), function() { - ActivityLib.removeTransition(this); - }); - - // remove the activity from reference tables - layout.activities.splice(layout.activities.indexOf(activity), 1); - if (layout.copiedActivity = activity) { - layout.copiedActivity = null; - } - // find references of this activity as grouping or input - $.each(layout.activities, function(){ - var candidate = this.branchingActivity || this; - if (candidate.grouping == coreActivity) { - candidate.grouping = null; - this.propertiesContent = null; - this.draw(); - } else if (candidate.input == coreActivity) { - candidate.input = null; - this.propertiesContent = null; + $.ajax({ + url : LAMS_URL + 'authoring/getToolOutputDefinitions.do', + data : { + 'toolContentID' : activity.toolContentID + || layout.toolMetadata[activity.learningLibraryID].defaultToolContentID + }, + cache : false, + async: false, + dataType : 'json', + success : function(response) { + // find the matching existing output and replace its conditions + $.each(response, function(){ + var output = this; + $.each(activity.outputDefinitions, function(){ + if (output.name == this.name) { + this.conditions = output.conditions; + } + }); + }); } }); - } - - // remove the activity from parent activity - if (activity.parentActivity && activity.parentActivity instanceof DecorationDefs.Container) { - activity.parentActivity.childActivities.splice(activity.parentActivity.childActivities.indexOf(activity), 1); - } - - // remove child activities - if (activity instanceof DecorationDefs.Container) { - $.each(activity.childActivities.slice(), function(){ - ActivityLib.removeActivity(this); - }); - } - - // visually remove the activity - activity.items.remove(); - GeneralLib.setModified(true); - }, - - /** - * Deletes an item (activity, annotation etc.) as a result of user pressing a button on properties box - */ - removeItemWithButton : function(item) { - if ((item instanceof ActivityDefs.BranchingEdgeActivity) || confirm(LABELS.REMOVE_BUTTON_CONFIRM)) { - ActivityLib.removeSelectEffect(item); - if (item instanceof DecorationDefs.Label) { - DecorationLib.removeLabel(item); - } else if (item instanceof DecorationDefs.Region) { - DecorationLib.removeRegion(item); - } else if (item instanceof ActivityDefs.Transition) { - ActivityLib.removeTransition(item); + }, + + + /** + * Deletes the given activity. + */ + removeActivity : function(activity, forceRemove) { + var coreActivity = activity.branchingActivity || activity; + if (!forceRemove && activity instanceof ActivityDefs.BranchingEdgeActivity){ + // user removes one of the branching edges, so remove the whole activity + if (!confirm(LABELS.REMOVE_ACTIVITY_CONFIRM)){ + return; + } + var otherEdge = activity.isStart ? coreActivity.end + : coreActivity.start; + ActivityLib.removeActivity(otherEdge, true); + } + + if (activity instanceof ActivityDefs.FloatingActivity) { + layout.floatingActivity = null; + // re-enable the button, as the only possible Floating Activity is gone now + $('.template[learningLibraryId="floating"]').slideDown(); } else { - ActivityLib.removeActivity(item); + // remove the transitions + // need to use slice() to copy the array as it gets modified in removeTransition() + $.each(activity.transitions.from.slice(), function() { + ActivityLib.removeTransition(this); + }); + $.each(activity.transitions.to.slice(), function() { + ActivityLib.removeTransition(this); + }); + + // remove the activity from reference tables + layout.activities.splice(layout.activities.indexOf(activity), 1); + if (layout.copiedActivity = activity) { + layout.copiedActivity = null; + } + + // find references of this activity as grouping or input + $.each(layout.activities, function(){ + var candidate = this.branchingActivity || this; + if (candidate.grouping == coreActivity) { + candidate.grouping = null; + this.propertiesContent = null; + this.draw(); + } else if (candidate.input == coreActivity) { + candidate.input = null; + this.propertiesContent = null; + } + }); } - } - }, - - - /** - * Deselects an activity/transition/annotation - */ - removeSelectEffect : function(object) { - // remove the effect from the given object or the selected one, whatever it is - if (!object) { - object = layout.selectedObject; - } - - if (object) { - var selectEffect = object.items.selectEffect; - if (selectEffect) { - object.items.selectEffect = null; - // different effects for different types of objects - if (object instanceof DecorationDefs.Region) { - object.items.shape.attr({ - 'stroke' : 'black', - 'stroke-dasharray' : null - }); - object.items.fitButton.attr('display','none'); - object.items.resizeButton.attr('display','none'); - - var childActivities = DecorationLib.getChildActivities(object.items.shape); - $.each(childActivities, function(){ - ActivityLib.removeSelectEffect(this); - }); - } else if (object instanceof ActivityDefs.Transition) { - // just redraw the transition, it's easier - object.draw(); + + // remove the activity from parent activity + if (activity.parentActivity && activity.parentActivity instanceof DecorationDefs.Container) { + activity.parentActivity.childActivities.splice(activity.parentActivity.childActivities.indexOf(activity), 1); + } + + // remove child activities + if (activity instanceof DecorationDefs.Container) { + $.each(activity.childActivities.slice(), function(){ + ActivityLib.removeActivity(this); + }); + } + + // visually remove the activity + activity.items.remove(); + GeneralLib.setModified(true); + }, + + /** + * Deletes an item (activity, annotation etc.) as a result of user pressing a button on properties box + */ + removeItemWithButton : function(item) { + if ((item instanceof ActivityDefs.BranchingEdgeActivity) || confirm(LABELS.REMOVE_BUTTON_CONFIRM)) { + ActivityLib.removeSelectEffect(item); + if (item instanceof DecorationDefs.Label) { + DecorationLib.removeLabel(item); + } else if (item instanceof DecorationDefs.Region) { + DecorationLib.removeRegion(item); + } else if (item instanceof ActivityDefs.Transition) { + ActivityLib.removeTransition(item); } else { - selectEffect.remove(); - - // if it's "import part" do special processing for branching - if (activitiesOnlySelectable) { - if (object instanceof ActivityDefs.BranchingEdgeActivity) { - if (object.isStart) { - ActivityLib.removeSelectEffect(object.branchingActivity.end); - - // deselect all children in branches - $.each(object.branchingActivity.branches, function(){ - var transition = this.transitionFrom; - while (transition) { - var activity = transition.toActivity; - if (activity instanceof ActivityDefs.BranchingEdgeActivity) { - return true; + ActivityLib.removeActivity(item); + } + } + }, + + + /** + * Deselects an activity/transition/annotation + */ + removeSelectEffect : function(object) { + // remove the effect from the given object or the selected one, whatever it is + if (!object) { + object = layout.selectedObject; + } + + if (object) { + var selectEffect = object.items.selectEffect; + if (selectEffect) { + object.items.selectEffect = null; + // different effects for different types of objects + if (object instanceof DecorationDefs.Region) { + object.items.shape.attr({ + 'stroke' : 'black', + 'stroke-dasharray' : null + }); + object.items.fitButton.attr('display','none'); + object.items.resizeButton.attr('display','none'); + + var childActivities = DecorationLib.getChildActivities(object.items.shape); + $.each(childActivities, function(){ + ActivityLib.removeSelectEffect(this); + }); + } else if (object instanceof ActivityDefs.Transition) { + // just redraw the transition, it's easier + object.draw(); + } else { + selectEffect.remove(); + + // if it's "import part" do special processing for branching + if (activitiesOnlySelectable) { + if (object instanceof ActivityDefs.BranchingEdgeActivity) { + if (object.isStart) { + ActivityLib.removeSelectEffect(object.branchingActivity.end); + + // deselect all children in branches + $.each(object.branchingActivity.branches, function(){ + var transition = this.transitionFrom; + while (transition) { + var activity = transition.toActivity; + if (activity instanceof ActivityDefs.BranchingEdgeActivity) { + return true; + } + ActivityLib.removeSelectEffect(activity); + transition = activity.transitions.from.length > 0 ? activity.transitions.from[0] : null; } - ActivityLib.removeSelectEffect(activity); - transition = activity.transitions.from.length > 0 ? activity.transitions.from[0] : null; - } - }); - } else { - ActivityLib.removeSelectEffect(object.branchingActivity.start); + }); + } else { + ActivityLib.removeSelectEffect(object.branchingActivity.start); + } } - } - // deselect Parallel Activity children - $.each(layout.activities, function(){ - if (this instanceof ActivityDefs.ParallelActivity && this.childActivities.indexOf(object) > -1){ - ActivityLib.removeSelectEffect(this); - $.each(this.childActivities, function(){ - if (this != object) { - this.items.selectEffect.remove(); - this.items.selectEffect = null; - } - }); - } - }); + // deselect Parallel Activity children + $.each(layout.activities, function(){ + if (this instanceof ActivityDefs.ParallelActivity && this.childActivities.indexOf(object) > -1){ + ActivityLib.removeSelectEffect(this); + $.each(this.childActivities, function(){ + if (this != object) { + this.items.selectEffect.remove(); + this.items.selectEffect = null; + } + }); + } + }); + } } } + + if (layout.propertiesDialog) { + // no selected activity = no properties dialog + layout.propertiesDialog.css('visibility', 'hidden'); + } + layout.selectedObject = null; } - - if (layout.propertiesDialog) { - // no selected activity = no properties dialog - layout.propertiesDialog.css('visibility', 'hidden'); + }, + + + /** + * Removes the given transition. + */ + removeTransition : function(transition) { + // find the transition and remove it + var transitions = transition.fromActivity.transitions.from; + transitions.splice(transitions.indexOf(transition), 1); + transitions = transition.toActivity.transitions.to; + transitions.splice(transitions.indexOf(transition), 1); + + if (transition.branch) { + // remove corresponding branch + var branches = transition.branch.branchingActivity.branches; + branches.splice(branches.indexOf(transition.branch), 1); + + if (transition.branch.defaultBranch && branches.length > 0) { + // reset the first branch as the default one + branches[0].defaultBranch = true; + } } - layout.selectedObject = null; - } - }, - - - /** - * Removes the given transition. - */ - removeTransition : function(transition) { - // find the transition and remove it - var transitions = transition.fromActivity.transitions.from; - transitions.splice(transitions.indexOf(transition), 1); - transitions = transition.toActivity.transitions.to; - transitions.splice(transitions.indexOf(transition), 1); - - if (transition.branch) { - // remove corresponding branch - var branches = transition.branch.branchingActivity.branches; - branches.splice(branches.indexOf(transition.branch), 1); - - if (transition.branch.defaultBranch && branches.length > 0) { - // reset the first branch as the default one - branches[0].defaultBranch = true; - } - } - - transition.items.remove(); - GeneralLib.setModified(true); - }, - - - /** - * Crawles through branches setting their lengths and finding the longest one. - */ - updateBranchesLength : function(branchingActivity) { - var longestBranchLength = 0; - $.each(branchingActivity.branches, function(){ - // include the first activity - var branchLength = 1, - activity = this.transitionFrom.toActivity; - if (activity instanceof ActivityDefs.BranchingEdgeActivity + + transition.items.remove(); + GeneralLib.setModified(true); + }, + + + /** + * Crawles through branches setting their lengths and finding the longest one. + */ + updateBranchesLength : function(branchingActivity) { + var longestBranchLength = 0; + $.each(branchingActivity.branches, function(){ + // include the first activity + var branchLength = 1, + activity = this.transitionFrom.toActivity; + if (activity instanceof ActivityDefs.BranchingEdgeActivity && branchingActivity == activity.branchingActivity){ - // branch with no activities - return true; - } - - while (activity.transitions.from.length > 0) { - activity = activity.transitions.from[0].toActivity; - // check if reached the end of branch - if (activity instanceof ActivityDefs.BranchingEdgeActivity) { - break; - } else { - branchLength++; + // branch with no activities + return true; } - }; - this.branchLength = branchLength; - if (branchLength > longestBranchLength) { - longestBranchLength = branchLength; + + while (activity.transitions.from.length > 0) { + activity = activity.transitions.from[0].toActivity; + // check if reached the end of branch + if (activity instanceof ActivityDefs.BranchingEdgeActivity) { + break; + } else { + branchLength++; + } + }; + this.branchLength = branchLength; + if (branchLength > longestBranchLength) { + longestBranchLength = branchLength; + } + }); + + branchingActivity.longestBranchLength = longestBranchLength; + }, + + getActivityTitle : function(title, x, y) { + + if (title.length > 35) { + title = title.substring(0, 35) + '...'; } - }); - - branchingActivity.longestBranchLength = longestBranchLength; - }, - - getActivityTitle : function(title, x, y) { - - if (title.length > 35) { - title = title.substring(0, 35) + '...'; + var label = $('
').addClass('svg-activity-title-label svg-tool-activity-title-box') + .attr('xmlns', 'http://www.w3.org/1999/xhtml') + .text(title), + wrapper = $('').append(label).attr({ + 'x' : x + 75, + 'y' : y, + 'width' : layout.activity.width - 75, + 'height': layout.activity.height + }); + return Snap.parse(wrapper[0].outerHTML); } - var label = $('
').addClass('svg-activity-title-label svg-tool-activity-title-box') - .attr('xmlns', 'http://www.w3.org/1999/xhtml') - .text(title), - wrapper = $('').append(label).attr({ - 'x' : x + 75, - 'y' : y, - 'width' : layout.activity.width - 75, - 'height': layout.activity.height - }); - return Snap.parse(wrapper[0].outerHTML); - } -}; \ No newline at end of file + }; \ No newline at end of file Index: lams_central/web/includes/javascript/authoring/authoringHandler.js =================================================================== diff -u -r5abc46552b1092af6fc0d955502b28d279a43359 -r3cf44886d1cad7009a685cca2a118e2a3f17a730 --- lams_central/web/includes/javascript/authoring/authoringHandler.js (.../authoringHandler.js) (revision 5abc46552b1092af6fc0d955502b28d279a43359) +++ lams_central/web/includes/javascript/authoring/authoringHandler.js (.../authoringHandler.js) (revision 3cf44886d1cad7009a685cca2a118e2a3f17a730) @@ -6,799 +6,800 @@ * Contains general (canvas, group of shapes) action handlers */ var HandlerLib = { - // taken from http://jsfiddle.net/LQuyr/8/ - touchHandler : function(event) { - var touches = event.changedTouches, - first = touches[0], - type = ""; + // taken from http://jsfiddle.net/LQuyr/8/ + touchHandler : function(event) { + var touches = event.changedTouches, + first = touches[0], + type = ""; - switch (event.type) { - case "touchstart": - type = "mousedown"; - window.startY = event.pageY; - break; - case "touchmove": - type = "mousemove"; - break; - case "touchend": - type = "mouseup"; - break; - default: - return; - } - var simulatedEvent = document.createEvent("MouseEvent"); - simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, - first.clientX, first.clientY, false, false, false, false, 0 /*left*/ , null); + switch (event.type) { + case "touchstart": + type = "mousedown"; + window.startY = event.pageY; + break; + case "touchmove": + type = "mousemove"; + break; + case "touchend": + type = "mouseup"; + break; + default: + return; + } + var simulatedEvent = document.createEvent("MouseEvent"); + simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, + first.clientX, first.clientY, false, false, false, false, 0 /*left*/ , null); - first.target.dispatchEvent(simulatedEvent); + first.target.dispatchEvent(simulatedEvent); - var scrollables = []; - // check if any of the parents has is-scollable class - $(event.target).parents().map(function() { - try { - if ($(this).hasClass('scrollable')) { - // get vertical direction of touch event - var direction = (window.startY < first.clientY) ? 'down' : 'up'; - // calculate stuff... :o) - if ((($(this).scrollTop() <= 0) && (direction === 'down')) - || (($(this).height() <= $(this).scrollTop()) && (direction === 'up')) ){ + var scrollables = []; + // check if any of the parents has is-scollable class + $(event.target).parents().map(function() { + try { + if ($(this).hasClass('scrollable')) { + // get vertical direction of touch event + var direction = (window.startY < first.clientY) ? 'down' : 'up'; + // calculate stuff... :o) + if ((($(this).scrollTop() <= 0) && (direction === 'down')) + || (($(this).height() <= $(this).scrollTop()) && (direction === 'up')) ){ - } else { - scrollables.push(this); - } - } - } catch (e) {} - }); - - // if not, prevent default to prevent bouncing - if ((scrollables.length === 0) && (type === 'mousemove')) { - event.preventDefault(); - } - }, - - - /** - * Remove activity selection when user clicks on canvas. - */ - canvasClickHandler : function(event) { - // check if user clicked on empty space on canvas - // or on some element on top of it - if (layout.drawMode || (event.originalEvent ? + } else { + scrollables.push(this); + } + } + } catch (e) {} + }); + + // if not, prevent default to prevent bouncing + if ((scrollables.length === 0) && (type === 'mousemove')) { + event.preventDefault(); + } + }, + + + /** + * Remove activity selection when user clicks on canvas. + */ + canvasClickHandler : function(event) { + // check if user clicked on empty space on canvas + // or on some element on top of it + if (layout.drawMode || (event.originalEvent ? event.originalEvent.defaultPrevented : event.defaultPrevented)){ - return; - } - - ActivityLib.removeSelectEffect(); - }, - - - /** - * Start dragging an activity or a transition. - */ - dragItemsStartHandler : function(object, draggedElement, mouseupHandler, event, startX, startY) { - if (layout.drawMode || (event.originalEvent ? + return; + } + + ActivityLib.removeSelectEffect(); + }, + + + /** + * Start dragging an activity or a transition. + */ + dragItemsStartHandler : function(object, draggedElement, mouseupHandler, event, startX, startY) { + if (layout.drawMode || (event.originalEvent ? event.originalEvent.defaultPrevented : event.defaultPrevented)){ - return; - } - - // if user clicks or drags very shortly, do not take it into account - var items = object.items, - dragCancel = function(){ - canvas.off('mouseup'); - // if there is already a function waiting to be started, clear it - if (items.dragStarter) { - // prevent confusion when double clicking - clearTimeout(items.dragStarter); - items.dragStarter = null; return; } - } - dragCancel(); - canvas.mouseup(dragCancel); - - // run only if "click" event was not generated, i.e. user really wants to drag - items.dragStarter = setTimeout(function(){ - items.dragStarter = null; - - // show that we are in the middle of something - HandlerLib.resetCanvasMode(); - items.isDragged = true; - items.attr('cursor', 'move'); - - var parentObject = ActivityLib.getParentObject(draggedElement); - sticky = parentObject && (parentObject instanceof ActivityDefs.ParallelActivity - || parentObject instanceof ActivityDefs.OptionalActivity - || parentObject instanceof ActivityDefs.FloatingActivity); - - // hide child activities while moving the parent around - // they will be redrawn when the parent is dropped - if (sticky) { - $.each(parentObject.childActivities, function(){ - this.items.attr('display', 'none'); - if (this.childActivities) { - $.each(this.childActivities, function() { - this.items.attr('display', 'none'); - }); + + // if user clicks or drags very shortly, do not take it into account + var items = object.items, + dragCancel = function(){ + canvas.off('mouseup'); + // if there is already a function waiting to be started, clear it + if (items.dragStarter) { + // prevent confusion when double clicking + clearTimeout(items.dragStarter); + items.dragStarter = null; + return; } + } + dragCancel(); + canvas.mouseup(dragCancel); + + // run only if "click" event was not generated, i.e. user really wants to drag + items.dragStarter = setTimeout(function(){ + items.dragStarter = null; + + // show that we are in the middle of something + HandlerLib.resetCanvasMode(); + items.isDragged = true; + items.attr('cursor', 'move'); + + var parentObject = ActivityLib.getParentObject(draggedElement); + sticky = parentObject && (parentObject instanceof ActivityDefs.ParallelActivity + || parentObject instanceof ActivityDefs.OptionalActivity + || parentObject instanceof ActivityDefs.FloatingActivity); + + // hide child activities while moving the parent around + // they will be redrawn when the parent is dropped + if (sticky) { + $.each(parentObject.childActivities, function(){ + this.items.attr('display', 'none'); + if (this.childActivities) { + $.each(this.childActivities, function() { + this.items.attr('display', 'none'); + }); + } + }); + } + + canvas.mousemove(function(event) { + HandlerLib.dragItemsMoveHandler(object, event, startX, startY); }); + + var mouseup = function(mouseupEvent){ + // finish dragging - restore various elements' default state + items.isDragged = false; + items.unmouseup(); + HandlerLib.resetCanvasMode(true); + if (layout.bin.glowEffect) { + layout.bin.glowEffect.remove(); + layout.bin.glowEffect = null; + } + + // do whetver needs to be done with the dragged elements + mouseupHandler(mouseupEvent); + }; + + /* The event is passed from items to canvas, so it is OK to assign it only to canvas. + Unfortunately, this does not apply to the icon. + Also, if mousedown was on items and mouseup on canvas (very quick move), + items will not accept mouseup until click. + */ + canvas.mouseup(mouseup); + + GeneralLib.setModified(true); + }, layout.conf.dragStartThreshold); + }, + + + /** + * Moves dragged elements on the canvas. + */ + dragItemsMoveHandler : function(object, event, startX, startY) { + // detect if activity is close to an edge of viewport and scroll automatically + if (event.pageX - canvas.offset().left > canvas.width() - 150) { + canvas.scrollLeft(canvas.scrollLeft() + 10); + } else if (event.pageX - canvas.offset().left < 150) { + canvas.scrollLeft(Math.max(0, canvas.scrollLeft() - 10)); } - - canvas.mousemove(function(event) { - HandlerLib.dragItemsMoveHandler(object, event, startX, startY); - }); - - var mouseup = function(mouseupEvent){ - // finish dragging - restore various elements' default state - items.isDragged = false; - items.unmouseup(); - HandlerLib.resetCanvasMode(true); - if (layout.bin.glowEffect) { - layout.bin.glowEffect.remove(); - layout.bin.glowEffect = null; - } - - // do whetver needs to be done with the dragged elements - mouseupHandler(mouseupEvent); - }; - - /* The event is passed from items to canvas, so it is OK to assign it only to canvas. - Unfortunately, this does not apply to the icon. - Also, if mousedown was on items and mouseup on canvas (very quick move), - items will not accept mouseup until click. - */ - canvas.mouseup(mouseup); - - GeneralLib.setModified(true); - }, layout.conf.dragStartThreshold); - }, - - - /** - * Moves dragged elements on the canvas. - */ - dragItemsMoveHandler : function(object, event, startX, startY) { - // detect if activity is close to an edge of viewport and scroll automatically - if (event.pageX - canvas.offset().left > canvas.width() - 150) { - canvas.scrollLeft(canvas.scrollLeft() + 10); - } else if (event.pageX - canvas.offset().left < 150) { - canvas.scrollLeft(Math.max(0, canvas.scrollLeft() - 10)); - } - - if (event.pageY - canvas.offset().top > canvas.height() - 150) { - canvas.scrollTop(canvas.scrollTop() + 10); - } else if (event.pageY - canvas.offset().top < 100) { - canvas.scrollTop(Math.max(0, canvas.scrollTop() - 10)); - } - - /* - Another approach, based on how the event is close to the edge - - var canvasWidth = canvas.width(), - canvasHeight = canvas.height(), - eventX = event.pageX - canvas.offset().left, - eventY = event.pageY - canvas.offset().top, - scrollYTrigger = layout.activity.height + 20; - - if (event.pageX - canvas.offset().left > canvas.width() - 50) { - canvas.scrollLeft(canvas.scrollLeft() + 10); - } else if (event.pageX - canvas.offset().left < 200) { - canvas.scrollLeft(Math.max(0, canvas.scrollLeft() - 10)); - } - - if (eventY > canvasHeight - 100) { - canvas.scrollTop(canvas.scrollTop() + eventY - canvasHeight + 100); - } else if (eventY < 80) { - canvas.scrollTop(Math.max(0, canvas.scrollTop() - 80 + eventY)); - } - - */ - var dx = event.pageX + canvas.scrollLeft() - startX, - dy = event.pageY + canvas.scrollTop() - startY; + if (event.pageY - canvas.offset().top > canvas.height() - 150) { + canvas.scrollTop(canvas.scrollTop() + 10); + } else if (event.pageY - canvas.offset().top < 100) { + canvas.scrollTop(Math.max(0, canvas.scrollTop() - 10)); + } - object.items.transform('t' + dx + ' ' + dy); - - if (object.transitions) { - $.each(object.transitions.from, function(){ - this.draw(); - }); - $.each(object.transitions.to, function(){ - this.draw(); - }); - } - - // highlight rubbish bin if dragged elements are over it - if (HandlerLib.isElemenentBinned(event)) { - if (!layout.bin.glowEffect) { - let binSvg = layout.bin.select('svg'); - layout.bin.glowEffect = paper.path(Snap.format('M {x} {y} h {side} v {side} h -{side} z', - { - 'x' : binSvg.attr('x'), - 'y' : binSvg.attr('y'), - 'side' : binSvg.attr('width') - })) - .attr({ - 'stroke' : layout.colors.binSelect, - 'stroke-width' : 2, - 'stroke-dasharray' : '5,3', - 'fill' : 'none' - }); - layout.bin.append(layout.bin.glowEffect); + /* + Another approach, based on how the event is close to the edge + + var canvasWidth = canvas.width(), + canvasHeight = canvas.height(), + eventX = event.pageX - canvas.offset().left, + eventY = event.pageY - canvas.offset().top, + scrollYTrigger = layout.activity.height + 20; + + if (event.pageX - canvas.offset().left > canvas.width() - 50) { + canvas.scrollLeft(canvas.scrollLeft() + 10); + } else if (event.pageX - canvas.offset().left < 200) { + canvas.scrollLeft(Math.max(0, canvas.scrollLeft() - 10)); + } + + if (eventY > canvasHeight - 100) { + canvas.scrollTop(canvas.scrollTop() + eventY - canvasHeight + 100); + } else if (eventY < 80) { + canvas.scrollTop(Math.max(0, canvas.scrollTop() - 80 + eventY)); + } + + */ + + var dx = event.pageX + canvas.scrollLeft() - startX, + dy = event.pageY + canvas.scrollTop() - startY; + + object.items.transform('t' + dx + ' ' + dy); + + if (object.transitions) { + $.each(object.transitions.from, function(){ + this.draw(); + }); + $.each(object.transitions.to, function(){ + this.draw(); + }); } - } else if (layout.bin.glowEffect){ - layout.bin.glowEffect.remove(); - layout.bin.glowEffect = null; - } - }, - - - /** - * Rewrites a shape's coordinates, so it is where the user dropped it. - */ - dropObject : function(object, cancel) { - // finally transform the dragged elements - var transformation = Snap.parseTransformString(object.items.transform().string); - object.items.transform(''); - - // cancel means that the object will be redrawn in its original place - if (cancel) { - object.draw(); - } else { - // finialise the drop - var box = object.items.getBBox(), - originalCoordinates = { - x : box.x, - // adjust this coordinate for annotation labels - y : box.y + (object instanceof DecorationDefs.Label ? 6 : 0) - }; - - if (transformation && transformation.length > 0) { - object.draw(originalCoordinates.x + transformation[0][1], - originalCoordinates.y + transformation[0][2]); + + // highlight rubbish bin if dragged elements are over it + if (HandlerLib.isElemenentBinned(event)) { + if (!layout.bin.glowEffect) { + let binSvg = layout.bin.select('svg'); + layout.bin.glowEffect = paper.path(Snap.format('M {x} {y} h {side} v {side} h -{side} z', + { + 'x' : binSvg.attr('x'), + 'y' : binSvg.attr('y'), + 'side' : binSvg.attr('width') + })) + .attr({ + 'stroke' : layout.colors.binSelect, + 'stroke-width' : 2, + 'stroke-dasharray' : '5,3', + 'fill' : 'none' + }); + layout.bin.append(layout.bin.glowEffect); + } + } else if (layout.bin.glowEffect){ + layout.bin.glowEffect.remove(); + layout.bin.glowEffect = null; } - - // add space if dropped object is next to border - GeneralLib.resizePaper(); - - return originalCoordinates; - } - }, - - - /** - * Checks whether activity or transition is over rubbish bin. - */ - isElemenentBinned : function(event) { - var translatedEvent = GeneralLib.translateEventOnCanvas(event); - return Snap.path.isPointInsideBBox(layout.bin.getBBox(), translatedEvent[0], translatedEvent[1]); - }, - - - /** - * Selects an activity/transition/annotation. - */ - itemClickHandler : function(event) { - if ((event.ctrlKey || event.metaKey) || layout.drawMode || - (event.originalEvent ? event.originalEvent.defaultPrevented : event.defaultPrevented)){ - return; - } - - var parentObject = ActivityLib.getParentObject(this); - // if it's "import part" allow multiple selection of activities - if (activitiesOnlySelectable) { - if (parentObject.items.selectEffect) { - ActivityLib.removeSelectEffect(parentObject); + }, + + + /** + * Rewrites a shape's coordinates, so it is where the user dropped it. + */ + dropObject : function(object, cancel) { + // finally transform the dragged elements + var transformation = Snap.parseTransformString(object.items.transform().string); + object.items.transform(''); + + // cancel means that the object will be redrawn in its original place + if (cancel) { + object.draw(); } else { - ActivityLib.addSelectEffect(parentObject); - } - } else if (parentObject != layout.selectedObject) { - HandlerLib.canvasClickHandler(event); - ActivityLib.addSelectEffect(parentObject, true); - } - - // so canvas handler unselectActivityHandler() is not run - event.preventDefault(); - }, - - - /** - * Default mode for canvas. Run after draw mode is no longer needed. - */ - resetCanvasMode : function(init){ - // so elements understand that no events should be triggered, i.e. canvas is "dead" - layout.drawMode = !init; - - // remove selection if exists - ActivityLib.removeSelectEffect(); - canvas.css('cursor', 'default') - .off('click') - .off('mousedown') - .off('mouseup') - .off('mousemove'); - - if (init) { - // if clicked anywhere, activity selection is gone - canvas.click(HandlerLib.canvasClickHandler) - // when mouse gets closer to properties dialog, make it fully visible - .mousemove(HandlerPropertyLib.approachPropertiesDialogHandler); - } - } -}, + // finialise the drop + var box = object.items.getBBox(), + originalCoordinates = { + x : box.x, + // adjust this coordinate for annotation labels + y : box.y + (object instanceof DecorationDefs.Label ? 6 : 0) + }; + if (transformation && transformation.length > 0) { + object.draw(originalCoordinates.x + transformation[0][1], + originalCoordinates.y + transformation[0][2]); + } + // add space if dropped object is next to border + GeneralLib.resizePaper(); -/** - * Contains handlers for actions over Activities. - */ -HandlerActivityLib = { - // double tap support - tapTimeout : 500, - lastTapTime : 0, - lastTapTarget : null, - - /** - * Double click opens activity authoring. - */ - activityClickHandler : function(event) { - var activity = ActivityLib.getParentObject(this), - currentTime = new Date().getTime(); - // is the second click on the same activity as the first one? - if (activity == HandlerActivityLib.lastTapTarget) { - // was the second click quick enough after the first one? - var tapLength = currentTime - HandlerActivityLib.lastTapTime; - if (tapLength < HandlerActivityLib.tapTimeout && tapLength > 0) { - event.preventDefault(); - if (activity.readOnly) { - layout.infoDialog.data('show')(LABELS.LIVEEDIT_READONLY_ACTIVITY_ERROR); + return originalCoordinates; + } + }, + + + /** + * Checks whether activity or transition is over rubbish bin. + */ + isElemenentBinned : function(event) { + var translatedEvent = GeneralLib.translateEventOnCanvas(event); + return Snap.path.isPointInsideBBox(layout.bin.getBBox(), translatedEvent[0], translatedEvent[1]); + }, + + + /** + * Selects an activity/transition/annotation. + */ + itemClickHandler : function(event) { + if ((event.ctrlKey || event.metaKey) || layout.drawMode || + (event.originalEvent ? event.originalEvent.defaultPrevented : event.defaultPrevented)){ + return; + } + + var parentObject = ActivityLib.getParentObject(this); + // if it's "import part" allow multiple selection of activities + if (activitiesOnlySelectable) { + if (parentObject.items.selectEffect) { + ActivityLib.removeSelectEffect(parentObject); } else { - ActivityLib.openActivityAuthoring(activity); + ActivityLib.addSelectEffect(parentObject); } - return; + } else if (parentObject != layout.selectedObject) { + HandlerLib.canvasClickHandler(event); + ActivityLib.addSelectEffect(parentObject, true); } + + // so canvas handler unselectActivityHandler() is not run + event.preventDefault(); + }, + + + /** + * Default mode for canvas. Run after draw mode is no longer needed. + */ + resetCanvasMode : function(init){ + // so elements understand that no events should be triggered, i.e. canvas is "dead" + layout.drawMode = !init; + + // remove selection if exists + ActivityLib.removeSelectEffect(); + canvas.css('cursor', 'default') + .off('click') + .off('mousedown') + .off('mouseup') + .off('mousemove'); + + if (init) { + // if clicked anywhere, activity selection is gone + canvas.click(HandlerLib.canvasClickHandler) + // when mouse gets closer to properties dialog, make it fully visible + .mousemove(HandlerPropertyLib.approachPropertiesDialogHandler); + } } - HandlerActivityLib.lastTapTime = currentTime; - HandlerActivityLib.lastTapTarget = activity; - // single click - HandlerLib.itemClickHandler.call(this, event); }, - - + + + /** - * Starts drawing a transition or dragging an activity. + * Contains handlers for actions over Activities. */ - activityMousedownHandler : function(event, x, y){ - if (layout.drawMode || (event.originalEvent ? + HandlerActivityLib = { + // double tap support + tapTimeout : 500, + lastTapTime : 0, + lastTapTarget : null, + + /** + * Double click opens activity authoring. + */ + activityClickHandler : function(event) { + var activity = ActivityLib.getParentObject(this), + currentTime = new Date().getTime(); + // is the second click on the same activity as the first one? + if (activity == HandlerActivityLib.lastTapTarget) { + // was the second click quick enough after the first one? + var tapLength = currentTime - HandlerActivityLib.lastTapTime; + if (tapLength < HandlerActivityLib.tapTimeout && tapLength > 0) { + event.preventDefault(); + if (activity.readOnly) { + layout.infoDialog.data('show')(LABELS.LIVEEDIT_READONLY_ACTIVITY_ERROR); + } else { + ActivityLib.openActivityAuthoring(activity); + } + return; + } + } + HandlerActivityLib.lastTapTime = currentTime; + HandlerActivityLib.lastTapTarget = activity; + // single click + HandlerLib.itemClickHandler.call(this, event); + }, + + + /** + * Starts drawing a transition or dragging an activity. + */ + activityMousedownHandler : function(event, x, y){ + if (layout.drawMode || (event.originalEvent ? event.originalEvent.defaultPrevented : event.defaultPrevented)){ - return; - } - - var activity = ActivityLib.getParentObject(this); - if (event.ctrlKey || event.metaKey) { - // when CTRL is held down, start drawing a transition - HandlerTransitionLib.drawTransitionStartHandler(activity, event, x, y); - } else if (!activity.parentActivity + return; + } + + var activity = ActivityLib.getParentObject(this); + if (event.ctrlKey || event.metaKey) { + // when CTRL is held down, start drawing a transition + HandlerTransitionLib.drawTransitionStartHandler(activity, event, x, y); + } else if (!activity.parentActivity || !(activity.parentActivity instanceof ActivityDefs.ParallelActivity)){ - var mouseupHandler = function(event){ - if (HandlerLib.isElemenentBinned(event)) { - // if the activity was over rubbish bin, remove it - var canRemove = true; - // check if the activity or its parent are read-only - if (activity.readOnly) { - canRemove = false; - layout.infoDialog.data('show')(LABELS.LIVEEDIT_REMOVE_ACTIVITY_ERROR); - } else if (activity.branchingActivity){ - if (activity.branchingActivity.readOnly) { + var mouseupHandler = function(event){ + if (HandlerLib.isElemenentBinned(event)) { + // if the activity was over rubbish bin, remove it + var canRemove = true; + // check if the activity or its parent are read-only + if (activity.readOnly) { canRemove = false; layout.infoDialog.data('show')(LABELS.LIVEEDIT_REMOVE_ACTIVITY_ERROR); + } else if (activity.branchingActivity){ + if (activity.branchingActivity.readOnly) { + canRemove = false; + layout.infoDialog.data('show')(LABELS.LIVEEDIT_REMOVE_ACTIVITY_ERROR); + } } - } - else if (activity.parentActivity && activity.parentActivity.readOnly){ - canRemove = false; - layout.infoDialog.data('show')(LABELS.LIVEEDIT_REMOVE_PARENT_ERROR); - } - - if (canRemove) { - ActivityLib.removeActivity(activity); + else if (activity.parentActivity && activity.parentActivity.readOnly){ + canRemove = false; + layout.infoDialog.data('show')(LABELS.LIVEEDIT_REMOVE_PARENT_ERROR); + } + + if (canRemove) { + ActivityLib.removeActivity(activity); + } else { + //revert to the original position + HandlerLib.dropObject(activity, true); + } } else { - //revert to the original position - HandlerLib.dropObject(activity, true); + // finalise movement - rewrite coordinates, see if the activity was not added to a container + var originalCoordinates = HandlerLib.dropObject(activity), + translatedEvent = GeneralLib.translateEventOnCanvas(event), + endX = translatedEvent[0], + endY = translatedEvent[1], + dropAllowed = ActivityLib.dropActivity(activity, endX, endY); + + if (!dropAllowed) { + // dropping the activity in this place is forbidden, revert the changes + activity.draw(originalCoordinates.x, originalCoordinates.y); + ActivityLib.redrawTransitions(activity); + } } - } else { - // finalise movement - rewrite coordinates, see if the activity was not added to a container - var originalCoordinates = HandlerLib.dropObject(activity), - translatedEvent = GeneralLib.translateEventOnCanvas(event), - endX = translatedEvent[0], - endY = translatedEvent[1], - dropAllowed = ActivityLib.dropActivity(activity, endX, endY); - - if (!dropAllowed) { - // dropping the activity in this place is forbidden, revert the changes - activity.draw(originalCoordinates.x, originalCoordinates.y); - } } + + var transitions = activity.transitions.from.concat(activity.transitions.to); + // start dragging the activity + HandlerLib.dragItemsStartHandler(activity, this, mouseupHandler, event, x + canvas.scrollLeft(), y + canvas.scrollTop(), transitions); } + }, - var transitions = activity.transitions.from.concat(activity.transitions.to); - // start dragging the activity - HandlerLib.dragItemsStartHandler(activity, this, mouseupHandler, event, x + canvas.scrollLeft(), y + canvas.scrollTop(), transitions); + + /** + * Lighthens up branching edges in the same colour for identifictation. + */ + branchingEdgeMouseoverHandler : function() { + var branchingActivity = ActivityLib.getParentObject(this).branchingActivity, + startItems = branchingActivity.start.items, + endItems = branchingActivity.end.items; + if (!startItems.isDragged && !endItems.isDragged) { + startItems.shape.addClass('svg-branching-match'); + endItems.shape.addClass('svg-branching-match'); + } + }, + + + /** + * Return branching edges to their normal colours. + */ + branchingEdgeMouseoutHandler : function() { + var branchingActivity = ActivityLib.getParentObject(this).branchingActivity, + startItems = branchingActivity.start.items, + endItems = branchingActivity.end.items; + + if (!startItems.isDragged && !endItems.isDragged) { + startItems.shape.removeClass('svg-branching-match'); + endItems.shape.removeClass('svg-branching-match'); + } } }, - - - /** - * Lighthens up branching edges in the same colour for identifictation. - */ - branchingEdgeMouseoverHandler : function() { - var branchingActivity = ActivityLib.getParentObject(this).branchingActivity, - startItems = branchingActivity.start.items, - endItems = branchingActivity.end.items; - if (!startItems.isDragged && !endItems.isDragged) { - startItems.shape.addClass('svg-branching-match'); - endItems.shape.addClass('svg-branching-match'); - } - }, - - - /** - * Return branching edges to their normal colours. - */ - branchingEdgeMouseoutHandler : function() { - var branchingActivity = ActivityLib.getParentObject(this).branchingActivity, - startItems = branchingActivity.start.items, - endItems = branchingActivity.end.items; - - if (!startItems.isDragged && !endItems.isDragged) { - startItems.shape.removeClass('svg-branching-match'); - endItems.shape.removeClass('svg-branching-match'); - } - } -}, -/** - * Contains handlers for actions over Decoration elements. - */ -HandlerDecorationLib = { - /** - * Starts dragging a container + * Contains handlers for actions over Decoration elements. */ - containerMousedownHandler : function(event, x, y){ - if (layout.drawMode || (event.originalEvent ? + HandlerDecorationLib = { + + /** + * Starts dragging a container + */ + containerMousedownHandler : function(event, x, y){ + if (layout.drawMode || (event.originalEvent ? event.originalEvent.defaultPrevented : event.defaultPrevented)){ - return; - } - - var container = ActivityLib.getParentObject(this); - // allow transition dragging - var mouseupHandler = function(event){ - if (HandlerLib.isElemenentBinned(event)) { - // if the container was over rubbish bin, remove it - if (container instanceof DecorationDefs.Region) { - DecorationLib.removeRegion(container); - } else { - var canRemove = true; - if (container.readOnly) { - canRemove = false; - layout.infoDialog.data('show')(LABELS.LIVEEDIT_REMOVE_ACTIVITY_ERROR); - } else if (container.childActivities){ - // if any of the child activities is read-only, the parent activity can not be removed - $.each(container.childActivities, function(){ - if (this.readOnly) { - canRemove = false; - layout.infoDialog.data('show')(LABELS.LIVEEDIT_REMOVE_CHILD_ERROR); - return false; - } - }); - } - - if (canRemove) { - ActivityLib.removeActivity(container); + return; + } + + var container = ActivityLib.getParentObject(this); + // allow transition dragging + var mouseupHandler = function(event){ + if (HandlerLib.isElemenentBinned(event)) { + // if the container was over rubbish bin, remove it + if (container instanceof DecorationDefs.Region) { + DecorationLib.removeRegion(container); } else { - // revert the activity back to its original place - HandlerLib.dropObject(container, true); + var canRemove = true; + if (container.readOnly) { + canRemove = false; + layout.infoDialog.data('show')(LABELS.LIVEEDIT_REMOVE_ACTIVITY_ERROR); + } else if (container.childActivities){ + // if any of the child activities is read-only, the parent activity can not be removed + $.each(container.childActivities, function(){ + if (this.readOnly) { + canRemove = false; + layout.infoDialog.data('show')(LABELS.LIVEEDIT_REMOVE_CHILD_ERROR); + return false; + } + }); + } + + if (canRemove) { + ActivityLib.removeActivity(container); + } else { + // revert the activity back to its original place + HandlerLib.dropObject(container, true); + } } + } else { + HandlerLib.dropObject(container); + if (container instanceof ActivityDefs.FloatingActivity + || container instanceof ActivityDefs.OptionalActivity + || container instanceof ActivityDefs.ParallelActivity) { + ActivityLib.dropActivity(container); + } } - } else { - HandlerLib.dropObject(container); - if (container instanceof ActivityDefs.FloatingActivity - || container instanceof ActivityDefs.OptionalActivity - || container instanceof ActivityDefs.ParallelActivity) { - ActivityLib.dropActivity(container); - } } - } - - HandlerLib.dragItemsStartHandler(container, this, mouseupHandler, event, x + canvas.scrollLeft(), y + canvas.scrollTop()); - }, - - /** - * Start drawing a region. - */ - drawRegionStartHandler : function(startEvent) { - HandlerLib.resetCanvasMode(); - - // remember what were the drawing start coordinates - var translatedEvent = GeneralLib.translateEventOnCanvas(startEvent), - data = { - 'startX' : translatedEvent[0], - 'startY' : translatedEvent[1] - }; - - canvas.mousemove(function(event){ - HandlerDecorationLib.drawRegionMoveHandler(data, event); - }) - .mouseup(function(event){ - HandlerDecorationLib.drawRegionEndHandler(data); - }); - }, - - - /** - * Keep drawing a region. - */ - drawRegionMoveHandler : function(data, event) { - var translatedEvent = GeneralLib.translateEventOnCanvas(event), - x = translatedEvent[0], - y = translatedEvent[1]; - - if (data.shape) { - // remove the previous rectangle - data.shape.remove(); - } - - data.shape = paper.path(Snap.format('M {x} {y} h {width} v {height} h -{width} z', - { - 'x' : x < data.startX ? x : data.startX, - 'y' : y < data.startY ? y : data.startY, - 'width' : Math.abs(x - data.startX), - 'height' : Math.abs(y - data.startY) - }) - ) - .attr({ - 'fill' : layout.colors.annotation, - 'opacity' : 0.3 - }); - - // immediatelly show which activities will be enveloped - var childActivities = DecorationLib.getChildActivities(data.shape); - $.each(layout.activities, function(){ - if (!this.parentActivity && $.inArray(this, childActivities) > -1){ - ActivityLib.addSelectEffect(this, false); - } else { + + HandlerLib.dragItemsStartHandler(container, this, mouseupHandler, event, x + canvas.scrollLeft(), y + canvas.scrollTop()); + }, + + /** + * Start drawing a region. + */ + drawRegionStartHandler : function(startEvent) { + HandlerLib.resetCanvasMode(); + + // remember what were the drawing start coordinates + var translatedEvent = GeneralLib.translateEventOnCanvas(startEvent), + data = { + 'startX' : translatedEvent[0], + 'startY' : translatedEvent[1] + }; + + canvas.mousemove(function(event){ + HandlerDecorationLib.drawRegionMoveHandler(data, event); + }) + .mouseup(function(event){ + HandlerDecorationLib.drawRegionEndHandler(data); + }); + }, + + + /** + * Keep drawing a region. + */ + drawRegionMoveHandler : function(data, event) { + var translatedEvent = GeneralLib.translateEventOnCanvas(event), + x = translatedEvent[0], + y = translatedEvent[1]; + + if (data.shape) { + // remove the previous rectangle + data.shape.remove(); + } + + data.shape = paper.path(Snap.format('M {x} {y} h {width} v {height} h -{width} z', + { + 'x' : x < data.startX ? x : data.startX, + 'y' : y < data.startY ? y : data.startY, + 'width' : Math.abs(x - data.startX), + 'height' : Math.abs(y - data.startY) + }) + ) + .attr({ + 'fill' : layout.colors.annotation, + 'opacity' : 0.3 + }); + + // immediatelly show which activities will be enveloped + var childActivities = DecorationLib.getChildActivities(data.shape); + $.each(layout.activities, function(){ + if (!this.parentActivity && $.inArray(this, childActivities) > -1){ + ActivityLib.addSelectEffect(this, false); + } else { + ActivityLib.removeSelectEffect(this); + } + }); + }, + + + /** + * Finalise region drawing. + */ + drawRegionEndHandler : function(data) { + // remove select effect from all activities + $.each(layout.activities, function(){ ActivityLib.removeSelectEffect(this); + }); + + if (data.shape) { + var box = data.shape.getBBox(), + region = DecorationLib.addRegion(box.x, box.y, box.x2, box.y2); + data.shape.remove(); + ActivityLib.addSelectEffect(region, true); } - }); - }, - - - /** - * Finalise region drawing. - */ - drawRegionEndHandler : function(data) { - // remove select effect from all activities - $.each(layout.activities, function(){ - ActivityLib.removeSelectEffect(this); - }); - - if (data.shape) { - var box = data.shape.getBBox(), - region = DecorationLib.addRegion(box.x, box.y, box.x2, box.y2); - data.shape.remove(); - ActivityLib.addSelectEffect(region, true); - } - HandlerLib.resetCanvasMode(true); - }, - - - /** - * Starts dragging a label - */ - labelMousedownHandler : function(event, x, y){ - if (layout.drawMode || (event.originalEvent ? + HandlerLib.resetCanvasMode(true); + }, + + + /** + * Starts dragging a label + */ + labelMousedownHandler : function(event, x, y){ + if (layout.drawMode || (event.originalEvent ? event.originalEvent.defaultPrevented : event.defaultPrevented)){ - return; - } - - var label = ActivityLib.getParentObject(this); - // allow transition dragging - var mouseupHandler = function(event){ - if (HandlerLib.isElemenentBinned(event)) { - // if the region was over rubbish bin, remove it - DecorationLib.removeLabel(label); - } else { - HandlerLib.dropObject(label); + return; } - } - - HandlerLib.dragItemsStartHandler(label, this, mouseupHandler, event, x + canvas.scrollLeft(), y + canvas.scrollTop()); - }, - - - /** - * Start resizing a region. - */ - resizeRegionStartHandler : function(event) { - // otherwise mousedown handler (dragging) can be triggered - event.stopImmediatePropagation(); - event.preventDefault(); - - HandlerLib.resetCanvasMode(); - - var region = ActivityLib.getParentObject(this); - - canvas.mousemove(function(event){ - HandlerDecorationLib.resizeRegionMoveHandler(region, event); - }) - .mouseup(function(){ - HandlerLib.resetCanvasMode(true); - ActivityLib.addSelectEffect(region, true); - }); - - GeneralLib.setModified(true); - }, - - - /** - * Keep resising a region. - */ - resizeRegionMoveHandler : function(region, event){ - var translatedEvent = GeneralLib.translateEventOnCanvas(event), - x = translatedEvent[0], - y = translatedEvent[1]; - - // keep the initial coordinates and adjust end coordinates - region.draw(null, null, x, y); - } -}, + var label = ActivityLib.getParentObject(this); + // allow transition dragging + var mouseupHandler = function(event){ + if (HandlerLib.isElemenentBinned(event)) { + // if the region was over rubbish bin, remove it + DecorationLib.removeLabel(label); + } else { + HandlerLib.dropObject(label); + } + } + HandlerLib.dragItemsStartHandler(label, this, mouseupHandler, event, x + canvas.scrollLeft(), y + canvas.scrollTop()); + }, -/** - * Contains handlers for actions over Properties dialog. - */ -HandlerPropertyLib = { - - /** - * Makes properties dialog fully visible. - */ - approachPropertiesDialogHandler : function(event) { - // properties dialog is a singleton - var dialog = layout.propertiesDialog, - // do not run this method too often - thisRun = new Date().getTime(); - if (thisRun - dialog.data('lastRun') < layout.conf.propertiesDialogDimThrottle){ - return; - } - dialog.data('lastRun', thisRun); - - // is the dialog visible at all? - if (layout.selectedObject) { - // calculate dim/show threshold - var dialogPosition = dialog.offset(), - dialogStartX = dialogPosition.left, - dialogStartY = dialogPosition.top, - dialogEndX = dialogStartX + dialog.width(), - dialogEndY = dialogStartY + dialog.height(), - dimTreshold = layout.conf.propertiesDialogDimThreshold, - tooFarX = event.pageX < dialogStartX - dimTreshold || event.pageX > dialogEndX + dimTreshold, - tooFarY = event.pageY < dialogStartY - dimTreshold || event.pageY > dialogEndY + dimTreshold, - opacity = tooFarX || tooFarY ? layout.conf.propertiesDialogDimOpacity : 1; - dialog.css('opacity', opacity); - } - } -}, + /** + * Start resizing a region. + */ + resizeRegionStartHandler : function(event) { + // otherwise mousedown handler (dragging) can be triggered + event.stopImmediatePropagation(); + event.preventDefault(); + HandlerLib.resetCanvasMode(); + var region = ActivityLib.getParentObject(this); -/** - * Contains handlers for actions over Transitions - */ -HandlerTransitionLib = { + canvas.mousemove(function(event){ + HandlerDecorationLib.resizeRegionMoveHandler(region, event); + }) + .mouseup(function(){ + HandlerLib.resetCanvasMode(true); + ActivityLib.addSelectEffect(region, true); + }); - /** - * Start drawing a transition. - */ - drawTransitionStartHandler : function(activity, event, x, y) { - if (activity.fromTransition && !(activity instanceof ActivityDefs.BranchingEdgeActivity)) { - layout.infoDialog.data('show')(LABELS.TRANSITION_FROM_EXISTS_ERROR); + GeneralLib.setModified(true); + }, + + + /** + * Keep resising a region. + */ + resizeRegionMoveHandler : function(region, event){ + var translatedEvent = GeneralLib.translateEventOnCanvas(event), + x = translatedEvent[0], + y = translatedEvent[1]; + + // keep the initial coordinates and adjust end coordinates + region.draw(null, null, x, y); } - - HandlerLib.resetCanvasMode(); - - var startX = x + canvas.scrollLeft() - canvas.offset().left, - startY = y + canvas.scrollTop() - canvas.offset().top; - - canvas.mousemove(function(event){ - HandlerTransitionLib.drawTransitionMoveHandler(activity, event, startX, startY); - }) - .mouseup(function(event){ - HandlerTransitionLib.drawTransitionEndHandler(activity, event); - }); }, - - + + + /** - * Keep drawing a transition. + * Contains handlers for actions over Properties dialog. */ - drawTransitionMoveHandler : function(activity, event, startX, startY) { - // remove the temporary transition (dashed line) - if (activity.tempTransition) { - activity.tempTransition.remove(); - activity.tempTransition = null; - } - - var translatedEvent = GeneralLib.translateEventOnCanvas(event), - endX = translatedEvent[0], - endY = translatedEvent[1]; - // draw a temporary transition so user sees what he is doing - activity.tempTransition = Snap.set(); - activity.tempTransition.push(paper.circle(startX, startY, 3).attr({ - 'stroke' : layout.colors.transition - })); - activity.tempTransition.push(paper.path(Snap.format('M {startX} {startY} L {endX} {endY}', - { - 'startX' : startX, - 'startY' : startY, - 'endX' : endX, - 'endY' : endY - })) - .addClass('svg-transition-draw') - ); - }, - - - /** - * Finalise transition drawing. - */ - drawTransitionEndHandler : function(activity, event) { - // prevent triggering event on several activity items; we just need it on transition - event.stopImmediatePropagation(); - event.preventDefault(); - - //remove the temporary transition (dashed line) - if (activity.tempTransition) { - activity.tempTransition.remove(); - activity.tempTransition = null; - } - var endActivity = null, - targetElement = Snap.getElementByPoint(event.pageX, event.pageY); - if (targetElement) { - endActivity = ActivityLib.getParentObject(targetElement); - } + HandlerPropertyLib = { - if (endActivity && activity != endActivity) { - ActivityLib.addTransition(activity, endActivity); + /** + * Makes properties dialog fully visible. + */ + approachPropertiesDialogHandler : function(event) { + // properties dialog is a singleton + var dialog = layout.propertiesDialog, + // do not run this method too often + thisRun = new Date().getTime(); + if (thisRun - dialog.data('lastRun') < layout.conf.propertiesDialogDimThrottle){ + return; + } + dialog.data('lastRun', thisRun); + + // is the dialog visible at all? + if (layout.selectedObject) { + // calculate dim/show threshold + var dialogPosition = dialog.offset(), + dialogStartX = dialogPosition.left, + dialogStartY = dialogPosition.top, + dialogEndX = dialogStartX + dialog.width(), + dialogEndY = dialogStartY + dialog.height(), + dimTreshold = layout.conf.propertiesDialogDimThreshold, + tooFarX = event.pageX < dialogStartX - dimTreshold || event.pageX > dialogEndX + dimTreshold, + tooFarY = event.pageY < dialogStartY - dimTreshold || event.pageY > dialogEndY + dimTreshold, + opacity = tooFarX || tooFarY ? layout.conf.propertiesDialogDimOpacity : 1; + + dialog.css('opacity', opacity); + } } - - HandlerLib.resetCanvasMode(true); }, - - - + + + /** - * Starts dragging a transition. + * Contains handlers for actions over Transitions */ - transitionMousedownHandler : function(event, x, y){ - var transition = ActivityLib.getParentObject(this); - // allow transition dragging - var mouseupHandler = function(event){ - if (HandlerLib.isElemenentBinned(event)) { - if (transition.toActivity.readOnly || transition.fromActivity.readOnly) { - layout.infoDialog.data('show')(LABELS.LIVEEDIT_REMOVE_TRANSITION_ERROR); - // just draw it again in the original place - transition.draw(); + HandlerTransitionLib = { + + /** + * Start drawing a transition. + */ + drawTransitionStartHandler : function(activity, event, x, y) { + if (activity.fromTransition && !(activity instanceof ActivityDefs.BranchingEdgeActivity)) { + layout.infoDialog.data('show')(LABELS.TRANSITION_FROM_EXISTS_ERROR); + } + + HandlerLib.resetCanvasMode(); + + var startX = x + canvas.scrollLeft() - canvas.offset().left, + startY = y + canvas.scrollTop() - canvas.offset().top; + + canvas.mousemove(function(event){ + HandlerTransitionLib.drawTransitionMoveHandler(activity, event, startX, startY); + }) + .mouseup(function(event){ + HandlerTransitionLib.drawTransitionEndHandler(activity, event); + }); + }, + + + /** + * Keep drawing a transition. + */ + drawTransitionMoveHandler : function(activity, event, startX, startY) { + // remove the temporary transition (dashed line) + if (activity.tempTransition) { + activity.tempTransition.remove(); + activity.tempTransition = null; + } + + var translatedEvent = GeneralLib.translateEventOnCanvas(event), + endX = translatedEvent[0], + endY = translatedEvent[1]; + // draw a temporary transition so user sees what he is doing + activity.tempTransition = Snap.set(); + activity.tempTransition.push(paper.circle(startX, startY, 3).attr({ + 'stroke' : layout.colors.transition + })); + activity.tempTransition.push(paper.path(Snap.format('M {startX} {startY} L {endX} {endY}', + { + 'startX' : startX, + 'startY' : startY, + 'endX' : endX, + 'endY' : endY + })) + .addClass('svg-transition-draw') + ); + }, + + + /** + * Finalise transition drawing. + */ + drawTransitionEndHandler : function(activity, event) { + // prevent triggering event on several activity items; we just need it on transition + event.stopImmediatePropagation(); + event.preventDefault(); + + //remove the temporary transition (dashed line) + if (activity.tempTransition) { + activity.tempTransition.remove(); + activity.tempTransition = null; + } + var endActivity = null, + targetElement = Snap.getElementByPoint(event.pageX, event.pageY); + if (targetElement) { + endActivity = ActivityLib.getParentObject(targetElement); + } + + if (endActivity && activity != endActivity) { + ActivityLib.addTransition(activity, endActivity); + } + + HandlerLib.resetCanvasMode(true); + }, + + + + /** + * Starts dragging a transition. + */ + transitionMousedownHandler : function(event, x, y){ + var transition = ActivityLib.getParentObject(this); + // allow transition dragging + var mouseupHandler = function(event){ + if (HandlerLib.isElemenentBinned(event)) { + if (transition.toActivity.readOnly || transition.fromActivity.readOnly) { + layout.infoDialog.data('show')(LABELS.LIVEEDIT_REMOVE_TRANSITION_ERROR); + // just draw it again in the original place + transition.draw(); + } else { + // if the transition was over rubbish bin, remove it + ActivityLib.removeTransition(transition); + } } else { - // if the transition was over rubbish bin, remove it - ActivityLib.removeTransition(transition); + transition.draw(); } - } else { - transition.draw(); } + + HandlerLib.dragItemsStartHandler(transition, this, mouseupHandler, event, x + canvas.scrollLeft(), y + canvas.scrollTop()); } - - HandlerLib.dragItemsStartHandler(transition, this, mouseupHandler, event, x + canvas.scrollLeft(), y + canvas.scrollTop()); - } -}; + }; \ No newline at end of file