adf311b1331d1953936c24724e64fc98d2e8d2dd
[tine20] / tine20 / Calendar / js / MainScreenCenterPanel.js
1 /* 
2  * Tine 2.0
3  * 
4  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
5  * @author      Cornelius Weiss <c.weiss@metaways.de>
6  * @copyright   Copyright (c) 2007-2013 Metaways Infosystems GmbH (http://www.metaways.de)
7  */
8
9 /* global Ext, Tine */
10
11 Ext.ns('Tine.Calendar');
12
13 Tine.Calendar.MainScreenCenterPanel = Ext.extend(Ext.Panel, {
14     /**
15      * @cfg {String} activeView
16      */
17     activeView: 'weekSheet',
18     
19     startDate: new Date().clearTime(),
20     
21     /**
22      * $property Object view -> startdate
23      */
24     startDates: null,
25     
26     /**
27      * @cfg {String} loadMaskText
28      * i18n._('Loading events, please wait...')
29      */
30     loadMaskText: 'Loading events, please wait...',
31     
32     /**
33      * @cfg {Number} autoRefreshInterval (seconds)
34      */
35     autoRefreshInterval: 300,
36
37     /**
38      * @cfg {Boolean} initialLoadAfterRender
39      */
40     initialLoadAfterRender: true,
41
42     /**
43      * @property autoRefreshTask
44      * @type Ext.util.DelayedTask
45      */
46     autoRefreshTask: null,
47     
48     /**
49      * add records from other applications using the split add button
50      * - activated by default
51      * 
52      * @type Bool
53      * @property splitAddButton
54      */
55     splitAddButton: true,
56     
57     /**
58      * default print mode
59      * 
60      * @type String {sheet|grid}
61      * @property defaultPrintMode
62      */
63     defaultPrintMode: 'sheet',
64     
65     periodRe: /^(day|week|month|year)/i,
66     presentationRe: /(sheet|grid|timeline)$/i,
67     
68     calendarPanels: {},
69     
70     border: false,
71     layout: 'border',
72     
73     stateful: true,
74     stateId: 'cal-mainscreen',
75     stateEvents: ['changeview'],
76     
77     getState: function () {
78         return Ext.copyTo({}, this, 'activeView');
79     },
80     
81     applyState: Ext.emptyFn,
82     
83     initComponent: function () {
84         var me = this;
85         
86         this.addEvents(
87         /**
88          * @event changeview
89          * fired if an event got clicked
90          * @param {Tine.Calendar.MainScreenCenterPanel} mspanel
91          * @param {String} view
92          */
93         'changeview');
94         
95         this.recordClass = Tine.Calendar.Model.Event;
96         
97         this.app = Tine.Tinebase.appMgr.get('Calendar');
98         
99         // init some translations
100         this.i18nRecordName = this.app.i18n.n_hidden(this.recordClass.getMeta('recordName'), this.recordClass.getMeta('recordsName'), 1);
101         this.i18nRecordsName = this.app.i18n._hidden(this.recordClass.getMeta('recordsName'));
102         this.i18nContainerName = this.app.i18n.n_hidden(this.recordClass.getMeta('containerName'), this.recordClass.getMeta('containersName'), 1);
103         this.i18nContainersName = this.app.i18n._hidden(this.recordClass.getMeta('containersName'));
104         
105         this.loadMaskText = this.app.i18n._hidden(this.loadMaskText);
106         
107         var state = Ext.state.Manager.get(this.stateId, {});
108         Ext.apply(this, state);
109         
110         this.defaultFilters = [
111             {field: 'attender', operator: 'in', value: [Ext.apply(Tine.Calendar.Model.Attender.getDefaultData(), {
112                 user_id: Tine.Tinebase.registry.get('currentAccount')
113             })]},
114             {field: 'attender_status', operator: 'notin', value: ['DECLINED']}
115         ];
116         this.filterToolbar = this.getFilterToolbar({
117             onFilterChange: this.refresh.createDelegate(this, [false]),
118             getAllFilterData: this.getAllFilterData.createDelegate(this)
119         });
120         
121         this.filterToolbar.getQuickFilterPlugin().criteriaIgnores.push(
122             {field: 'period'},
123             {field: 'grants'}
124         );
125         
126         this.startDates = [];
127         this.initActions();
128         this.initLayout();
129         
130         // init autoRefresh
131         this.autoRefreshTask = new Ext.util.DelayedTask(this.refresh, this, [{
132             refresh: true,
133             autoRefresh: true
134         }]);
135         
136         Tine.Calendar.MainScreenCenterPanel.superclass.initComponent.call(this);
137     },
138     
139     initActions: function () {
140         this.action_editInNewWindow = new Ext.Action({
141             requiredGrant: 'readGrant',
142             text: this.i18nEditActionText ? this.app.i18n._hidden(this.i18nEditActionText) : String.format(i18n._hidden('Edit {0}'), this.i18nRecordName),
143             disabled: true,
144             handler: this.onEditInNewWindow.createDelegate(this, ["edit"], 0),
145             iconCls: 'action_edit'
146         });
147         
148         this.action_addInNewWindow = new Ext.Action({
149             requiredGrant: 'addGrant',
150             text: this.i18nAddActionText ? this.app.i18n._hidden(this.i18nAddActionText) : String.format(i18n._hidden('Add {0}'), this.i18nRecordName),
151             handler: this.onEditInNewWindow.createDelegate(this, ["add"], 0),
152             iconCls: 'action_add'
153         });
154         
155         this.action_cut = new Ext.Action({
156             requiredGrant: 'deleteGrant',
157             text: this.app.i18n._('Cut event'),
158             handler: this.onCutEvent.createDelegate(this),
159             iconCls: 'action_cut'
160         });
161
162         this.action_copy_to = new Ext.Action({
163             requiredGrant: 'deleteGrant',
164             text: this.app.i18n._('Copy Event to clipboard'),
165             handler: this.onCopyToEvent.createDelegate(this),
166             iconCls: 'action_editcopy'
167         });
168         
169         this.action_cancelPasting = new Ext.Action({
170             requiredGrant: 'deleteGrant',
171             text: this.app.i18n._('Stop cut / copy & paste'),
172             handler: this.onCutCancelEvent.createDelegate(this),
173             iconCls: 'action_cut_break'
174         });
175         
176         // note: unprecise plural form here, but this is hard to change
177         this.action_deleteRecord = new Ext.Action({
178             requiredGrant: 'deleteGrant',
179             allowMultiple: true,
180             singularText: this.i18nDeleteActionText ? i18nDeleteActionText[0] : String.format(i18n.n_hidden('Delete {0}', 'Delete {0}', 1), this.i18nRecordName),
181             pluralText: this.i18nDeleteActionText ? i18nDeleteActionText[1] : String.format(i18n.n_hidden('Delete {0}', 'Delete {0}', 1), this.i18nRecordsName),
182             translationObject: this.i18nDeleteActionText ? this.app.i18n : i18n,
183             text: this.i18nDeleteActionText ? this.i18nDeleteActionText[0] : String.format(i18n.n_hidden('Delete {0}', 'Delete {0}', 1), this.i18nRecordName),
184             handler: this.onDeleteRecords,
185             disabled: true,
186             iconCls: 'action_delete',
187             scope: this
188         });
189         
190         this.actions_print = new Ext.Action({
191             requiredGrant: 'readGrant',
192             text: this.app.i18n._('Print Page'),
193             handler: this.onPrint.createDelegate(this, []),
194             iconCls:'action_print',
195             scope: this,
196             listeners: {
197                 arrowclick: this.onPrintMenuClick.createDelegate(this)
198             },
199             menu:{
200                 items:[{
201                     text: this.app.i18n._('Grid'),
202                     iconCls: 'cal-grid-view-type',
203                     handler: this.onPrint.createDelegate(this, ['grid'])
204                 }, {
205                     text: this.app.i18n._('Sheet'),
206                     iconCls: 'cal-week-view',
207                     handler: this.onPrint.createDelegate(this, ['sheet']),
208                     disabled: Ext.isIE || Ext.isNewIE
209                 }]
210             }
211         });
212         
213         this.showSheetView = new Ext.Button({
214             pressed: this.isActiveView('Sheet'),
215             scale: 'medium',
216             minWidth: 60,
217             rowspan: 2,
218             iconAlign: 'top',
219             requiredGrant: 'readGrant',
220             text: this.app.i18n._('Sheet'),
221             handler: this.changeView.createDelegate(this, ["sheet"]),
222             iconCls:'cal-sheet-view-type',
223             xtype: 'tbbtnlockedtoggle',
224             toggleGroup: 'Calendar_Toolbar_tgViewTypes',
225             scope: this
226         });
227         
228         this.showGridView = new Ext.Button({
229             pressed: this.isActiveView('Grid'),
230             scale: 'medium',
231             minWidth: 60,
232             rowspan: 2,
233             iconAlign: 'top',
234             requiredGrant: 'readGrant',
235             text: this.app.i18n._('Grid'),
236             handler: this.changeView.createDelegate(this, ["grid"]),
237             iconCls:'cal-grid-view-type',
238             xtype: 'tbbtnlockedtoggle',
239             toggleGroup: 'Calendar_Toolbar_tgViewTypes',
240             scope: this
241         });
242
243         this.showTimelineView = new Ext.Button({
244             pressed: this.isActiveView('Timeline'),
245             scale: 'medium',
246             minWidth: 60,
247             rowspan: 2,
248             iconAlign: 'top',
249             requiredGrant: 'readGrant',
250             text: this.app.i18n._('Timeline'),
251             handler: this.changeView.createDelegate(this, ["timeline"]),
252             iconCls:'cal-timeline-view-type',
253             xtype: 'tbbtnlockedtoggle',
254             toggleGroup: 'Calendar_Toolbar_tgViewTypes',
255             scope: this
256         });
257
258         this.showDayView = new Ext.Toolbar.Button({
259             pressed: this.isActiveView('Day'),
260             text: this.app.i18n._('Day'),
261             iconCls: 'cal-day-view',
262             xtype: 'tbbtnlockedtoggle',
263             handler: this.changeView.createDelegate(this, ["day"]),
264             enableToggle: true,
265             toggleGroup: 'Calendar_Toolbar_tgViews'
266         });
267         this.showWeekView = new Ext.Toolbar.Button({
268             pressed: this.isActiveView('Week'),
269             text: this.app.i18n._('Week'),
270             iconCls: 'cal-week-view',
271             xtype: 'tbbtnlockedtoggle',
272             handler: this.changeView.createDelegate(this, ["week"]),
273             enableToggle: true,
274             toggleGroup: 'Calendar_Toolbar_tgViews'
275         });
276         this.showMonthView = new Ext.Toolbar.Button({
277             pressed: this.isActiveView('Month'),
278             text: this.app.i18n._('Month'),
279             iconCls: 'cal-month-view',
280             xtype: 'tbbtnlockedtoggle',
281             handler: this.changeView.createDelegate(this, ["month"]),
282             enableToggle: true,
283             toggleGroup: 'Calendar_Toolbar_tgViews'
284         });
285         this.showYearView = new Ext.Toolbar.Button({
286             pressed: String(this.activeView).match(/^year/i),
287             text: this.app.i18n._('Year'),
288             iconCls: 'cal-year-view',
289             xtype: 'tbbtnlockedtoggle',
290             handler: this.changeView.createDelegate(this, ["year"]),
291             enableToggle: true,
292             toggleGroup: 'Calendar_Toolbar_tgViews'
293         });
294         this.toggleFullScreen = new Ext.Toolbar.Button({
295             text: '\u2197',
296             scope: this,
297             handler: function() {
298                 if (this.ownerCt.ref == 'tineViewportMaincardpanel') {
299                     Tine.Tinebase.viewport.tineViewportMaincardpanel.remove(this, false);
300                     Tine.Tinebase.viewport.tineViewportMaincardpanel.layout.setActiveItem(Tine.Tinebase.viewport.tineViewportMaincardpanel.layout.lastActiveItem);
301                     this.originalOwner.add(this);
302                     this.originalOwner.layout.setActiveItem(this);
303                     this.toggleFullScreen.setText('\u2197');
304                     this.southPanel.expand();
305                 } else {
306                     this.originalOwner = this.ownerCt;
307                     this.originalOwner.remove(this, false);
308                     Tine.Tinebase.viewport.tineViewportMaincardpanel.layout.lastActiveItem = Tine.Tinebase.viewport.tineViewportMaincardpanel.layout.activeItem;
309                     Tine.Tinebase.viewport.tineViewportMaincardpanel.add(this);
310                     Tine.Tinebase.viewport.tineViewportMaincardpanel.layout.setActiveItem(this);
311                     this.toggleFullScreen.setText('\u2199');
312                     this.southPanel.collapse();
313                 }
314             }
315         });
316         
317        this.action_import = new Ext.Action({
318             requiredGrant: 'addGrant',
319             text: this.app.i18n._('Import Events'),
320             disabled: false,
321             handler: this.onImport,
322             minWidth: 60,
323             requiredGrant: 'readGrant',
324             iconCls: 'action_import',
325             scope: this,
326             allowMultiple: true
327         });
328
329         this.action_export = Tine.widgets.exportAction.getExportButton(
330             Tine.Calendar.Model.Event, {
331                 getExportOptions: (function() {
332                     return {
333                         filter: this.getAllFilterData({
334                             noPeriodFilter: false
335                         })
336                     }
337                 }).createDelegate(this)
338         }, Tine.widgets.exportAction.SCOPE_MULTI);
339         // FIXME: how can this be null? is it ok??
340         if (this.action_export) {
341             this.action_export.setDisabled(false);
342             this.action_export.setText(this.action_export.initialConfig.pluralText);
343         }
344
345         this.changeViewActions = [
346             this.showDayView,
347             this.showWeekView,
348             this.showMonthView
349         ];
350
351         if (this.app.featureEnabled('featureYearView')) {
352             this.changeViewActions.push(this.showYearView);
353         }
354
355         this.changeViewActions.push(this.toggleFullScreen);
356
357         this.recordActions = [
358             this.action_editInNewWindow,
359             this.action_deleteRecord
360         ];
361         
362         this.actionUpdater = new  Tine.widgets.ActionUpdater({
363             actions: this.recordActions
364         });
365     },
366
367     /**
368      * get current view type (Grid/Day/Week/...)
369      *
370      * @param {String} view
371      * @returns {Boolean}
372      */
373     isActiveView: function(view)
374     {
375         var re = new RegExp(String(view).toLowerCase(), 'i');
376         return String(this.activeView).match(re);
377     },
378     
379     /**
380      * returns the paste action
381      * 
382      * @param {Date} datetime
383      * @param {Tine.Calendar.Model.Event} event
384      */
385     getPasteAction: function(datetime, event) {
386         var shortSummary = Ext.util.Format.ellipsis(event.get('summary'), 15);
387         return new Ext.Action({
388             requiredGrant: 'addGrant',
389             text: String.format(this.app.i18n._('Paste event "{0}"'), shortSummary),
390             handler: this.onPasteEvent.createDelegate(this, [datetime]),
391             iconCls: 'action_paste'
392         });
393     },
394         
395     getActionToolbar: Tine.widgets.grid.GridPanel.prototype.getActionToolbar,
396     onActionToolbarResize: Tine.widgets.grid.GridPanel.prototype.onActionToolbarResize,
397     
398     getActionToolbarItems: function() {
399         var items = [this.action_import];
400         if (this.action_export) {
401             items = items.concat(this.action_export);
402         }
403         return [{
404             xtype: 'buttongroup',
405             columns: 1,
406             rows: 2,
407             frame: false,
408             items: items
409         }, {xtype: 'tbseparator'}, {
410             xtype: 'buttongroup',
411             frame: false,
412             plugins: [{
413                 ptype: 'ux.itemregistry',
414                 key:   'Calendar-MainScreenPanel-ViewBtnGrp'
415             }],
416             items: [
417                 this.showSheetView,
418                 this.showTimelineView,
419                 this.showGridView
420             ]
421         }];
422     },
423     
424     /**
425      * @private
426      * 
427      * NOTE: Order of items matters! Ext.Layout.Border.SplitRegion.layout() does not
428      *       fence the rendering correctly, as such it's impotant, so have the ftb
429      *       defined after all other layout items
430      */
431     initLayout: function () {
432         this.items = [{
433             region: 'center',
434             layout: 'card',
435             activeItem: 0,
436             border: false,
437             items: [this.getCalendarPanel(this.activeView)]
438         }];
439         
440         // add detail panel
441         if (this.detailsPanel) {
442             this.items.push({
443                 region: 'south',
444                 ref: 'southPanel',
445                 border: false,
446                 collapsible: true,
447                 collapseMode: 'mini',
448                 header: false,
449                 split: true,
450                 layout: 'fit',
451                 height: this.detailsPanel.defaultHeight ? this.detailsPanel.defaultHeight : 125,
452                 items: this.detailsPanel
453                 
454             });
455             //this.detailsPanel.doBind(this.activeView);
456         }
457         
458         // add filter toolbar
459         if (this.filterToolbar) {
460             this.items.push({
461                 region: 'north',
462                 border: false,
463                 items: this.filterToolbar,
464                 listeners: {
465                     scope: this,
466                     afterlayout: function (ct) {
467                         ct.suspendEvents();
468                         ct.setHeight(this.filterToolbar.getHeight());
469                         ct.ownerCt.layout.layout();
470                         ct.resumeEvents();
471                     }
472                 }
473             });
474         }
475     },
476     
477     /**
478      * import events
479      * 
480      * @param {Button} btn 
481      */
482     onImport: function(btn) {
483         var popupWindow = Tine.Calendar.ImportDialog.openWindow({
484             appName: 'Calendar',
485             modelName: 'Event',
486             defaultImportContainer: this.app.getMainScreen().getWestPanel().getContainerTreePanel().getDefaultContainer('defaultContainer'),
487             
488             ignoreConflicts: true,
489             doTryRun: false,
490             
491             
492             // update grid after import
493             listeners: {
494                 scope: this,
495                 'finish': function() {
496                     this.refresh();
497                 }
498             }
499         });
500     },
501     
502     /**
503      * @private
504      */
505     onRender: function(ct, position) {
506         Tine.Calendar.MainScreenCenterPanel.superclass.onRender.apply(this, arguments);
507
508         this.loadMask = new Ext.LoadMask(this.body, {msg: this.loadMaskText});
509
510         var defaultFavorite = Tine.widgets.persistentfilter.model.PersistentFilter.getDefaultFavorite(this.app.appName, this.recordClass.prototype.modelName);
511
512         if (this.initialLoadAfterRender) {
513             if (defaultFavorite) {
514                 this.selectFavorite(defaultFavorite);
515             } else {
516                 this.refresh();
517             }
518         }
519     },
520
521     selectFavorite: function(favorite) {
522         var favorite = favorite || Tine.widgets.persistentfilter.model.PersistentFilter.getDefaultFavorite(this.app.appName, this.recordClass.prototype.modelName),
523             favoritesPanel  = this.app.getMainScreen().getWestPanel().getFavoritesPanel();
524
525         favoritesPanel.selectFilter(favorite);
526     },
527
528     
529     getViewParts: function (view) {
530         view = String(view);
531         
532         var activeView = String(this.activeView),
533             periodMatch = view.match(this.periodRe),
534             period = Ext.isArray(periodMatch) ? periodMatch[0] : null,
535             activePeriodMatch = activeView.match(this.periodRe),
536             activePeriod = Ext.isArray(activePeriodMatch) ? activePeriodMatch[0] : 'week',
537             presentationMatch = view.match(this.presentationRe),
538             presentation = Ext.isArray(presentationMatch) ? presentationMatch[0] : null,
539             activePresentationMatch = activeView.match(this.presentationRe),
540             activePresentation = Ext.isArray(activePresentationMatch) ? activePresentationMatch[0] : 'sheet';
541             
542         return {
543             period: period ? period : activePeriod,
544             presentation: presentation ? presentation : activePresentation,
545             toString: function() {return this.period + Ext.util.Format.capitalize(this.presentation);}
546         };
547     },
548     
549     changeView: function (view, startDate) {
550         // autocomplete view
551         var viewParts = this.getViewParts(view);
552         view = viewParts.toString();
553         
554         Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::changeView(' + view + ',' + startDate + ')');
555         
556         // save current startDate
557         this.startDates[this.activeView] = this.startDate.clone();
558         
559         if (startDate && Ext.isDate(startDate)) {
560             this.startDate = startDate.clone();
561         } else {
562             // see if a recent startDate of that view fits
563             var lastStartDate = this.startDates[view],
564                 currentPeriod = this.getCalendarPanel(this.activeView).getView().getPeriod();
565                 
566             if (Ext.isDate(lastStartDate) && lastStartDate.between(currentPeriod.from, currentPeriod.until)) {
567                 this.startDate = this.startDates[view].clone();
568             }
569         }
570         
571         var panel = this.getCalendarPanel(view);
572         var cardPanel = this.items.first();
573         
574         if (panel.rendered) {
575             cardPanel.layout.setActiveItem(panel.id);
576         } else {
577             cardPanel.add(panel);
578             cardPanel.layout.setActiveItem(panel.id);
579             cardPanel.doLayout();
580         }
581         
582         this.activeView = view;
583         
584         // move around changeViewButtons
585         var rightRow = Ext.get(Ext.DomQuery.selectNode('tr[class=x-toolbar-right-row]', panel.tbar.dom));
586         
587         for (var i = this.changeViewActions.length - 1; i >= 0; i--) {
588             rightRow.insertFirst(this.changeViewActions[i].getEl().parent().dom);
589         }
590         this['show' + Ext.util.Format.capitalize(viewParts.period) +  'View'].toggle(true);
591         this['show' + Ext.util.Format.capitalize(viewParts.presentation) +  'View'].toggle(true);
592         
593         // update actions
594         this.updateEventActions();
595         
596         // update data
597         panel.getView().updatePeriod({from: this.startDate});
598         panel.getStore().load({});
599         
600         this.fireEvent('changeview', this, view);
601     },
602
603     /**
604      * returns all filter data for current view
605      */
606     getAllFilterData: function (options) {
607         var store = this.getCalendarPanel(this.activeView).getStore();
608         
609         options = Ext.apply({
610             refresh: true, // ommit loadMask
611             noPeriodFilter: true
612         }, options || {});
613         this.onStoreBeforeload(store, options);
614         
615         return options.params.filter;
616     },
617     
618     getCustomfieldFilters: Tine.widgets.grid.GridPanel.prototype.getCustomfieldFilters,
619     
620     getFilterToolbar: Tine.widgets.grid.GridPanel.prototype.getFilterToolbar,
621     
622     /**
623      * returns store of currently active view
624      */
625     getStore: function () {
626         return this.getCalendarPanel(this.activeView).getStore();
627     },
628     
629     /**
630      * onContextMenu
631      * 
632      * @param {Event} e
633      */
634     onContextMenu: function (e) {
635         e.stopEvent();
636
637         var view = this.getCalendarPanel(this.activeView).getView();
638         var event = view.getTargetEvent(e);
639         var datetime = view.getTargetDateTime(e);
640         
641         if (event && event.id.match(/new-ext-gen/) ) {
642             // new event, no ctx menu possible atm
643             // @see 0008948: deactivate context menu on new events
644             return;
645         }
646         
647         var addAction, responseAction, copyAction;
648
649         if (datetime || event) {
650             var dtStart = datetime || event.get('dtstart').clone();
651             if (dtStart.format('H:i') === '00:00') {
652                 dtStart = dtStart.add(Date.HOUR, 9);
653             }
654             
655             addAction = {
656                 text: this.i18nAddActionText ? this.app.i18n._hidden(this.i18nAddActionText) : String.format(i18n._hidden('Add {0}'), this.i18nRecordName),
657                 handler: this.onEditInNewWindow.createDelegate(this, ["add", null, null, {dtStart: dtStart, is_all_day_event: datetime && datetime.is_all_day_event}]),
658                 iconCls: 'action_add'
659             };
660             
661             // assemble event actions
662             if (event) {
663                 responseAction = this.getResponseAction(event);
664                 copyAction = this.getCopyAction(event);
665             }
666         } else {
667             addAction = this.action_addInNewWindow;
668         }
669         
670         if (event) {
671             view.getSelectionModel().select(event, e, e.ctrlKey);
672         } else {
673             view.getSelectionModel().clearSelections();
674         }
675
676         var menuitems = this.recordActions.concat(addAction, responseAction || [], copyAction || []);
677         
678         if (event) {
679             this.action_copy_to.setDisabled(event.isRecurInstance() || event.isRecurException() || event.isRecurBase());
680             menuitems = menuitems.concat(['-', this.action_cut, this.action_copy_to, '-']);
681         } else if (Tine.Tinebase.data.Clipboard.has('Calendar', 'Event')) {
682             menuitems = menuitems.concat(['-', this.getPasteAction(datetime, Tine.Tinebase.data.Clipboard.pull('Calendar', 'Event', true)), this.action_cancelPasting, '-']);
683         }
684
685         var ctxMenu = new Ext.menu.Menu({
686             plugins: [{
687                 ptype: 'ux.itemregistry',
688                 key:   'Calendar-MainScreenPanel-ContextMenu',
689                 config: {
690                     event: event,
691                     datetime: datetime
692                 }
693             },
694             // to allow gridpanel hooks (like email compose)
695             {
696                 ptype: 'ux.itemregistry',
697                 key:   'Calendar-Event-GridPanel-ContextMenu'
698             }, {
699                 ptype: 'ux.itemregistry',
700                 key:   'Tinebase-MainContextMenu'
701             }],
702             items: menuitems
703         });
704         ctxMenu.showAt(e.getXY());
705     },
706     
707     /**
708      * get response action
709      * 
710      * @param {Tine.Calendar.Model.Event} event
711      * @return {Object}
712      */
713     getResponseAction: function(event) {
714         var statusStore = Tine.Tinebase.widgets.keyfield.StoreMgr.get('Calendar', 'attendeeStatus'),
715             myAttenderRecord = event.getMyAttenderRecord(),
716             myAttenderStatus = myAttenderRecord ? myAttenderRecord.get('status') : null,
717             myAttenderStatusRecord = statusStore.getById(myAttenderStatus),
718             responseAction = null;
719         
720         if (myAttenderRecord) {
721             responseAction = {
722                 text: this.app.i18n._('Set my response'),
723                 icon: myAttenderStatusRecord ? myAttenderStatusRecord.get('icon') : false,
724                 menu: []
725             };
726             
727             statusStore.each(function(status) {
728                 var isCurrent = myAttenderRecord.get('status') === status.id;
729                 
730                 // NOTE: we can't use checked items here as we use icons already
731                 responseAction.menu.push({
732                     text: status.get('i18nValue'),
733                     handler: this.setResponseStatus.createDelegate(this, [event, status.id]),
734                     icon: status.get('icon'),
735                     disabled: myAttenderRecord.get('status') === status.id
736                 });
737             }, this);
738         }
739         
740         return responseAction;
741     },
742
743     /**
744      * get copy action
745      * 
746      * @param {Tine.Calendar.Model.Event} event
747      * @return {Object}
748      */
749     getCopyAction: function(event) {
750         var copyAction = {
751             text: String.format(this.app.i18n._('Copy {0}'), this.i18nRecordName),
752             handler: this.onEditInNewWindow.createDelegate(this, ["copy", event]),
753             iconCls: 'action_editcopy',
754             // TODO allow to copy recurring events / exceptions
755             disabled: event.isRecurInstance() || event.isRecurException() || event.isRecurBase()
756         };
757         
758         return copyAction
759     },
760     
761     checkPastEvent: function(event, checkBusyConflicts, actionType, oldEvent) {
762         var start = event.get('dtstart').getTime();
763         var morning = new Date().clearTime().getTime();
764
765         switch (actionType) {
766             case 'update':
767                 var title = this.app.i18n._('Updating event in the past'),
768                     optionYes = this.app.i18n._('Update this event'),
769                     optionNo = this.app.i18n._('Do not update this event');
770                 break;
771             case 'add':
772             default:
773                 var title = this.app.i18n._('Creating event in the past'),
774                     optionYes = this.app.i18n._('Create this event'),
775                     optionNo = this.app.i18n._('Do not create this event');
776         }
777         
778         if(start < morning) {
779             Tine.widgets.dialog.MultiOptionsDialog.openWindow({
780                 title: title,
781                 height: 170,
782                 scope: this,
783                 options: [
784                     {text: optionYes, name: 'yes'},
785                     {text: optionNo, name: 'no'}
786                 ],
787                 
788                 handler: function(option) {
789                     try {
790                         switch (option) {
791                             case 'yes':
792                                 if (actionType == 'update') this.onUpdateEvent(event, true, oldEvent);
793                                 else this.onAddEvent(event, checkBusyConflicts, true);
794                                 break;
795                             case 'no':
796                             default:
797                                 try {
798                                     var panel = this.getCalendarPanel(this.activeView);
799                                     if(panel) {
800                                         var store = panel.getStore(),
801                                             view = panel.getView();
802                                     }
803                                 } catch(e) {
804                                     var panel = null, 
805                                         store = null,
806                                         view = null;
807                                 }
808                                 
809                                 if (actionType == 'add') {
810                                     if(store) store.remove(event);
811                                 } else {
812                                     if (view && view.rendered) {
813                                         this.loadMask.show();
814                                         store.reload();
815 //                                        TODO: restore original event so no reload is needed
816 //                                        var updatedEvent = event;
817 //                                        updatedEvent.dirty = false;
818 //                                        updatedEvent.editing = false;
819 //                                        updatedEvent.modified = null;
820 //                                        
821 //                                        store.replaceRecord(event, updatedEvent);
822 //                                        view.getSelectionModel().select(event);
823                                     }
824                                     this.setLoading(false);
825                                 }
826                         }
827                     } catch (e) {
828                         Tine.log.error('Tine.Calendar.MainScreenCenterPanel::checkPastEvent::handler');
829                         Tine.log.error(e);
830                     }
831                 }             
832             });
833         } else {
834             if (actionType == 'update') this.onUpdateEvent(event, true, oldEvent);
835             else this.onAddEvent(event, checkBusyConflicts, true);
836         }
837     },
838     
839     onAddEvent: function(event, checkBusyConflicts, pastChecked) {
840         if(!pastChecked) {
841             this.checkPastEvent(event, checkBusyConflicts, 'add');
842             return;
843         }
844         
845         this.setLoading(true);
846         
847         // remove temporary id
848         if (event.get('id').match(/new/)) {
849             event.set('id', '');
850         }
851         
852         if (event.isRecurBase()) {
853             if (this.loadMask) {
854                 this.loadMask.show();
855             }
856         }
857         
858         var panel = this.getCalendarPanel(this.activeView),
859             store = event.store,
860             view = panel.getView();
861         
862         Tine.Calendar.backend.saveRecord(event, {
863             scope: this,
864             success: function(createdEvent) {
865                 if (createdEvent.isRecurBase()) {
866                     store.load({refresh: true});
867                 } else {
868                     // store may be lost on conflict or else
869                     if (store) {
870                         store.replaceRecord(event, createdEvent);
871                         this.setLoading(false);
872                     } else {
873                         this.refresh();
874                     }
875                 }
876             },
877             failure: this.onProxyFail.createDelegate(this, [event], true)
878         }, {
879             checkBusyConflicts: checkBusyConflicts === false ? 0 : 1
880         });
881     },
882     
883     onUpdateEvent: function(event, pastChecked, oldEvent) {
884         this.setLoading(true);
885         
886         if(!pastChecked) {
887             this.checkPastEvent(event, null, 'update', oldEvent);
888             return;
889         }
890         
891         if (event.isRecurBase() && this.loadMask) {
892            this.loadMask.show();
893         }
894         
895         if (event.id && (event.isRecurInstance() || event.isRecurException() || (event.isRecurBase() && ! event.get('rrule').newrule))) {
896             
897             var options = [];
898             
899
900             options.push({text: this.app.i18n._('Update this event only'), name: 'this'});
901
902             // if
903             options.push({text: this.app.i18n._('Update this and all future events'), name: (event.isRecurBase() && ! event.get('rrule').newrule) ? 'series' : 'future'});
904             options.push({text: this.app.i18n._('Update whole series'), name: 'series'});
905             // endif
906
907             options.push({text: this.app.i18n._('Update nothing'), name: 'cancel'});
908             
909             Tine.widgets.dialog.MultiOptionsDialog.openWindow({
910                 title: this.app.i18n._('Update Event'),
911                 height: 170,
912                 scope: this,
913                 options: options,
914                 handler: function(option) {
915                     var store = event.store;
916
917                     if (! store) {
918                         store = this.getStore();
919                     }
920                     
921                     switch (option) {
922                         case 'series':
923                             if (this.loadMask) {
924                                 this.loadMask.show();
925                             }
926                             
927                             var options = {
928                                 scope: this,
929                                 success: function() {
930                                     store.load({refresh: true});
931                                 }
932                             };
933                             options.failure = this.onProxyFail.createDelegate(this, [event, Tine.Calendar.backend.updateRecurSeries.createDelegate(Tine.Calendar.backend, [event, false, options])], true);
934                             
935                             if (event.isRecurException()) {
936                                 Tine.Calendar.backend.saveRecord(event, options, {range: 'ALL', checkBusyConflicts: true});
937                             } else {
938                                 Tine.Calendar.backend.updateRecurSeries(event, true, options);
939                             }
940                             break;
941                             
942                         case 'this':
943                         case 'future':
944                             var options = {
945                                 scope: this,
946                                 success: function(updatedEvent) {
947                                     // NOTE: we also need to refresh for 'this' as otherwise
948                                     //       baseevent is not refreshed -> concurrency failures
949                                     store.load({refresh: true});
950                                 },
951                                 failure: this.onProxyFail.createDelegate(this, [event], true)
952                             };
953                             
954                             
955                             if (event.isRecurException()) {
956                                 var range = (option === 'this' ? 'THIS' : 'THISANDFUTURE');
957                                 options.failure = this.onProxyFail.createDelegate(this, [event, Tine.Calendar.backend.saveRecord.createDelegate(Tine.Calendar.backend, [event, options, {range: range, checkBusyConflicts: false}])], true);
958                                 Tine.Calendar.backend.saveRecord(event, options, {range: range, checkBusyConflicts: true});
959                             } else {
960                                 options.failure = this.onProxyFail.createDelegate(this, [event, Tine.Calendar.backend.createRecurException.createDelegate(Tine.Calendar.backend, [event, false, option == 'future', false, options])], true);
961                                 Tine.Calendar.backend.createRecurException(event, false, option === 'future', true, options);
962                             }
963                             break;
964                             
965                         default:
966                             if(this.loadMask) { //no loadMask called from another app
967                                 this.loadMask.show();
968                                 store.load({refresh: true});
969                             }
970                             break;
971                     }
972
973                 } 
974             });
975         } else {
976             this.onUpdateEventAction(event);
977         }
978     },
979     
980     onUpdateEventAction: function(event) {
981         var panel = this.getCalendarPanel(this.activeView),
982             store = panel.getStore(),
983             view = panel.getView();
984             
985         Tine.Calendar.backend.saveRecord(event, {
986             scope: this,
987             success: function(updatedEvent) {
988                 if (updatedEvent.isRecurBase()) {
989                     store.load({refresh: true});
990                 } else {
991                     this.congruenceFilterCheck(event, updatedEvent);
992                 }
993             },
994             failure: this.onProxyFail.createDelegate(this, [event], true)
995         }, {
996             checkBusyConflicts: 1
997         });
998     },
999
1000     /**
1001      * checks, if the last filter still matches after update
1002      * 
1003      * @param {Tine.Calendar.Model.Event} event
1004      * @param {Tine.Calendar.Model.Event} updatedEvent
1005      */
1006     congruenceFilterCheck: function(event, updatedEvent) {
1007         if (! (event.ui && event.ui.rendered)) {
1008             event.store.replaceRecord(event, updatedEvent);
1009             this.setLoading(false);
1010             return;
1011         }
1012         
1013         var filterData = this.getAllFilterData(),
1014             store = event.store;
1015         
1016         if (! store) {
1017             store = this.getStore();
1018         }
1019         
1020         filterData[0].filters[0].filters.push({field: 'id', operator: 'in', value: [ updatedEvent.get('id') ]});
1021         
1022         Tine.Calendar.searchEvents(filterData, {}, function(r) {
1023             if(r.totalcount == 0) {
1024                 updatedEvent.outOfFilter = true;
1025             }
1026             
1027             store.replaceRecord(event, updatedEvent);
1028             this.setLoading(false);
1029         }, this);
1030     },
1031     
1032     onDeleteRecords: function () {
1033         var panel = this.getCalendarPanel(this.activeView);
1034         var selection = panel.getSelectionModel().getSelectedEvents();
1035         
1036         var containsRecurBase = false,
1037             containsRecurInstance = false,
1038             containsRecurException = false;
1039         
1040         Ext.each(selection, function (event) {
1041             if(event.ui) event.ui.markDirty();
1042             if (event.isRecurInstance()) {
1043                 containsRecurInstance = true;
1044             }
1045             if (event.isRecurBase()) {
1046                 containsRecurBase = true;
1047             }
1048             if (event.isRecurException()) {
1049                 containsRecurException = true;
1050             }
1051         });
1052         
1053         if (selection.length > 1 && (containsRecurBase || containsRecurInstance)) {
1054             Ext.Msg.show({
1055                 title: this.app.i18n._('Please Change Selection'), 
1056                 msg: this.app.i18n._('Your selection contains recurring events. Recuring events must be deleted seperatly!'),
1057                 icon: Ext.MessageBox.INFO,
1058                 buttons: Ext.Msg.OK,
1059                 scope: this,
1060                 fn: function () {
1061                     this.onDeleteRecordsConfirmFail(panel, selection);
1062                 }
1063             });
1064             return;
1065         }
1066         
1067         if (selection.length === 1 && (containsRecurBase || containsRecurInstance || containsRecurException)) {
1068             this.deleteMethodWin = Tine.widgets.dialog.MultiOptionsDialog.openWindow({
1069                 title: this.app.i18n._('Delete Event'),
1070                 scope: this,
1071                 height: 170,
1072                 options: [
1073                     {text: this.app.i18n._('Delete this event only'), name: 'this'},
1074                     {text: this.app.i18n._('Delete this and all future events'), name: containsRecurBase ? 'all' : 'future'},
1075                     {text: this.app.i18n._('Delete whole series'), name: 'all'},
1076                     {text: this.app.i18n._('Delete nothing'), name: 'nothing'}
1077                 ],
1078                 handler: function (option) {
1079                     try {
1080                         switch (option) {
1081                             case 'all':
1082                             case 'this':
1083                             case 'future':
1084                                 panel.getTopToolbar().beforeLoad();
1085                                 if (option !== 'this') {
1086                                     this.loadMask.show();
1087                                 }
1088                                 
1089                                 var options = {
1090                                     scope: this,
1091                                     success: function () {
1092                                         if (option === 'this') {
1093                                             Ext.each(selection, function (event) {
1094                                                 panel.getStore().remove(event);
1095                                             });
1096                                         }
1097                                         this.refresh(true);
1098                                     }
1099                                 };
1100                                 
1101                                 if (containsRecurException) {
1102                                     var range = (option === 'future') ? 'THISANDFUTURE' : Ext.util.Format.uppercase(option);
1103                                     Tine.Calendar.backend.deleteRecords([selection[0]], options, {range: range});
1104                                 } else {
1105                                     if (option === 'all') {
1106                                         Tine.Calendar.backend.deleteRecurSeries(selection[0], options);
1107                                     } else {
1108                                         Tine.Calendar.backend.createRecurException(selection[0], true, option === 'future', false, options);
1109                                     }
1110                                 }
1111                                 break;
1112                             default:
1113                                 this.onDeleteRecordsConfirmFail(panel, selection);
1114                                 break;
1115                     }
1116                     
1117                     } catch (e) {
1118                         Tine.log.error('Tine.Calendar.MainScreenCenterPanel::onDeleteRecords::handle');
1119                         Tine.log.error(e);
1120                     }
1121                 }
1122             });
1123             return;
1124         }
1125         
1126         // else
1127         var i18nQuestion = String.format(this.app.i18n.ngettext('Do you really want to delete this event?', 'Do you really want to delete the {0} selected events?', selection.length), selection.length);
1128         Ext.MessageBox.confirm(i18n._hidden('Confirm'), i18nQuestion, function (btn) {
1129             if (btn === 'yes') {
1130                 this.onDeleteRecordsConfirmNonRecur(panel, selection);
1131             } else {
1132                 this.onDeleteRecordsConfirmFail(panel, selection);
1133             }
1134         }, this);
1135         
1136     },
1137     
1138     onDeleteRecordsConfirmNonRecur: function (panel, selection) {
1139         panel.getTopToolbar().beforeLoad();
1140         
1141         // create a copy of selection so selection changes don't affect this
1142         var sel = Ext.unique(selection);
1143                 
1144         var options = {
1145             scope: this,
1146             success: function () {
1147                 panel.getTopToolbar().onLoad();
1148                 Ext.each(sel, function (event) {
1149                     panel.getStore().remove(event);
1150                 });
1151             },
1152             failure: function () {
1153                 panel.getTopToolbar().onLoad();
1154                 Ext.MessageBox.alert(i18n._hidden('Failed'), String.format(this.app.i18n.n_('Failed to delete event', 'Failed to delete the {0} events', selection.length), selection.length));
1155             }
1156         };
1157         
1158         Tine.Calendar.backend.deleteRecords(selection, options);
1159     },
1160     
1161     onDeleteRecordsConfirmFail: function (panel, selection) {
1162         Ext.each(selection, function (event) {
1163             event.ui.clearDirty();
1164         });
1165     },
1166     
1167     /**
1168      * is called on action cut
1169      * 
1170      * @param {} action
1171      * @param {} event
1172      */
1173     onCutEvent: function(action, event) {
1174         var panel = this.getCalendarPanel(this.activeView);
1175         var selection = panel.getSelectionModel().getSelectedEvents();
1176         if (Ext.isArray(selection) && selection.length === 1) {
1177             event = selection[0];
1178         }
1179         if (event.ui) {
1180             event.ui.markDirty();
1181         }
1182         Tine.Tinebase.data.Clipboard.push(event);
1183     },
1184
1185     /**
1186      * Is called on copy to clipboard
1187      *
1188      * @param action
1189      * @param event
1190      */
1191     onCopyToEvent: function(action, event) {
1192         var panel = this.getCalendarPanel(this.activeView);
1193         var selection = panel.getSelectionModel().getSelectedEvents();
1194         if (Ext.isArray(selection) && selection.length === 1) {
1195             event = selection[0];
1196         }
1197
1198         event.isCopy = true;
1199         event.view = event.view ? event.view : panel.view;
1200
1201         Tine.Tinebase.data.Clipboard.push(event);
1202     },
1203     
1204     /**
1205      * is called on cancelling cut & paste
1206      */
1207     onCutCancelEvent: function() {
1208         var store = this.getStore();
1209         var ids = Tine.Tinebase.data.Clipboard.getIds('Calendar', 'Event');
1210         
1211         for (var index = 0; index < ids.length; index++) {
1212             var record = store.getAt(store.findExact('id', ids[index]));
1213             if (record && record.ui) {
1214                 record.ui.clearDirty();
1215             }
1216         }
1217
1218         Tine.Tinebase.data.Clipboard.clear('Calendar', 'Event');
1219     },
1220     
1221     /**
1222      * is called on action paste
1223      * 
1224      * @param {Date} datetime
1225      */
1226     onPasteEvent: function(datetime) {
1227         var record = Tine.Tinebase.data.Clipboard.pull('Calendar', 'Event'),
1228             isCopy = record.isCopy,
1229             sourceView = record.view,
1230             sourceRecord = record,
1231             sourceViewAttendee = sourceView.ownerCt.attendee,
1232             destinationView = this.getCalendarPanel(this.activeView).getView(),
1233             destinationViewAttendee = destinationView.ownerCt.attendee;
1234
1235         if (! record) {
1236             return;
1237         }
1238         
1239         var dtend   = record.get('dtend'),
1240             dtstart = record.get('dtstart'),
1241             eventLength = dtend - dtstart,
1242             store = this.getStore();
1243
1244         record.beginEdit();
1245
1246         if (isCopy != true) {
1247             // remove from ui before update
1248             var oldRecord = store.getAt(store.findExact('id', record.getId()));
1249             if (oldRecord && oldRecord.hasOwnProperty('ui')) {
1250                 oldRecord.ui.remove();
1251             }
1252         } else {
1253             record = Tine.Calendar.EventEditDialog.superclass.doCopyRecordToReturn.call(this, record);
1254
1255             record.set('editGrant', true);
1256             record.set('id', '');
1257             record.view = sourceView;
1258
1259             // remove attender ids
1260             Ext.each(record.data.attendee, function(attender) {
1261                 delete attender.id;
1262             }, this);
1263         }
1264
1265         // @TODO move to common function with daysView::notifyDrop parts
1266         // change attendee in split view
1267         if (sourceViewAttendee || destinationViewAttendee) {
1268             var attendeeStore = Tine.Calendar.Model.Attender.getAttendeeStore(sourceRecord.get('attendee')),
1269                 sourceAttendee = sourceViewAttendee ? Tine.Calendar.Model.Attender.getAttendeeStore.getAttenderRecord(attendeeStore, sourceViewAttendee) : false,
1270                 destinationAttendee = destinationViewAttendee ? Tine.Calendar.Model.Attender.getAttendeeStore.getAttenderRecord(attendeeStore, destinationViewAttendee) : false;
1271
1272             if (destinationViewAttendee && !destinationAttendee) {
1273                 destinationAttendee = new Tine.Calendar.Model.Attender(destinationViewAttendee.data);
1274
1275                 attendeeStore.remove(sourceAttendee);
1276                 attendeeStore.add(destinationAttendee);
1277                 record.view = destinationView;
1278                 
1279                 Tine.Calendar.Model.Attender.getAttendeeStore.getData(attendeeStore, record);
1280             }
1281         }
1282
1283
1284         if (datetime.is_all_day_event) {
1285             record.set('dtstart', datetime);
1286             record.set('dtend', datetime.clone().add(Date.DAY, 1).add(Date.SECOND, -1));
1287             record.set('is_all_day_event', true);
1288         } else if (datetime.date_only) {
1289             var adoptedDtStart = datetime.clone();
1290             adoptedDtStart.setHours(dtstart.getHours());
1291             adoptedDtStart.setMinutes(dtstart.getMinutes());
1292             adoptedDtStart.setSeconds(dtstart.getSeconds());
1293
1294             record.set('dtstart', adoptedDtStart);
1295             record.set('dtend', new Date(adoptedDtStart.getTime() + eventLength));
1296         } else {
1297             record.set('dtstart', datetime);
1298             record.set('dtend', new Date(datetime.getTime() + eventLength));
1299         }
1300
1301         record.endEdit();
1302
1303         if (isCopy == true) {
1304             record.isCopy = true;
1305             Tine.Tinebase.data.Clipboard.push(record);
1306             if (record.ui) {
1307                 record.ui.clearDirty();
1308             }
1309
1310             this.onAddEvent(record);
1311         } else {
1312             this.onUpdateEvent(record);
1313         }
1314     },
1315     
1316     /**
1317      * open event in new window
1318      * 
1319      * @param {String} action  add|edit|copy
1320      * @param {String} event   edit given event instead of selected event
1321      * @param {Object} plugins event dlg plugins
1322      * @param {Object} default properties for new items
1323      */
1324     onEditInNewWindow: function (action, event, plugins, defaults) {
1325         if (! (event && Ext.isFunction(event.beginEdit))) {
1326             event = null;
1327         }
1328
1329         // needed for addToEventPanel
1330         if (Ext.isObject(action)) {
1331             action = action.actionType;
1332         }
1333         
1334         if (action === 'edit' && ! event) {
1335             var panel = this.getCalendarPanel(this.activeView);
1336             var selection = panel.getSelectionModel().getSelectedEvents();
1337             if (Ext.isArray(selection) && selection.length === 1) {
1338                 event = selection[0];
1339             }
1340         }
1341
1342         if (action === 'edit' && ! event) {
1343             return;
1344         }
1345
1346         if (! event) {
1347             event = new Tine.Calendar.Model.Event(Ext.apply(Tine.Calendar.Model.Event.getDefaultData(), defaults), 0);
1348
1349             if (defaults && Ext.isDate(defaults.dtStart)) {
1350                 event.set('dtstart', defaults.dtStart);
1351                 event.set('dtend', defaults.dtStart.add(Date.MINUTE, Tine.Calendar.Model.Event.getMeta('defaultEventDuration')));
1352             }
1353         }
1354         
1355         Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::onEditInNewWindow() - Opening event edit dialog with action: ' + action);
1356
1357         Tine.Calendar.EventEditDialog.openWindow({
1358             plugins: Ext.isArray(plugins) ? Ext.encode(plugins) : null,
1359             record: Ext.encode(event.data),
1360             recordId: event.data.id,
1361             copyRecord: (action == 'copy'),
1362             listeners: {
1363                 scope: this,
1364                 update: function (eventJson) {
1365                     var updatedEvent = Tine.Calendar.backend.recordReader({responseText: eventJson});
1366                     
1367                     updatedEvent.dirty = true;
1368                     updatedEvent.modified = {};
1369                     event.phantom = (action === 'edit');
1370                     var panel = this.getCalendarPanel(this.activeView);
1371                     var store = panel.getStore();
1372                     event = store.getById(event.id);
1373                     
1374                     if (event && action !== 'copy') {
1375                         store.replaceRecord(event, updatedEvent);
1376                     } else {
1377                         store.add(updatedEvent);
1378                     }
1379                     
1380                     this.onUpdateEvent(updatedEvent, false, event);
1381                 }
1382             }
1383         });
1384     },
1385     
1386     onKeyDown: function (e) {
1387         if (e.ctrlKey) {
1388             switch (e.getKey()) 
1389             {
1390             case e.A:
1391                 // select only current page
1392                 //this.grid.getSelectionModel().selectAll(true);
1393                 e.preventDefault();
1394                 break;
1395             case e.E:
1396                 if (!this.action_editInNewWindow.isDisabled()) {
1397                     this.onEditInNewWindow('edit');
1398                 }
1399                 e.preventDefault();
1400                 break;
1401             case e.N:
1402                 if (!this.action_addInNewWindow.isDisabled()) {
1403                     this.onEditInNewWindow('add');
1404                 }
1405                 e.preventDefault();
1406                 break;
1407             }
1408         } else if (e.getKey() === e.DELETE) {
1409             if (! this.action_deleteRecord.isDisabled()) {
1410                 this.onDeleteRecords.call(this);
1411             }
1412         }
1413     },
1414     
1415     onPrintMenuClick: function(splitBtn, e) {
1416         if (! String(this.activeView).match(/(day|week)sheet$/i)) {
1417             splitBtn.menu.hide();
1418         }
1419     },
1420     
1421     onPrint: function(printMode) {
1422         var printMode = printMode ? printMode : this.defaultPrintMode,
1423             panel = this.getCalendarPanel(this.activeView),
1424             view = panel ? panel.getView() : null;
1425             
1426         if (view && Ext.isFunction(view.print)) {
1427             view.print(printMode);
1428         } else {
1429             Ext.Msg.alert(this.app.i18n._('Could not Print'), this.app.i18n._('Sorry, your current view does not support printing.'));
1430         }
1431     },
1432     
1433     /**
1434      * called before store queries for data
1435      */
1436     onStoreBeforeload: function (store, options) {
1437         options.params = options.params || {};
1438         
1439         // define a transaction
1440         this.lastStoreTransactionId = options.transactionId = Ext.id();
1441         
1442         // allways start with an empty filter set!
1443         // this is important for paging and sort header!
1444         options.params.filter = [];
1445         
1446         if (! options.refresh && this.rendered) {
1447             // defer to have the loadMask centered in case of rendering actions
1448             this.loadMask.show.defer(50, this.loadMask);
1449         }
1450         
1451         // note, we can't use the 'normal' plugin approach here, cause we have to deal with n stores
1452         //var calendarSelectionPlugin = this.app.getMainScreen().getWestPanel().getContainerTreePanel().getFilterPlugin();
1453         //calendarSelectionPlugin.onBeforeLoad.call(calendarSelectionPlugin, store, options);
1454         
1455         this.filterToolbar.onBeforeLoad.call(this.filterToolbar, store, options);
1456         
1457         // add period filter as last filter to not conflict with first OR filter
1458         if (! options.noPeriodFilter) {
1459             options.params.filter.push({field: 'period', operator: 'within', value: this.getCalendarPanel(this.activeView).getView().getPeriod() });
1460         }
1461     },
1462     
1463     /**
1464      * fence against loading of wrong data set
1465      */
1466     onStoreBeforeLoadRecords: function(o, options, success) {
1467         return this.lastStoreTransactionId === options.transactionId;
1468     },
1469     
1470     /**
1471      * called when store loaded data
1472      */
1473     onStoreLoad: function (store, options) {
1474         if (this.rendered) {
1475             this.loadMask.hide();
1476         }
1477         
1478         // reset autoRefresh
1479         if (window.isMainWindow && this.autoRefreshInterval) {
1480             this.autoRefreshTask.delay(this.autoRefreshInterval * 1000);
1481         }
1482         
1483         // check if store is current store
1484         if (store !== this.getCalendarPanel(this.activeView).getStore()) {
1485             Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::onStoreLoad view is not active anymore');
1486             return;
1487         }
1488         
1489         if (this.rendered) {
1490             // update filtertoolbar
1491             if(this.filterToolbar) {
1492                 this.filterToolbar.setValue(store.proxy.jsonReader.jsonData.filter);
1493             }
1494             // update container tree
1495             // why do we need this?
1496             // Tine.Tinebase.appMgr.get('Calendar').getMainScreen().getWestPanel().getContainerTreePanel().getFilterPlugin().setValue(store.proxy.jsonReader.jsonData.filter);
1497             
1498             // update attendee filter grid
1499             Tine.Tinebase.appMgr.get('Calendar').getMainScreen().getWestPanel().getAttendeeFilter().setFilterValue(store.proxy.jsonReader.jsonData.filter);
1500         }
1501     },
1502     
1503     /**
1504      * on store load exception
1505      * 
1506      * @param {Tine.Tinebase.data.RecordProxy} proxy
1507      * @param {String} type
1508      * @param {Object} error
1509      * @param {Object} options
1510      */
1511     onStoreLoadException: function(proxy, type, error, options) {
1512         // reset autoRefresh
1513         if (window.isMainWindow && this.autoRefreshInterval) {
1514             this.autoRefreshTask.delay(this.autoRefreshInterval * 5000);
1515         }
1516         
1517         this.setLoading(false);
1518         this.loadMask.hide();
1519         
1520         if (! options.autoRefresh) {
1521             this.onProxyFail(error);
1522         }
1523     },
1524     
1525     onProxyFail: function(error, event, ignoreFn) {
1526         this.setLoading(false);
1527         if(this.loadMask) this.loadMask.hide();
1528         
1529         if (error.code == 901) {
1530             var attendeeStore = Tine.Calendar.Model.Attender.getAttendeeStore(error.event.attendee),
1531                 fbInfo = new Tine.Calendar.FreeBusyInfo(error.freebusyinfo),
1532                 denyIgnore = fbInfo.getStateOfAllAttendees() == Tine.Calendar.FreeBusyInfo.states.BUSY_UNAVAILABLE;
1533
1534             this.conflictConfirmWin = Tine.widgets.dialog.MultiOptionsDialog.openWindow({
1535                 modal: true,
1536                 allowCancel: false,
1537                 width: 550,
1538                 height: 180 + fbInfo.attendeeCount*14 + 12*error.freebusyinfo.length,
1539                 title: this.app.i18n._('Scheduling Conflict'),
1540                 questionText: '<div class = "cal-conflict-heading">' +
1541                                    this.app.i18n._('The following attendee are busy at the requested time:') + 
1542                                '</div>' +
1543                                 fbInfo.getInfoByAttendee(attendeeStore, event),
1544                 options: [
1545                     {text: this.app.i18n._('Ignore Conflict'), name: 'ignore', disabled: denyIgnore},
1546                     {text: this.app.i18n._('Edit Event'), name: 'edit', checked: true},
1547                     {text: this.app.i18n._('Cancel this action'), name: 'cancel'}
1548                 ],
1549                 scope: this,
1550                 handler: function(option) {
1551                     var panel = this.getCalendarPanel(this.activeView),
1552                         store = panel.getStore();
1553
1554                     switch (option) {
1555                         case 'ignore':
1556                             if (Ext.isFunction(ignoreFn)) {
1557                                 ignoreFn();
1558                             } else {
1559                                 this.onAddEvent(event, false, true);
1560                             }
1561                             this.conflictConfirmWin.close();
1562                             break;
1563                         
1564                         case 'edit':
1565                             
1566                             var presentationMatch = this.activeView.match(this.presentationRe),
1567                                 presentation = Ext.isArray(presentationMatch) ? presentationMatch[0] : null;
1568                             
1569                             if (presentation != 'Grid') {
1570                                 var view = panel.getView();
1571                                 view.getSelectionModel().select(event);
1572                                 // mark event as not dirty to allow edit dlg
1573                                 event.dirty = false;
1574                                 view.fireEvent('dblclick', view, event);
1575                             } else {
1576                                 // add or edit?
1577                                 this.onEditInNewWindow(null, event);
1578                             }
1579                             
1580                             this.conflictConfirmWin.close();
1581                             break;
1582                         case 'cancel':
1583                         default:
1584                             this.conflictConfirmWin.close();
1585                             this.loadMask.show();
1586                             store.load({refresh: true});
1587                             break;
1588                     }
1589                 }
1590             });
1591             
1592         } else {
1593             Tine.Tinebase.ExceptionHandler.handleRequestException(error);
1594         }
1595     },
1596     
1597     refresh: function (options) {
1598         // convert old boolean argument
1599         options = Ext.isObject(options) ? options : {
1600             refresh: !!options
1601         };
1602         Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::refresh(' + options.refresh + ')');
1603
1604         // reset autoRefresh it might get lost if request fails
1605         if (window.isMainWindow && this.autoRefreshInterval) {
1606             this.autoRefreshTask.delay(this.autoRefreshInterval * 2000);
1607         }
1608
1609         var panel = this.getCalendarPanel(this.activeView);
1610         panel.getStore().load(options);
1611         
1612         // clear favorites
1613         Tine.Tinebase.appMgr.get('Calendar').getMainScreen().getWestPanel().getFavoritesPanel().getSelectionModel().clearSelections();
1614     },
1615     
1616     setLoading: function(bool) {
1617         if (this.rendered) {
1618             var panel = this.getCalendarPanel(this.activeView),
1619                 tbar = panel.getTopToolbar();
1620                 
1621             if (tbar && tbar.loading) {
1622                 tbar.loading[bool ? 'disable' : 'enable']();
1623             }
1624         }
1625     },
1626     
1627     setResponseStatus: function(event, status) {
1628         var myAttenderRecord = event.getMyAttenderRecord();
1629         if (myAttenderRecord) {
1630             myAttenderRecord.set('status', status);
1631             event.dirty = true;
1632             event.modified = {};
1633             
1634             var panel = this.getCalendarPanel(this.activeView);
1635             var store = panel.getStore();
1636             
1637             store.replaceRecord(event, event);
1638             
1639             this.onUpdateEvent(event);
1640         }
1641     },
1642     
1643     updateEventActions: function () {
1644         var panel = this.getCalendarPanel(this.activeView);
1645         var selection = panel.getSelectionModel().getSelectedEvents();
1646         
1647         this.actionUpdater.updateActions(selection);
1648         if (this.detailsPanel) {
1649             this.detailsPanel.onDetailsUpdate(panel.getSelectionModel());
1650         }
1651     },
1652     
1653     updateView: function (which) {
1654         Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::updateView(' + which + ')');
1655         var panel = this.getCalendarPanel(which);
1656         var period = panel.getTopToolbar().getPeriod();
1657         
1658         panel.getView().updatePeriod(period);
1659         panel.getStore().load({});
1660         //this.updateMiniCal();
1661     },
1662     
1663     /**
1664      * returns requested CalendarPanel
1665      * 
1666      * @param {String} which
1667      * @return {Tine.Calendar.CalendarPanel}
1668      */
1669     getCalendarPanel: function (which) {
1670         var whichParts = this.getViewParts(which);
1671         which = whichParts.toString();
1672         
1673         if (! this.calendarPanels[which]) {
1674             Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::getCalendarPanel creating new calender panel for view ' + which);
1675             
1676             var store = new Ext.data.JsonStore({
1677                 //autoLoad: true,
1678                 id: 'id',
1679                 fields: Tine.Calendar.Model.Event,
1680                 proxy: Tine.Calendar.backend,
1681                 reader: new Ext.data.JsonReader({}), //Tine.Calendar.backend.getReader(),
1682                 listeners: {
1683                     scope: this,
1684                     'beforeload': this.onStoreBeforeload,
1685                     'beforeloadrecords' : this.onStoreBeforeLoadRecords,
1686                     'load': this.onStoreLoad,
1687                     'loadexception': this.onStoreLoadException
1688                 },
1689                 replaceRecord: function(o, n) {
1690                     var idx = this.indexOf(o);
1691                     this.remove(o);
1692                     this.insert(idx, n);
1693                 }
1694             });
1695             
1696             var tbar = new Tine.Calendar.PagingToolbar({
1697                 view: whichParts.period,
1698                 store: store,
1699                 dtStart: this.startDate,
1700                 listeners: {
1701                     scope: this,
1702                     // NOTE: only render the button once for the toolbars
1703                     //       the buttons will be moved on chageView later
1704                     render: function (tbar) {
1705                         for (var i = 0; i < this.changeViewActions.length; i += 1) {
1706                             if (! this.changeViewActions[i].rendered) {
1707                                 tbar.addButton(this.changeViewActions[i]);
1708                             }
1709                         }
1710                     }
1711                 }
1712             });
1713             
1714             tbar.on('change', this.updateView.createDelegate(this, [which]), this, {buffer: 200});
1715             tbar.on('refresh', this.refresh.createDelegate(this, [true]), this, {buffer: 200});
1716             
1717             if (whichParts.presentation.match(/sheet/i)) {
1718                 var view;
1719                 switch (which) {
1720                     case 'daySheet':
1721                         view = new Tine.Calendar.DaysView({
1722                             store: store,
1723                             startDate: tbar.getPeriod().from,
1724                             numOfDays: 1
1725                         });
1726                         break;
1727                     case 'monthSheet':
1728                         view = new Tine.Calendar.MonthView({
1729                             store: store,
1730                             period: tbar.getPeriod()
1731                         });
1732                         break;
1733                     case 'yearSheet':
1734                         view = new Tine.Calendar.YearView({
1735                             store: store,
1736                             period: tbar.getPeriod()
1737                         });
1738                         break;
1739                     default:
1740                     case 'weekSheet':
1741                         view = new Tine.Calendar.DaysView({
1742                             store: store,
1743                             startDate: tbar.getPeriod().from,
1744                             numOfDays: 7
1745                         });
1746                         break;
1747                 }
1748
1749                 view.on('changeView', this.changeView, this);
1750                 view.on('changePeriod', function (period) {
1751                     this.startDate = period.from;
1752                     this.startDates[which] = this.startDate.clone();
1753                     this.updateMiniCal();
1754                 }, this);
1755                 
1756                 // quick add/update actions
1757                 view.on('addEvent', this.onAddEvent, this);
1758                 view.on('updateEvent', this.onUpdateEvent, this);
1759             
1760                 this.calendarPanels[which] = new Tine.Calendar.CalendarPanel({
1761                     tbar: tbar,
1762                     view: view,
1763                     mainScreen: this,
1764                     canonicalName: ['Event', 'Sheet']
1765                 });
1766             } else if (whichParts.presentation.match(/grid/i)) {
1767                 this.calendarPanels[which] = new Tine.Calendar.GridView({
1768                     tbar: tbar,
1769                     store: store,
1770                     canonicalName: ['Event', 'Grid']
1771                 });
1772             }  else if (whichParts.presentation.match(/timeline/i)) {
1773                 this.calendarPanels[which] = new Tine.Calendar.TimelinePanel({
1774                     tbar: tbar,
1775                     period: tbar.periodPicker.getPeriod(),
1776                     viewType: whichParts.period,
1777                     store: store,
1778                     canonicalName: ['Event', 'Timeline']
1779                 });
1780             }
1781
1782             this.calendarPanels[which]['canonicalName'].push(Ext.util.Format.capitalize(which.match(/[a-z]+/)[0]));
1783             this.calendarPanels[which]['canonicalName'] = this.calendarPanels[which]['canonicalName'].join(Tine.Tinebase.CanonicalPath.separator);
1784
1785             this.calendarPanels[which].on('dblclick', this.onEditInNewWindow.createDelegate(this, ["edit"], 0));
1786             this.calendarPanels[which].on('contextmenu', this.onContextMenu, this);
1787             this.calendarPanels[which].getSelectionModel().on('selectionchange', this.updateEventActions, this);
1788             this.calendarPanels[which].on('keydown', this.onKeyDown, this);
1789             
1790             this.calendarPanels[which].relayEvents(this, ['show', 'beforehide']);
1791
1792             // rebind store so it's event listeners get called at last.
1793             // -> otherwise loading spinner would be active event if the view beforeload cancels the request
1794             tbar.unbind(store);
1795             tbar.bind(store);
1796         }
1797         
1798         return this.calendarPanels[which];
1799     },
1800     
1801     updateMiniCal: function () {
1802         var miniCal = Ext.getCmp('cal-mainscreen-minical');
1803         var weekNumbers = null;
1804         var period = this.getCalendarPanel(this.activeView).getView().getPeriod();
1805         
1806         switch (this.activeView) 
1807         {
1808         case 'week' :
1809             weekNumbers = [period.from.add(Date.DAY, 1).getWeekOfYear()];
1810             break;
1811         case 'month' :
1812             weekNumbers = [];
1813             var startWeek = period.from.add(Date.DAY, 1).getWeekOfYear();
1814             var numWeeks = Math.round((period.until.getTime() - period.from.getTime()) / Date.msWEEK);
1815             for (var i = 0; i < numWeeks; i += 1) {
1816                 weekNumbers.push(startWeek + i);
1817             }
1818             break;
1819         }
1820         miniCal.update(this.startDate, true, weekNumbers);
1821     }
1822 });