diff options
author | amartin <olpc@localhost.localdomain> | 2007-02-09 01:58:04 (GMT) |
---|---|---|
committer | amartin <olpc@localhost.localdomain> | 2007-02-09 01:58:04 (GMT) |
commit | 34df2f3075dad2b2a91d3ad2f7d5201d73ad07b9 (patch) | |
tree | 5c218ac84f2dcd83759edef8af313075e6313524 | |
parent | dbf0e2d924840be32f60611ae7ae8e09111a8740 (diff) |
track delete/duplicate, note delete/duplicate, page predrawing, misc bug fixes
-rw-r--r-- | Config.py | 8 | ||||
-rw-r--r-- | Edit/HitInterface.py | 14 | ||||
-rw-r--r-- | Edit/MainWindow.py | 351 | ||||
-rw-r--r-- | Edit/NoteInterface.py | 37 | ||||
-rw-r--r-- | Edit/TrackInterface.py | 294 | ||||
-rw-r--r-- | Edit/TuneInterface.py | 89 | ||||
-rw-r--r-- | Util/NoteDB.py | 160 | ||||
-rw-r--r-- | scripts/clean_trailing_whitespace.py | 2 |
8 files changed, 768 insertions, 187 deletions
@@ -472,12 +472,12 @@ PLAYER_TEMPO_UPPER = 200 DEFAULT_VOLUME = 80 #NUMERICAL CONSTANTS -NUMBER_OF_POSSIBLE_PITCHES = 25.0 -MINIMUM_PITCH = 24.0 +NUMBER_OF_POSSIBLE_PITCHES = 25 +MINIMUM_PITCH = 24 MAXIMUM_PITCH = MINIMUM_PITCH + NUMBER_OF_POSSIBLE_PITCHES - 1 -NUMBER_OF_POSSIBLE_PITCHES_DRUM = 13.0 +NUMBER_OF_POSSIBLE_PITCHES_DRUM = 13 PITCH_STEP_DRUM = 2 -MINIMUM_PITCH_DRUM = 24.0 +MINIMUM_PITCH_DRUM = 24 MAXIMUM_PITCH_DRUM = MINIMUM_PITCH_DRUM + PITCH_STEP_DRUM*(NUMBER_OF_POSSIBLE_PITCHES_DRUM - 1) MINIMUM_NOTE_DURATION = 1 # ticks MS_PER_MINUTE = 60000.0 diff --git a/Edit/HitInterface.py b/Edit/HitInterface.py index 1301a33..f8cf691 100644 --- a/Edit/HitInterface.py +++ b/Edit/HitInterface.py @@ -18,10 +18,14 @@ class HitInterface( NoteInterface ): self.updateTransform() def updateTransform( self ): - if self.note.page == self.owner.curPage and not self.firstTransform: - oldX = self.imgX - oldY = self.imgY - oldEndX = self.imgX + self.imgWidth + if self.note.page in self.owner.getActivePages(): + if not self.firstTransform: + oldX = self.imgX + oldY = self.imgY + oldEndX = self.imgX + self.imgWidth + dirty = True + else: + dirty = False if self.note.cs.onset != self.oldOnset: self.x = self.owner.ticksToPixels( self.noteDB.getPage(self.note.page).beats, self.note.cs.onset ) @@ -33,7 +37,7 @@ class HitInterface( NoteInterface ): self.imgY = self.y - Config.NOTE_IMAGE_PADDING self.oldPitch = self.note.cs.pitch - if self.note.page == self.owner.curPage: + if dirty: if self.firstTransform: self.owner.invalidate_rect( self.imgX, self.imgY, self.imgWidth, self.imgHeight, self.note.page ) self.firstTransform = False diff --git a/Edit/MainWindow.py b/Edit/MainWindow.py index 2a8465b..b3e9512 100644 --- a/Edit/MainWindow.py +++ b/Edit/MainWindow.py @@ -207,17 +207,17 @@ class MainWindow( gtk.EventBox ): self.GUI["2toolPanel"].set_size_request( -1, 75 ) # + + tool box self.GUI["2toolBox"] = formatRoundBox( RoundHBox(), "#6C9790" ) - self.GUI["2toolBox"].set_size_request( 146, -1 ) + self.GUI["2toolBox"].set_size_request( 154, -1 ) self.GUI["2toolPointerButton"] = ImageRadioButton( None, Config.IMAGE_ROOT+"pointer.png", Config.IMAGE_ROOT+"pointerDown.png", backgroundFill = "#6C9790" ) - self.GUI["2toolPointerButton"].connect( "clicked", self.handleToolClick , "Default" ) + self.GUI["2toolPointerButton"].connect( "clicked", self.handleToolClick , "default" ) self.GUI["2toolBox"].pack_start( self.GUI["2toolPointerButton"] ) self.GUI["2toolPencilButton"] = ImageRadioButton( self.GUI["2toolPointerButton"], Config.IMAGE_ROOT+"pencil.png", Config.IMAGE_ROOT+"pencilDown.png", backgroundFill = "#6C9790" ) - self.GUI["2toolPencilButton"].connect( "clicked", self.handleToolClick , "Draw" ) + self.GUI["2toolPencilButton"].connect( "clicked", self.handleToolClick , "draw" ) self.GUI["2toolBox"].pack_start( self.GUI["2toolPencilButton"] ) self.GUI["2toolPanel"].pack_start( self.GUI["2toolBox"], False, False ) self.GUI["2rightPanel"].pack_start( self.GUI["2toolPanel"], False ) # + + context box (for context sensitive buttons, nothing to do with CAIRO) - contextWidth = 592 + contextWidth = 674 self.GUI["2contextBox"] = formatRoundBox( RoundFixed(), "#6C9790" ) self.GUI["2contextBox"].set_size_request( contextWidth, -1 ) self.GUI["2contextPrevButton"] = gtk.Button("<") @@ -229,48 +229,92 @@ class MainWindow( gtk.EventBox ): # + + + page box self.GUI["2pageBox"] = gtk.HBox() self.GUI["2pageBox"].set_size_request( contextWidth-50, -1 ) + self.GUI["2pageGenerateButton"] = gtk.Button("Gen") + self.GUI["2pageGenerateButton"].connect( "clicked", lambda a1:self.pageGenerate() ) + self.GUI["2pageBox"].pack_start( self.GUI["2pageGenerateButton"] ) + self.GUI["2pagePropertiesButton"] = gtk.Button("Prop") + self.GUI["2pagePropertiesButton"].connect( "clicked", lambda a1:self.pageProperties() ) + self.GUI["2pageBox"].pack_start( self.GUI["2pagePropertiesButton"] ) self.GUI["2pageDeleteButton"] = gtk.Button("Delete") - self.GUI["2pageDeleteButton"].connect( "clicked", lambda a1:self.removePages() ) + self.GUI["2pageDeleteButton"].connect( "clicked", lambda a1:self.pageDelete() ) self.GUI["2pageBox"].pack_start( self.GUI["2pageDeleteButton"] ) - self.GUI["2pageNewButton"] = gtk.Button("New") - self.GUI["2pageNewButton"].connect( "clicked", lambda a1:self.addPage() ) - self.GUI["2pageBox"].pack_start( self.GUI["2pageNewButton"] ) self.GUI["2pageDuplicateButton"] = gtk.Button("Duplicate") - self.GUI["2pageDuplicateButton"].connect( "clicked", lambda a1:self.duplicatePages() ) + self.GUI["2pageDuplicateButton"].connect( "clicked", lambda a1:self.pageDuplicate() ) self.GUI["2pageBox"].pack_start( self.GUI["2pageDuplicateButton"] ) + self.GUI["2pageNewButton"] = gtk.Button("New") + self.GUI["2pageNewButton"].connect( "clicked", lambda a1:self.pageAdd() ) + self.GUI["2pageBox"].pack_start( self.GUI["2pageNewButton"] ) + self.GUI["2pageBeatsButton"] = gtk.Button("Beats") + self.GUI["2pageBeatsButton"].connect( "clicked", lambda a1:self.pageBeats() ) + self.GUI["2pageBox"].pack_start( self.GUI["2pageBeatsButton"] ) self.GUI["2contextBox"].put( self.GUI["2pageBox"], 25, 0 ) # + + + track box self.GUI["2trackBox"] = gtk.HBox() self.GUI["2trackBox"].set_size_request( contextWidth-50, -1 ) + self.GUI["2trackGenerateButton"] = gtk.Button("tGen") + self.GUI["2trackGenerateButton"].connect( "clicked", lambda a1:self.trackGenerate() ) + self.GUI["2trackBox"].pack_start( self.GUI["2trackGenerateButton"] ) + self.GUI["2trackPropertiesButton"] = gtk.Button("tProp") + self.GUI["2trackPropertiesButton"].connect( "clicked", lambda a1:self.trackProperties() ) + self.GUI["2trackBox"].pack_start( self.GUI["2trackPropertiesButton"] ) self.GUI["2trackDeleteButton"] = gtk.Button("tDelete") - self.GUI["2trackDeleteButton"].connect( "clicked", lambda a1:self.removePages() ) + self.GUI["2trackDeleteButton"].connect( "clicked", lambda a1:self.trackDelete() ) self.GUI["2trackBox"].pack_start( self.GUI["2trackDeleteButton"] ) - self.GUI["2trackNewButton"] = gtk.Button("tNew") - self.GUI["2trackNewButton"].connect( "clicked", lambda a1:self.addPage() ) - self.GUI["2trackBox"].pack_start( self.GUI["2trackNewButton"] ) - self.GUI["2trackDuplicateButton"] = gtk.Button("tDuplicate") - self.GUI["2trackDuplicateButton"].connect( "clicked", lambda a1:self.duplicatePages() ) + self.GUI["2trackDuplicateButton"] = gtk.ToggleButton("tDuplicate") + self.GUI["2trackDuplicateButton"].connect( "toggled", lambda a1:self.trackDuplicate() ) self.GUI["2trackBox"].pack_start( self.GUI["2trackDuplicateButton"] ) self.GUI["2contextBox"].put( self.GUI["2trackBox"], 25, 0 ) # + + + note box self.GUI["2noteBox"] = gtk.HBox() self.GUI["2noteBox"].set_size_request( contextWidth-50, -1 ) + self.GUI["2noteGenerateButton"] = gtk.Button("nGen") + self.GUI["2noteGenerateButton"].connect( "clicked", lambda a1:self.noteGenerate() ) + self.GUI["2noteBox"].pack_start( self.GUI["2noteGenerateButton"] ) + self.GUI["2notePropertiesButton"] = gtk.Button("nProp") + self.GUI["2notePropertiesButton"].connect( "clicked", lambda a1:self.noteProperties() ) + self.GUI["2noteBox"].pack_start( self.GUI["2notePropertiesButton"] ) self.GUI["2noteDeleteButton"] = gtk.Button("nDelete") - self.GUI["2noteDeleteButton"].connect( "clicked", lambda a1:self.removePages() ) + self.GUI["2noteDeleteButton"].connect( "clicked", lambda a1:self.noteDelete() ) self.GUI["2noteBox"].pack_start( self.GUI["2noteDeleteButton"] ) - self.GUI["2noteNewButton"] = gtk.Button("nNew") - self.GUI["2noteNewButton"].connect( "clicked", lambda a1:self.addPage() ) - self.GUI["2noteBox"].pack_start( self.GUI["2noteNewButton"] ) - self.GUI["2noteDuplicateButton"] = gtk.Button("nDuplicate") - self.GUI["2noteDuplicateButton"].connect( "clicked", lambda a1:self.duplicatePages() ) + self.GUI["2noteDuplicateButton"] = gtk.ToggleButton("nDuplicate") + self.GUI["2noteDuplicateButton"].connect( "toggled", self.noteDuplicateWidget ) self.GUI["2noteBox"].pack_start( self.GUI["2noteDuplicateButton"] ) + self.GUI["2noteOnsetBox"] = gtk.HBox() + self.GUI["2noteOnsetMinusButton"] = gtk.Button("<") + self.GUI["2noteOnsetMinusButton"].connect( "clicked", lambda a1:self.noteOnset(-1) ) + self.GUI["2noteOnsetBox"].pack_start( self.GUI["2noteOnsetMinusButton"] ) + self.GUI["2noteOnsetPlusButton"] = gtk.Button(">") + self.GUI["2noteOnsetPlusButton"].connect( "clicked", lambda a1:self.noteOnset(1) ) + self.GUI["2noteOnsetBox"].pack_start( self.GUI["2noteOnsetPlusButton"] ) + self.GUI["2noteBox"].pack_start( self.GUI["2noteOnsetBox"] ) + self.GUI["2notePitchBox"] = gtk.VBox() + self.GUI["2notePitchPlusButton"] = gtk.Button("^") + self.GUI["2notePitchPlusButton"].connect( "clicked", lambda a1:self.notePitch(1) ) + self.GUI["2notePitchBox"].pack_start( self.GUI["2notePitchPlusButton"] ) + self.GUI["2notePitchMinusButton"] = gtk.Button("v") + self.GUI["2notePitchMinusButton"].connect( "clicked", lambda a1:self.notePitch(-1) ) + self.GUI["2notePitchBox"].pack_start( self.GUI["2notePitchMinusButton"] ) + self.GUI["2noteBox"].pack_start( self.GUI["2notePitchBox"] ) + self.GUI["2noteDurationBox"] = gtk.HBox() + self.GUI["2noteDurationMinusButton"] = gtk.Button("<") + self.GUI["2noteDurationMinusButton"].connect( "clicked", lambda a1:self.noteDuration(-1) ) + self.GUI["2noteDurationBox"].pack_start( self.GUI["2noteDurationMinusButton"] ) + self.GUI["2noteDurationPlusButton"] = gtk.Button(">") + self.GUI["2noteDurationPlusButton"].connect( "clicked", lambda a1:self.noteDuration(1) ) + self.GUI["2noteDurationBox"].pack_start( self.GUI["2noteDurationPlusButton"] ) + self.GUI["2noteBox"].pack_start( self.GUI["2noteDurationBox"] ) + self.GUI["2noteVolumeBox"] = gtk.VBox() + self.GUI["2noteVolumePlusButton"] = gtk.Button("^") + self.GUI["2noteVolumePlusButton"].connect( "clicked", lambda a1:self.noteVolume(1) ) + self.GUI["2noteVolumeBox"].pack_start( self.GUI["2noteVolumePlusButton"] ) + self.GUI["2noteVolumeMinusButton"] = gtk.Button("v") + self.GUI["2noteVolumeMinusButton"].connect( "clicked", lambda a1:self.noteVolume(-1) ) + self.GUI["2noteVolumeBox"].pack_start( self.GUI["2noteVolumeMinusButton"] ) + self.GUI["2noteBox"].pack_start( self.GUI["2noteVolumeBox"] ) self.GUI["2contextBox"].put( self.GUI["2noteBox"], 25, 0 ) self.GUI["2toolPanel"].pack_start( self.GUI["2contextBox"], False ) # + + transport box self.GUI["2transportBox"] = formatRoundBox( RoundHBox(), "#6C9790" ) - self.GUI["2generateButton"] = gtk.Button("G") - self.GUI["2generateButton"].connect( "clicked", self.handleGenerate, None ) - self.GUI["2transportBox"].pack_start( self.GUI["2generateButton"] ) self.GUI["2recordButton"] = gtk.ToggleButton("R") self.GUI["2transportBox"].pack_start( self.GUI["2recordButton"] ) self.GUI["2playButton"] = gtk.ToggleButton("P") @@ -311,6 +355,10 @@ class MainWindow( gtk.EventBox ): self.currentpageId = 0 self.playingTuneIdx = 0 + # timers + self.predrawTimeout = False + self.playbackTimeout = False + # FPS stuff self.fpsTotalTime = 0 self.fpsFrameCount = 0 @@ -323,7 +371,7 @@ class MainWindow( gtk.EventBox ): init_data() #above init_GUI() #above - + self.csnd.setMasterVolume( self.getVolume() ) for tid in range(Config.NUMBER_OF_TRACKS): @@ -376,7 +424,10 @@ class MainWindow( gtk.EventBox ): notes += self.noteDB.getCSNotesByTrack( page, track ) self.playing = True - self.playbackTimeout = gobject.timeout_add( 100, self.onTimeout ) + if self.predrawTimeout: + gobject.source_remove( self.predrawTimeout ) + self.predrawTimeout = False + self.playbackTimeout = gobject.timeout_add( 50, self.onTimeout ) if len(self.pages_playing) > 1: self.displayPage( self.pages_playing[0], self.pages_playing[1] ) @@ -407,6 +458,7 @@ class MainWindow( gtk.EventBox ): else: #stop gobject.source_remove( self.playbackTimeout ) + self.playbackTimeout = False if False: #This is causing csound to stop working... # reimplement this with real CSoundNotes and it should be ok. @@ -426,10 +478,22 @@ class MainWindow( gtk.EventBox ): curtick = self.csnd.loopGetTick() curIdx = curtick / ( 4 * Config.TICKS_PER_BEAT) #TODO handle each pages_playing length - if curIdx + 1 < len(self.pages_playing): predraw = self.pages_playing[curIdx+1] - else: predraw = self.pages_playing[0] - self.displayPage( self.pages_playing[curIdx], predraw ) + # TODO update playhead + + if self.pages_playing[curIdx] != self.displayedPage: + if curIdx + 1 < len(self.pages_playing): predraw = self.pages_playing[curIdx+1] + else: predraw = self.pages_playing[0] + self.displayPage( self.pages_playing[curIdx], predraw ) + else: + self.trackInterface.predrawPage( time.time() + 0.020 ) # 20 ms time limit + + return True + + def onPredrawTimeout( self ): + if self.trackInterface.predrawPage( time.time() + 0.020 ): # 20 ms time limit + self.predrawTimeout = False + return False return True def onMuteTrack( self, widget, trackId ): @@ -478,6 +542,10 @@ class MainWindow( gtk.EventBox ): def handleToolClick( self, widget, mode ): if widget.get_active(): self.trackInterface.setInterfaceMode( mode ) + def getTool( self ): + if self.GUI["2toolPointerButton"].get_active(): return "default" + else: return "draw" + def onKeyboardButton( self, widget, data ): self.kb_active = widget.get_active() @@ -488,11 +556,6 @@ class MainWindow( gtk.EventBox ): #----------------------------------- # generation functions #----------------------------------- - def handleGenerate( self, widget, data ): - #if widget.get_active(): - self.generationParametersWindow.show_all() - #else: - # self.handleCloseGeneratonParametersWindow() def handleCloseGenerationParametersWindow( self, widget = None, data = None ): self.generationParametersWindow.hide_all() @@ -507,11 +570,19 @@ class MainWindow( gtk.EventBox ): for p in range(Config.NUMBER_OF_PAGES): dict[t][p] = [] - if self.trackSelected == [ 0 for i in range(Config.NUMBER_OF_TRACKS) ]: + if self.generateMode == "note": + # unsupported!? + self.handleCloseGenerationParametersWindow( None, None ) + return + elif self.generateMode == "track": + if self.trackSelected == [ 0 for i in range(Config.NUMBER_OF_TRACKS) ]: + newtracks = set(range(Config.NUMBER_OF_TRACKS)) + else: + newtracks = set( [ i for i in range(Config.NUMBER_OF_TRACKS) if self.trackSelected[i] ] ) + newpages = self.tuneInterface.getSelectedIds() + else: # page mode newtracks = set(range(Config.NUMBER_OF_TRACKS)) - else: - newtracks = set( [ i for i in range(Config.NUMBER_OF_TRACKS) if self.trackSelected[i] ] ) - newpages = self.tuneInterface.getSelectedIds() + newpages = self.tuneInterface.getSelectedIds() algo( params, @@ -532,17 +603,21 @@ class MainWindow( gtk.EventBox ): note.pageId = page note.trackId = track - # add the new notes + # prepare the new notes newnotes = [] for tid in dict: for pid in dict[tid]: newnotes += dict[tid][pid] # delete the notes and add the new + if self.generateMode == "note": + print "TODO generateMode == note" + else: # track or page mode + self.noteDB.deleteNotesByTrack( newpages, newtracks ) + stream = [] for page in newpages: for track in newtracks: - self.noteDB.deleteNotesByTrack( page, track ) stream += [ page, track, len(dict[track][page]) ] stream += dict[track][page] stream += [-1] @@ -557,14 +632,97 @@ class MainWindow( gtk.EventBox ): self.recompose( variate, params) #======================================================= + # Clipboard Functions + + def getClipboardArea( self, page = -1 ): + if page == -1: page = self.displayedPage + ids = self.tuneInterface.getSelectedIds() + return self.noteDB.getClipboardArea( ids.index(page) ) + + def pasteClipboard( self, offset, trackMap ): + pages = self.tuneInterface.getSelectedIds() + return self.noteDB.pasteClipboard( pages, offset, trackMap ) + + def cleanupClipboard( self ): + if self.GUI["2noteDuplicateButton"].get_active(): + self.GUI["2noteDuplicateButton"].set_active(False) + if self.GUI["2trackDuplicateButton"].get_active(): + self.GUI["2trackDuplicateButton"].set_active(False) + self.trackInterface.donePaste() + + + #======================================================= + # Note Functions + + def noteGenerate( self ): + self.generateMode = "note" + self.generationParametersWindow.show_all() + return + + def noteProperties( self ): + # TODO + return + + def noteDelete( self ): + ids = self.trackInterface.getSelectedNotes() + stream = [] + for t in range(Config.NUMBER_OF_TRACKS): + N = len(ids[t]) + if not N: continue + stream += [ self.displayedPage, t, N ] + ids[t] + if len(stream): + self.noteDB.deleteNotes( stream + [-1] ) + + def noteDuplicate( self ): + ids = self.trackInterface.getSelectedNotes() + stream = [] + for t in range(Config.NUMBER_OF_TRACKS): + N = len(ids[t]) + if not N: continue + stream += [ self.displayedPage, t, N ] + ids[t] + if len(stream): + if self.GUI["2trackDuplicateButton"].get_active(): + self.GUI["2trackDuplicateButton"].set_active( False ) + self.noteDB.notesToClipboard( stream + [-1] ) + self.trackInterface.setInterfaceMode("paste_notes") + return True + return False + + def noteDuplicateWidget( self, widget ): + if widget.get_active(): + if self.noteDuplicate(): # duplicate succeeded + return + # cancel duplicate + self.trackInterface.setInterfaceMode("tool") + widget.set_active(False) + + def noteOnset( self, step ): + # TODO + return + + def notePitch( self, step ): + # TODO + return + + def noteDuration( self, step ): + # TODO + return + + def noteVolume( self, step ): + # TODO + return + + #======================================================= # Track Functions def toggleTrack( self, trackN, exclusive ): if exclusive: for i in range(Config.NUMBER_OF_TRACKS): - self.trackSelected[i] = False + if self.trackSelected[i]: + self.trackSelected[i] = False + self.trackInterface.trackToggled( i ) self.trackSelected[trackN] = True - self.trackInterface.trackToggled() # invalidate whole page + self.trackInterface.trackToggled( trackN ) self.setContextState( CONTEXT.TRACK, True ) self.setContext( CONTEXT.TRACK ) else: @@ -577,27 +735,60 @@ class MainWindow( gtk.EventBox ): return self.setContextState( CONTEXT.TRACK, False ) + def setTrack( self, trackN, state ): + if self.trackSelected[trackN] != state: + self.trackSelected[trackN] = state + self.trackInterface.trackToggled( trackN ) + + def clearTracks( self ): + for i in range(Config.NUMBER_OF_TRACKS): + if self.trackSelected[i]: + self.trackSelected[i]= False + self.trackInterface.trackToggled( i ) + + self.setContextState( CONTEXT.TRACK, False ) + def getTrackSelected( self, trackN ): return self.trackSelected[trackN] - #======================================================= - # NoteDB notifications + def trackGenerate( self ): + self.generateMode = "track" + self.generationParametersWindow.show_all() - def notifyPageAdd( self, id, at ): - self.displayPage( id ) + def trackProperties( self, trackIds = -1 ): + # TODO + return - def notifyPageDelete( self, which, safe ): - if self.displayedPage in which: - self.displayPage( safe ) + def trackDelete( self, pageIds = -1, trackIds = -1 ): - def notifyPageDuplicate( self, new, at ): - self.displayPage( new[self.displayedPage] ) + if pageIds == -1: pageIds = self.tuneInterface.getSelectedIds() + if trackIds == -1: trackIds = [ i for i in range(Config.NUMBER_OF_TRACKS) if self.trackSelected[i] ] - def notifyPageMove( self, which, low, high ): - return + self.noteDB.deleteNotesByTrack( pageIds, trackIds ) + + def trackDuplicate( self, pageIds = -1, trackIds = -1 ): + + if pageIds == -1: pageIds = self.tuneInterface.getSelectedIds() + if trackIds == -1: trackIds = [ i for i in range(Config.NUMBER_OF_TRACKS) if self.trackSelected[i] ] + + if len(trackIds): + if self.GUI["2noteDuplicateButton"].get_active(): + self.GUI["2noteDuplicateButton"].set_active( False ) + self.noteDB.tracksToClipboard( pageIds, trackIds ) + self.trackInterface.setInterfaceMode("paste_tracks") + return True + return False + + def trackDuplicateWidget( self, widget ): + if widget.get_active(): + if self.trackDuplicate(): # duplicate succeeded + return + # cancel duplicate + self.trackInterface.setInterfaceMode("tool") + widget.set_active(False) #----------------------------------- - # tune functions + # tune/page functions #----------------------------------- def scrollTune( self, scroll ): @@ -614,25 +805,63 @@ class MainWindow( gtk.EventBox ): self.trackInterface.displayPage( pageId, nextId ) - def addPage( self, after = -1, beats = False ): + def predrawPage( self, pageId ): + if self.playbackTimeout: return # we're playing, predrawing is already handled + if self.trackInterface.setPredrawPage( pageId ): # page needs to be drawn + if not self.predrawTimeout: + self.predrawTimeout = gobject.timeout_add( 50, self.onPredrawTimeout ) - if after == -1: after = self.tuneInterface.getLastSelected() - if not beats: beats = self.noteDB.getPage( self.displayedPage ).beats + def pageGenerate( self ): + self.generateMode = "page" + self.generationParametersWindow.show_all() - self.noteDB.addPage( beats, after ) + def pageProperties( self, pageIds = -1 ): + + if pageIds == -1: pageIds = self.tuneInterface.getSelectedIds() + + # TODO show properties or something + + def pageDelete( self, pageIds = -1 ): + + if pageIds == -1: pageIds = self.tuneInterface.getSelectedIds() + + self.noteDB.deletePages( pageIds ) - def duplicatePages( self, after = -1, pageIds = False ): + def pageDuplicate( self, after = -1, pageIds = False ): if after == -1: after = self.tuneInterface.getLastSelected() if not pageIds: pageIds = self.tuneInterface.getSelectedIds() self.noteDB.duplicatePages( pageIds, after ) - def removePages( self, pageIds = -1 ): + def pageAdd( self, after = -1, beats = False ): + + if after == -1: after = self.tuneInterface.getLastSelected() + if not beats: beats = self.noteDB.getPage( self.displayedPage ).beats + + self.noteDB.addPage( beats, after ) + + def pageBeats( self, pageIds = -1 ): if pageIds == -1: pageIds = self.tuneInterface.getSelectedIds() - self.noteDB.deletePages( pageIds ) + # TODO change the beats + + #======================================================= + # NoteDB notifications + + def notifyPageAdd( self, id, at ): + self.displayPage( id ) + + def notifyPageDelete( self, which, safe ): + if self.displayedPage in which: + self.displayPage( safe ) + + def notifyPageDuplicate( self, new, at ): + self.displayPage( new[self.displayedPage] ) + + def notifyPageMove( self, which, low, high ): + return #----------------------------------- # load and save functions diff --git a/Edit/NoteInterface.py b/Edit/NoteInterface.py index 9dc9e43..20886b5 100644 --- a/Edit/NoteInterface.py +++ b/Edit/NoteInterface.py @@ -40,7 +40,11 @@ class NoteInterface: self.updateParameter( None, None ) def destroy( self ): - self.owner.invalidate_rect( self.imgX, self.imgY, self.imgWidth, self.imgHeight, self.note.page, True ) + if self.selected: + print "destroy", self.note.id + self.owner.deselectNotes( { self.note.track: [self] } ) + else: # if we were deselected above the rect has already been invalidated + self.owner.invalidate_rect( self.imgX, self.imgY, self.imgWidth, self.imgHeight, self.note.page, True ) def updateParameter( self, parameter, value ): self.end = self.note.cs.onset + self.note.cs.duration @@ -70,10 +74,14 @@ class NoteInterface: return self.note.cs.pitch def updateTransform( self ): - if self.note.page == self.owner.curPage and not self.firstTransform: - oldX = self.imgX - oldY = self.imgY - oldEndX = self.imgX + self.imgWidth + if self.note.page in self.owner.getActivePages(): + if not self.firstTransform: + oldX = self.imgX + oldY = self.imgY + oldEndX = self.imgX + self.imgWidth + dirty = True + else: + dirty = False if self.note.cs.onset != self.oldOnset: self.x = self.owner.ticksToPixels( self.noteDB.getPage( self.note.page).beats, self.note.cs.onset ) @@ -89,15 +97,16 @@ class NoteInterface: self.imgY = self.y - Config.NOTE_IMAGE_PADDING self.oldPitch = self.note.cs.pitch - if self.firstTransform: - self.owner.invalidate_rect( self.imgX, self.imgY, self.imgWidth, self.imgHeight, self.note.page, True ) - self.firstTransform = False - else: - x = min( self.imgX, oldX ) - y = min( self.imgY, oldY ) - endx = max( self.imgX + self.imgWidth, oldEndX ) - endy = max( self.imgY, oldY ) + self.imgHeight - self.owner.invalidate_rect( x, y, endx-x, endy-y, self.note.page, True ) + if dirty: + if self.firstTransform: + self.owner.invalidate_rect( self.imgX, self.imgY, self.imgWidth, self.imgHeight, self.note.page, True ) + self.firstTransform = False + else: + x = min( self.imgX, oldX ) + y = min( self.imgY, oldY ) + endx = max( self.imgX + self.imgWidth, oldEndX ) + endy = max( self.imgY, oldY ) + self.imgHeight + self.owner.invalidate_rect( x, y, endx-x, endy-y, self.note.page, True ) def updateDragLimits( self, dragLimits, leftBound, rightBound, widthBound, maxRightBound ): left = leftBound - self.note.cs.onset diff --git a/Edit/TrackInterface.py b/Edit/TrackInterface.py index a3b3986..f33909d 100644 --- a/Edit/TrackInterface.py +++ b/Edit/TrackInterface.py @@ -3,6 +3,7 @@ pygtk.require( '2.0' ) import gtk from math import floor +import time import Config from Edit.NoteInterface import NoteInterface @@ -22,7 +23,8 @@ class SELECTNOTES: class INTERFACEMODE: DEFAULT = 0 DRAW = 1 - PASTE = 2 + PASTE_NOTES = 2 + PASTE_TRACKS = 3 class TrackInterfaceParasite: def __init__( self, noteDB, owner, note ): @@ -66,15 +68,20 @@ class TrackInterface( gtk.EventBox ): self.marqueeLoc = False # current drag location of the marquee self.marqueeRect = [[0,0],[0,0]] + self.pasteTick = -1 + self.pasteTrack = -1 + self.pasteRect = False + self.playheadX = Config.TRACK_SPACING_DIV2 self.cursor = { \ "default": None, \ "drag-onset": gtk.gdk.Cursor(gtk.gdk.SB_RIGHT_ARROW), \ - "drag-pitch": gtk.gdk.Cursor(gtk.gdk.SB_V_DOUBLE_ARROW), \ - "drag-duration": gtk.gdk.Cursor(gtk.gdk.SB_H_DOUBLE_ARROW), \ - "drag-playhead": gtk.gdk.Cursor(gtk.gdk.LEFT_SIDE), \ + "drag-pitch": gtk.gdk.Cursor(gtk.gdk.BOTTOM_SIDE), \ + "drag-duration": gtk.gdk.Cursor(gtk.gdk.RIGHT_SIDE), \ + "drag-playhead": gtk.gdk.Cursor(gtk.gdk.SB_H_DOUBLE_ARROW), \ "pencil": gtk.gdk.Cursor(gtk.gdk.PENCIL), \ + "paste": gtk.gdk.Cursor(gtk.gdk.CENTER_PTR), \ "error": None } self.add_events(gtk.gdk.POINTER_MOTION_MASK|gtk.gdk.POINTER_MOTION_HINT_MASK) @@ -164,6 +171,16 @@ class TrackInterface( gtk.EventBox ): self.curScreen = 0 self.preScreen = 1 + #-- private -------------------------------------------- + + def _updateClipboardArea( self ): + self.clipboardArea = self.owner.getClipboardArea( self.curPage ) + self.clipboardTrackTop = 0 + for t in range(self.drumIndex): + if self.clipboardArea["tracks"][t]: break + self.clipboardTrackTop += 1 + self.clipboardDrumTrack = self.clipboardArea["tracks"][self.drumIndex] + #======================================================= # Module Interface @@ -173,18 +190,32 @@ class TrackInterface( gtk.EventBox ): else: return ( self.image["note"], self.image["noteSelected"], self.drawingArea.get_colormap(), self.trackColors[track] ) - def predrawPage( self, page ): + def getActivePages( self ): + return self.screenBufPage + + def setPredrawPage( self, page ): if self.screenBufPage[self.preScreen] != page: self.screenBufPage[self.preScreen] = page self.screenBufBeats[self.preScreen] = self.noteDB.getPage(page).beats self.invalidate_rect( 0, 0, self.width, self.height, page ) + return True + return False + + def predrawPage( self, timeout ): + return self.draw( self.preScreen, False, timeout ) def displayPage( self, page, predraw = -1 ): - if page == self.curPage: return + if page == self.curPage: + if predraw >= 0 and self.screenBufPage[self.preScreen] != predraw: + self.screenBufPage[self.preScreen] = predraw + self.screenBufBeats[self.preScreen] = self.noteDB.getPage(predraw).beats + self.invalidate_rect( 0, 0, self.width, self.height, predraw ) + return if self.curPage >= 0 and self.curPage != page: clearNotes = True else: clearNotes = False + oldPage = self.curPage self.curPage = page self.curBeats = self.noteDB.getPage(page).beats @@ -204,21 +235,39 @@ class TrackInterface( gtk.EventBox ): self.invalidate_rect( 0, 0, self.width, self.height, predraw ) if clearNotes: # clear the notes now that we've sorted out the screen buffers - self.clearSelectedNotes() + self.clearSelectedNotes( oldPage ) + + if self.curAction == "paste": + self._updateClipboardArea() def setPlayhead( self, ticks ): self.invalidate_rect( self.playheadX-Config.PLAYHEAD_SIZE/2, 0, Config.PLAYHEAD_SIZE, self.height, self.curPage, False ) - self.playheadX = self.ticksToPixels( ticks, self.screenBufBeats[self.curScreen] ) + Config.TRACK_SPACING_DIV2 + self.playheadX = self.ticksToPixels( self.curBeats, ticks ) + Config.TRACK_SPACING_DIV2 self.invalidate_rect( self.playheadX-Config.PLAYHEAD_SIZE/2, 0, Config.PLAYHEAD_SIZE, self.height, self.curPage, False ) def setInterfaceMode( self, mode ): - if mode == "Draw": + self.doneCurrentAction() + + if mode == "tool": + mode = self.owner.getTool() + + if mode == "draw": self.interfaceMode = INTERFACEMODE.DRAW - elif mode == "Paste": - self.interfaceMode = INTERFACEMODE.PASTE + elif mode == "paste_notes": + self.interfaceMode = INTERFACEMODE.PASTE_NOTES + self.setCurrentAction("paste", self) + elif mode == "paste_tracks": + self.interfaceMode = INTERFACEMODE.PASTE_TRACKS + self.setCurrentAction("paste", self ) else: self.interfaceMode = INTERFACEMODE.DEFAULT + def getSelectedNotes( self ): + ids = [] + for t in range(Config.NUMBER_OF_TRACKS): + ids.append( [ n.note.id for n in self.selectedNotes[t] ] ) + return ids + #======================================================= # Event Callbacks @@ -226,7 +275,7 @@ class TrackInterface( gtk.EventBox ): self.alloc = allocation width = allocation.width height = allocation.height - + self.drawingArea.set_size_request( width, height ) if self.window != None: @@ -242,6 +291,13 @@ class TrackInterface( gtk.EventBox ): self.clickLoc = [ int(event.x), int(event.y) ] + if self.curAction == "paste": + self.doPaste() + self.setCurrentAction("block-track-select") + TP.ProfileEnd( "TI::handleButtonPress" ) + return + + # check if we clicked on the playhead if event.x >= self.playheadX and event.x <= self.playheadX + Config.PLAYHEAD_SIZE: self.setCurrentAction( "playhead-drag", self ) @@ -289,8 +345,9 @@ class TrackInterface( gtk.EventBox ): if self.trackLimits[i][0] > event.y: break if self.trackLimits[i][1] < event.y: continue if event.button == 1: - if self.buttonPressCount == 1: self.owner.toggleTrack( i, False ) - else: self.owner.toggleTrack( i, True ) + if self.buttonPressCount == 1: self.owner.toggleTrack( i, False ) + elif self.buttonPressCount == 2: self.owner.toggleTrack( i, True ) + else: self.owner.clearTracks() break TP.ProfileEnd( "TI::handleButtonRelease" ) @@ -317,7 +374,6 @@ class TrackInterface( gtk.EventBox ): def handleMotion( self, widget, event ): TP.ProfileBegin( "TI::handleMotion::Common" ) - if event.is_hint: x, y, state = self.window.get_pointer() event.x = float(x) @@ -326,7 +382,17 @@ class TrackInterface( gtk.EventBox ): TP.ProfileEnd( "TI::handleMotion::Common" ) - if event.state & gtk.gdk.BUTTON1_MASK: + if self.curAction == "paste": + TP.ProfileBegin( "TI::handleMotion::Paste" ) + top = Config.NUMBER_OF_TRACKS + for i in range(Config.NUMBER_OF_TRACKS): + if self.trackLimits[i][0] > event.y: break + if self.trackLimits[i][1] < event.y: continue + top = i + break + self.updatePaste( self.pixelsToTicks( self.curBeats, event.x ), top ) + TP.ProfileEnd( "TI::handleMotion::Paste" ) + elif event.state & gtk.gdk.BUTTON1_MASK: TP.ProfileBegin( "TI::handleMotion::Drag" ) if not self.curAction: # no action is in progress yet we're dragging, start a marquee @@ -361,9 +427,9 @@ class TrackInterface( gtk.EventBox ): #======================================================= # Actions - def setCurrentAction( self, action, obj ): + def setCurrentAction( self, action, obj = None ): if self.curAction: - print "BackgroundView - Action already in progress!" + self.doneCurrentAction() self.curAction = action self.curActionObject = obj @@ -372,19 +438,25 @@ class TrackInterface( gtk.EventBox ): elif action == "note-drag-duration": self.updateDragLimits() elif action == "note-drag-pitch": self.updateDragLimits() elif action == "note-drag-pitch-drum": self.updateDragLimits() + elif action == "paste": + self._updateClipboardArea() + self.setCursor("paste") def doneCurrentAction( self ): - if self.curAction == "note-drag-onset": self.doneNoteDrag() - elif self.curAction == "note-drag-duration": self.doneNoteDrag() - elif self.curAction == "note-drag-pitch": self.doneNoteDrag() - elif self.curAction == "note-drag-pitch-drum": self.doneNoteDrag() - + if not self.curAction: return + action = self.curAction self.curAction = False - self.curActionObject = False + + if action == "note-drag-onset": self.doneNoteDrag() + elif action == "note-drag-duration": self.doneNoteDrag() + elif action == "note-drag-pitch": self.doneNoteDrag() + elif action == "note-drag-pitch-drum": self.doneNoteDrag() + elif action == "paste": + self.owner.cleanupClipboard() def trackToggled( self, trackN = -1 ): - if trackN == -1: self.invalidate_rect( 0, 0, self.width, self.height, self.curPage ) - else: self.invalidate_rect( 0, self.trackLimits[trackN][0], self.width, self.trackLimits[trackN][1]-self.trackLimits[trackN][0], self.curPage ) + if trackN == -1: self.invalidate_rect( 0, 0, self.width, self.height ) + else: self.invalidate_rect( 0, self.trackLimits[trackN][0], self.width, self.trackLimits[trackN][1]-self.trackLimits[trackN][0] ) def selectionChanged( self ): if self.curAction == "note-drag-onset": self.updateDragLimits() @@ -398,14 +470,15 @@ class TrackInterface( gtk.EventBox ): return self.owner.setContextState( CONTEXT.NOTE, False ) - def applyNoteSelection( self, mode, trackN, which ): + def applyNoteSelection( self, mode, trackN, which, page = -1 ): + if page == -1: page = self.curPage if mode == SELECTNOTES.ALL: - track = self.noteDB.getNotesByTrack( self.curPage, trackN, self ) + track = self.noteDB.getNotesByTrack( page, trackN, self ) map( lambda note:note.setSelected( True ), track ) self.selectedNotes[trackN] = [] map( lambda note:self.selectedNotes[trackN].append(note), track ) elif mode == SELECTNOTES.NONE: - track = self.noteDB.getNotesByTrack( self.curPage, trackN, self ) + track = self.noteDB.getNotesByTrack( page, trackN, self ) map( lambda note:note.setSelected( False ), track ) self.selectedNotes[trackN] = [] elif mode == SELECTNOTES.ADD: @@ -425,7 +498,7 @@ class TrackInterface( gtk.EventBox ): note.setSelected( True ) self.selectedNotes[trackN].append( note ) elif mode == SELECTNOTES.EXCLUSIVE: - notes = self.noteDB.getNotesByTrack( self.curPage, trackN, self ) + notes = self.noteDB.getNotesByTrack( page, trackN, self ) for n in range(len(notes)): if notes[n] in which: if notes[n].setSelected( True ): @@ -434,46 +507,46 @@ class TrackInterface( gtk.EventBox ): if notes[n].setSelected( False ): self.selectedNotes[trackN].remove( notes[n] ) - def selectNotesByBar( self, trackN, start, stop ): + def selectNotesByBar( self, trackN, start, stop, page = -1 ): for i in range(Config.NUMBER_OF_TRACKS): if i == trackN: notes = [] track = self.noteDB.getNotesByTrack( self.curPage, trackN, self ) for n in range(len(track)): if track[n].testOnset( start, stop ): notes.append(track[n]) - if not Config.ModKeys.ctrlDown: self.applyNoteSelection( SELECTNOTES.EXCLUSIVE, trackN, notes ) - else: self.applyNoteSelection( SELECTNOTES.ADD, trackN, notes ) + if not Config.ModKeys.ctrlDown: self.applyNoteSelection( SELECTNOTES.EXCLUSIVE, trackN, notes, page ) + else: self.applyNoteSelection( SELECTNOTES.ADD, trackN, notes, page ) else: - if not Config.ModKeys.ctrlDown: self.applyNoteSelection( SELECTNOTES.NONE, i, [] ) + if not Config.ModKeys.ctrlDown: self.applyNoteSelection( SELECTNOTES.NONE, i, [], page ) self.selectionChanged() - def selectNotesByTrack( self, trackN ): + def selectNotesByTrack( self, trackN, page = -1 ): if Config.ModKeys.ctrlDown: - self.applyNoteSelection( SELECTNOTES.ALL, trackN, [] ) + self.applyNoteSelection( SELECTNOTES.ALL, trackN, [], page ) else: for i in range(Config.NUMBER_OF_TRACKS): - if i == trackN: self.applyNoteSelection( SELECTNOTES.ALL, trackN, [] ) - else: self.applyNoteSelection( SELECTNOTES.NONE, i, [] ) + if i == trackN: self.applyNoteSelection( SELECTNOTES.ALL, trackN, [], page ) + else: self.applyNoteSelection( SELECTNOTES.NONE, i, [], page ) self.selectionChanged() - def selectNotes( self, noteDic ): - if Config.ModKeys.ctrlDown: + def selectNotes( self, noteDic, ignoreCtrl = False, page = -1 ): + if Config.ModKeys.ctrlDown and not ignoreCtrl: for i in noteDic: - self.applyNoteSelection( SELECTNOTES.FLIP, i, noteDic[i] ) + self.applyNoteSelection( SELECTNOTES.FLIP, i, noteDic[i], page ) else: for i in range(Config.NUMBER_OF_TRACKS): - if i in noteDic: self.applyNoteSelection( SELECTNOTES.EXCLUSIVE, i, noteDic[i] ) - else: self.applyNoteSelection( SELECTNOTES.NONE, i, [] ) + if i in noteDic: self.applyNoteSelection( SELECTNOTES.EXCLUSIVE, i, noteDic[i], page ) + else: self.applyNoteSelection( SELECTNOTES.NONE, i, [], page ) self.selectionChanged() - def deselectNotes( self, noteDic ): + def deselectNotes( self, noteDic, page = -1 ): for i in noteDic: - self.applyNoteSelection( SELECTNOTES.REMOVE, i, noteDic[i] ) + self.applyNoteSelection( SELECTNOTES.REMOVE, i, noteDic[i], page ) self.selectionChanged() - def clearSelectedNotes( self ): + def clearSelectedNotes( self, page = -1 ): for i in range(Config.NUMBER_OF_TRACKS): - self.applyNoteSelection( SELECTNOTES.NONE, i, [] ) + self.applyNoteSelection( SELECTNOTES.NONE, i, [], page ) self.selectionChanged() def updateDragLimits( self ): @@ -616,6 +689,79 @@ class TrackInterface( gtk.EventBox ): print "set playhead to %d ticks" % (ticks) self.doneCurrentAction() + def updatePaste( self, tick, track ): + if self.interfaceMode == INTERFACEMODE.PASTE_TRACKS: tick = 0 + if self.pasteTick == tick and self.pasteTrack == track: return + if self.noteDB.getPage(self.curPage).ticks < tick < 0 \ + or track > self.drumIndex \ + or ( track == self.drumIndex and not self.clipboardDrumTrack ): + if self.pasteRect: + self.invalidate_rect( self.pasteRect[0][0], self.pasteRect[0][1], self.pasteRect[1][0], self.pasteRect[1][1], self.curPage, False ) + self.pasteTick = self.pasteTrack = -1 + self.pasteRect = False + return + if self.pasteRect: + self.invalidate_rect( self.pasteRect[0][0], self.pasteRect[0][1], self.pasteRect[1][0], self.pasteRect[1][1], self.curPage, False ) + if self.clipboardDrumTrack: + bottom = self.drumIndex + else: + bottom = self.drumIndex - 1 + for t in range(self.drumIndex-1,self.clipboardTrackTop-1,-1): + if self.clipboardArea["tracks"][t]: break + bottom -= 1 + end = -tick + min( self.noteDB.getPage(self.curPage).ticks, tick + self.clipboardArea["limit"][1]-self.clipboardArea["limit"][0] ) + self.pasteTick = tick + self.pasteTrack = track + self.pasteRect = [ [ self.ticksToPixels( self.curBeats, tick ), \ + self.trackLimits[track][0] ], \ + [ self.ticksToPixels( self.curBeats, end), \ + self.trackLimits[bottom][1] ] ] + self.invalidate_rect( self.pasteRect[0][0], self.pasteRect[0][1], self.pasteRect[1][0], self.pasteRect[1][1], self.curPage, False ) + + def doPaste( self ): + if self.pasteTrack == -1: + self.doneCurrentAction() + return + + trackMap = {} + for t in range(self.pasteTrack,self.drumIndex): + ind = t+self.clipboardTrackTop-self.pasteTrack + if ind >= self.drumIndex: break + if not self.clipboardArea["tracks"][ind]: + continue + trackMap[t] = ind + if self.clipboardDrumTrack: + trackMap[self.drumIndex] = self.drumIndex + new = self.owner.pasteClipboard( self.pasteTick - self.clipboardArea["limit"][0], trackMap ) + if self.interfaceMode == INTERFACEMODE.PASTE_NOTES and self.curPage in new: + noteDic = {} + for t in range(Config.NUMBER_OF_TRACKS): + if len(new[self.curPage][t]): + noteDic[t] = [ self.noteDB.getNote( self.curPage, t, n, self ) for n in new[self.curPage][t] ] + self.selectNotes(noteDic) + elif self.interfaceMode == INTERFACEMODE.PASTE_TRACKS: + for t in range(self.drumIndex): + ind = t + self.clipboardTrackTop - self.pasteTrack + if ind >= self.drumIndex or ind < 0: self.owner.setTrack( t, False ) + else: self.owner.setTrack( t, self.clipboardArea["tracks"][ind] ) + self.owner.setTrack( self.drumIndex, self.clipboardDrumTrack ) + + self.doneCurrentAction() + + def donePaste( self ): + if self.pasteRect: + self.invalidate_rect( self.pasteRect[0][0], self.pasteRect[0][1], self.pasteRect[1][0], self.pasteRect[1][1], self.curPage, False ) + self.pasteTick = self.pasteTrack = -1 + self.pasteRect = False + self.setInterfaceMode("tool") + # make a fake event for updateTooltip + event = gtk.gdk.Event(gtk.gdk.MOTION_NOTIFY) + x, y, state = self.window.get_pointer() + event.x = float(x) + event.y = float(y) + event.state = state + self.updateTooltip( event ) + def updateTooltip( self, event ): # check clicked the playhead @@ -654,7 +800,9 @@ class TrackInterface( gtk.EventBox ): #======================================================= # Drawing - def predraw( self, buf, noescape = True ): + def draw( self, buf, noescape = True, timeout = 0 ): + if not self.screenBufDirty[buf]: return True + TP.ProfileBegin( "TrackInterface::predraw" ) startX = self.screenBufDirtyRect[buf].x @@ -675,7 +823,7 @@ class TrackInterface( gtk.EventBox ): self.gc.set_line_attributes( Config.BEAT_LINE_SIZE, gtk.gdk.LINE_ON_OFF_DASH, gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER ) # regular tracks for i in range( resume[0], self.drumIndex ): - if resume[0] == 0: + if resume[1] == 0: if startY > self.trackLimits[i][1]: continue if stopY < self.trackLimits[i][0]: break @@ -691,14 +839,16 @@ class TrackInterface( gtk.EventBox ): x = beatStart + j*beatSpacing pixmap.draw_line( self.gc, x, self.trackRect[i].y, x, self.trackRect[i].y+self.trackRect[i].height ) + resume[1] = 1 # background drawn + # draw notes TP.ProfileBegin("TI::draw notes") - notes = self.noteDB.getNotesByTrack( self.curPage, i, self ) - for n in range( resume[1], len(notes) ): + notes = self.noteDB.getNotesByTrack( self.screenBufPage[buf], i, self ) + for n in range( resume[2], len(notes) ): # check escape - if 0: + if not noescape and time.time() > timeout: resume[0] = i - resume[1] = n + resume[2] = n TP.ProfilePause( "TrackInterface::predraw" ) return False @@ -706,13 +856,13 @@ class TrackInterface( gtk.EventBox ): TP.ProfileEnd("TI::draw notes") # finished a track, reset the resume values for the next one - resume[0] = 0 resume[1] = 0 + resume[2] = 0 # drum track if stopY > self.trackLimits[self.drumIndex][0]: - if resume[0] == 0: + if resume[1] == 0: # draw background if self.owner.getTrackSelected( self.drumIndex ): pixmap.draw_drawable( self.gc, self.image["trackBGDrumSelected"], 0, 0, 0, self.trackLimits[self.drumIndex][0], self.trackFullWidth, self.trackFullHeightDrum ) @@ -725,13 +875,15 @@ class TrackInterface( gtk.EventBox ): x = beatStart + j*beatSpacing pixmap.draw_line( self.gc, x, self.trackRect[self.drumIndex].y, x, self.trackRect[self.drumIndex].y+self.trackRect[self.drumIndex].height ) + resume[1] = 1 # background drawn + # draw notes - notes = self.noteDB.getNotesByTrack( self.curPage, self.drumIndex, self ) - for n in range( resume[1], len(notes) ): + notes = self.noteDB.getNotesByTrack( self.screenBufPage[buf], self.drumIndex, self ) + for n in range( resume[2], len(notes) ): # check escape - if 0: + if not noescape and time.time() > timeout: resume[0] = i - resume[1] = n + resume[2] = n TP.ProfilePause( "TrackInterface::predraw" ) return False if not notes[n].draw( pixmap, self.gc, startX, stopX ): break @@ -745,7 +897,7 @@ class TrackInterface( gtk.EventBox ): def expose( self, DA, event ): if self.screenBufDirty[self.curScreen]: - self.predraw( self.curScreen ) + self.draw( self.curScreen ) TP.ProfileBegin( "TrackInterface::expose" ) @@ -771,11 +923,23 @@ class TrackInterface( gtk.EventBox ): self.gc.foreground = self.marqueeColor DA.window.draw_rectangle( self.gc, False, self.marqueeRect[0][0], self.marqueeRect[0][1], self.marqueeRect[1][0], self.marqueeRect[1][1] ) + if self.pasteRect: # draw the paste highlight + self.gc.set_function( gtk.gdk.INVERT ) + for t in range(self.pasteTrack,self.drumIndex): + ind = t+self.clipboardTrackTop-self.pasteTrack + if ind >= self.drumIndex: break + if not self.clipboardArea["tracks"][ind]: + continue + DA.window.draw_rectangle( self.gc, True, self.pasteRect[0][0], self.trackLimits[t][0] + Config.TRACK_SPACING_DIV2, self.pasteRect[1][0], self.trackHeight ) + if self.clipboardDrumTrack: + DA.window.draw_rectangle( self.gc, True, self.pasteRect[0][0], self.trackLimits[self.drumIndex][0] + Config.TRACK_SPACING_DIV2, self.pasteRect[1][0], self.trackHeightDrum ) + self.gc.set_function( gtk.gdk.COPY ) + self.drawingAreaDirty = False TP.ProfileEnd( "TrackInterface::expose" ) - def invalidate_rect( self, x, y, width, height, page, base = True ): + def invalidate_rect( self, x, y, width, height, page = -1, base = True ): #print "%d %d %d %d Page %d CurPage %d" % (x,y,width,height,page,self.curPage) self.dirtyRectToAdd.x = x self.dirtyRectToAdd.y = y @@ -783,7 +947,7 @@ class TrackInterface( gtk.EventBox ): self.dirtyRectToAdd.height = height #print "dirty %d %d %d %d %d %d" % (x, y, width, height, x+width, y+height) - if page == self.curPage: + if page == self.curPage or page == -1: if base: # the base image has been dirtied if not self.screenBufDirty[self.curScreen]: self.screenBufDirtyRect[self.curScreen].x = x @@ -792,13 +956,13 @@ class TrackInterface( gtk.EventBox ): self.screenBufDirtyRect[self.curScreen].height = height else: self.screenBufDirtyRect[self.curScreen] = self.screenBufDirtyRect[self.curScreen].union( self.dirtyRectToAdd ) - self.screenBufResume[self.curScreen] = [0,0] + self.screenBufResume[self.curScreen] = [0,0,0] self.screenBufDirty[self.curScreen] = True if self.drawingArea.window != None: self.drawingArea.window.invalidate_rect( self.dirtyRectToAdd, True ) self.drawingAreaDirty = True - elif page == self.screenBufPage[self.preScreen]: + if page == self.screenBufPage[self.preScreen] or page == -1: if not self.screenBufDirty[self.preScreen]: self.screenBufDirtyRect[self.preScreen].x = x self.screenBufDirtyRect[self.preScreen].y = y @@ -806,7 +970,7 @@ class TrackInterface( gtk.EventBox ): self.screenBufDirtyRect[self.preScreen].height = height else: self.screenBufDirtyRect[self.preScreen] = self.screenBufDirtyRect[self.preScreen].union( self.dirtyRectToAdd ) - self.screenBufResume[self.preScreen] = [0,0] + self.screenBufResume[self.preScreen] = [0,0,0] self.screenBufDirty[self.preScreen] = True #self.queue_draw() diff --git a/Edit/TuneInterface.py b/Edit/TuneInterface.py index e6790b9..d1d5481 100644 --- a/Edit/TuneInterface.py +++ b/Edit/TuneInterface.py @@ -58,6 +58,8 @@ class TuneInterface( gtk.EventBox ): self.dragMode = None self.dropAt = -1 + self.add_events(gtk.gdk.POINTER_MOTION_MASK|gtk.gdk.POINTER_MOTION_HINT_MASK) + self.connect( "size-allocate", self.size_allocated ) self.drawingArea.connect( "expose-event", self.draw ) self.connect( "button-press-event", self.handleButtonPress ) @@ -90,15 +92,23 @@ class TuneInterface( gtk.EventBox ): def handleButtonPress( self, widget, event ): ind = int(event.x-self.pageOffset)//self.pageSpacing if ind >= self.noteDB.getPageCount(): - self.dragMode = self.DRAG_BLOCK + if self.dragMode != self.DRAG_MOVE: + self.dragMode = self.DRAG_BLOCK return if ind < 0: ind = 0 - + self.clickX = event.x id = self.noteDB.getPageByIndex( ind ) - - if event.type == gtk.gdk._2BUTTON_PRESS: # double click -> exclusive select + + if event.state & gtk.gdk.BUTTON2_MASK: + # bring up properties or something + return + + if event.type == gtk.gdk._3BUTTON_PRESS: # triple click -> select all + self.selectAll() + self.owner.displayPage( id ) + elif event.type == gtk.gdk._2BUTTON_PRESS: # double click -> exclusive select self.selectPage( id ) self.owner.displayPage( id ) else: @@ -120,38 +130,54 @@ class TuneInterface( gtk.EventBox ): self.owner.setContext( CONTEXT.PAGE ) def handleButtonRelease( self, widget, event ): - - if self.dragMode == self.DRAG_MOVE: + if self.dragMode == self.DRAG_MOVE \ + and event.button == 1: self.invalidate_rect( 0, 0, self.width, self.height ) # drop head if self.dropAt > 0: after = self.noteDB.getPageByIndex( self.dropAt-1 ) else: after = False self.noteDB.movePages( self.selectedIds, after ) + self.dropAt = -1 - self.dragMode = None + self.dragMode = None def handleMotion( self, widget, event ): - if Config.ModKeys.ctrlDown and (self.dragMode == None or self.dragMode == self.DRAG_MOVE): - self.dropAt = -1 - self.dragMode = self.DRAG_SELECT - - if self.dragMode == self.DRAG_SELECT: # select on drag - ind = int(event.x-self.pageOffset)//self.pageSpacing - if ind < self.noteDB.getPageCount(): self.selectPage( self.noteDB.getPageByIndex(ind), False ) - elif self.dragMode == self.DRAG_DESELECT: # deselect on drag + if event.is_hint: + x, y, state = self.window.get_pointer() + event.x = float(x) + event.y = float(y) + event.state = state + + if event.state & gtk.gdk.BUTTON1_MASK: # clicking + if Config.ModKeys.ctrlDown and (self.dragMode == None or self.dragMode == self.DRAG_MOVE): + self.dropAt = -1 + self.dragMode = self.DRAG_SELECT + + if self.dragMode == self.DRAG_SELECT: # select on drag + ind = int(event.x-self.pageOffset)//self.pageSpacing + if ind < self.noteDB.getPageCount(): self.selectPage( self.noteDB.getPageByIndex(ind), False ) + elif self.dragMode == self.DRAG_DESELECT: # deselect on drag + ind = int(event.x-self.pageOffset)//self.pageSpacing + if ind < self.noteDB.getPageCount(): self.deselectPage( self.noteDB.getPageByIndex(ind) ) + elif self.dragMode == None and abs(self.clickX-event.x) > 20: # drag and drop + self.dragMode = self.DRAG_MOVE + + if self.dragMode == self.DRAG_MOVE: + self.dropAt = int(event.x-self.pageOffset+Config.PAGE_THUMBNAIL_WIDTH_DIV2)//self.pageSpacing + c = self.noteDB.getPageCount() + if self.dropAt > c: self.dropAt = c + self.invalidate_rect( 0, 0, self.width, self.height ) + + else: # hovering ind = int(event.x-self.pageOffset)//self.pageSpacing - if ind < self.noteDB.getPageCount(): self.deselectPage( self.noteDB.getPageByIndex(ind) ) - elif self.dragMode == None and abs(self.clickX-event.x) > 20: # drag and drop - self.dragMode = self.DRAG_MOVE + if 0 <= ind < self.noteDB.getPageCount(): + id = self.noteDB.getPageByIndex(ind) + if id != self.displayedPage: + self.owner.predrawPage( id ) - if self.dragMode == self.DRAG_MOVE: - self.dropAt = int(event.x-self.pageOffset+Config.PAGE_THUMBNAIL_WIDTH_DIV2)//self.pageSpacing - c = self.noteDB.getPageCount() - if self.dropAt > c: self.dropAt = c - self.invalidate_rect( 0, 0, self.width, self.height ) def displayPage( self, id, scroll ): if self.displayedPage == id: return -1 @@ -160,12 +186,12 @@ class TuneInterface( gtk.EventBox ): if id not in self.selectedIds: self.selectPage( id ) - + ind = self.noteDB.getPageIndex( id ) - + startX = self.pageOffset + ind*self.pageSpacing stopX = startX + self.pageSpacing - + self.invalidate_rect( 0, 0, self.width, self.height ) if scroll > startX: scroll = startX @@ -176,7 +202,7 @@ class TuneInterface( gtk.EventBox ): return -1 else: scroll = stopX - self.baseWidth - + return scroll def selectPage( self, id, exclusive = True ): @@ -213,11 +239,14 @@ class TuneInterface( gtk.EventBox ): return True # page removed from the selection + def selectAll( self ): + self.selectedIds = self.noteDB.getTune()[:] + self.invalidate_rect( 0, 0, self.width, self.height ) + def clearSelection( self ): self.selectedIds = [] - self.invalidate_rect( 0, 0, self.width, self.height ) - + def getSelectedIds( self ): return self.selectedIds @@ -249,7 +278,7 @@ class TuneInterface( gtk.EventBox ): # Drawing def draw( self, drawingArea, event ): - + startX = event.area.x startY = event.area.y stopX = event.area.x + event.area.width diff --git a/Util/NoteDB.py b/Util/NoteDB.py index ff6f2d4..257c8a2 100644 --- a/Util/NoteDB.py +++ b/Util/NoteDB.py @@ -50,6 +50,9 @@ class NoteDB: self.nextId = 0 # base id, first page will be 1 + self.clipboard = [] # stores copied cs notes + self.clipboardArea = [] # stores the limits and tracks for each page in the clipboard + #-- private -------------------------------------------- def _genId( self ): self.nextId += 1 @@ -224,16 +227,21 @@ class NoteDB: # ... up to N # page id or -1 to exit def addNotes( self, stream ): + new = {} i = [-1] p = self._readstream(stream,i) while p != -1: + if p not in new: + new[p] = [ [] for x in range(Config.NUMBER_OF_TRACKS) ] t = self._readstream(stream,i) N = self._readstream(stream,i) hint = [0] for j in range(N): - self.addNote( p, t, self._readstream(stream,i), hint ) + new[p][t].append( self.addNote( p, t, self._readstream(stream,i), hint ) ) p = self._readstream(stream,i) + return new + def deleteNote( self, page, track, id ): ind = self.noteS[page][track].index( self.noteD[page][track][id] ) @@ -265,14 +273,17 @@ class NoteDB: self.deleteNote( p, t, self._readstream(stream,i) ) p = self._readstream(stream,i) - def deleteNotesByTrack( self, page, track ): - notes = self.noteS[page][track][:] - for n in notes: - self.deleteNote( page, track, n.id ) + def deleteNotesByTrack( self, pages, tracks ): + for p in pages: + for t in tracks: + notes = self.noteS[p][t][:] + for n in notes: + self.deleteNote( p, t, n.id ) def duplicateNote( self, page, track, id, toPage, toTrack, offset ): cs = self.noteD[page][track][id].cs.clone() - cs.track = toTrack + cs.trackId = toTrack + cs.pageId = toPage cs.onset += offset ticks = self.pages[toPage].ticks if cs.onset >= ticks: return False # off the end of the page @@ -340,7 +351,7 @@ class NoteDB: param = self._readstream(stream,i) N = self._readstream(stream,i) for j in range(N): - self.updateNote( p, t, self._readstream(stream,i), param, stream[inc(i)] ) + self.updateNote( p, t, self._readstream(stream,i), param, self._readstream(stream,i) ) p = self._readstream(stream,i) #-- private -------------------------------------------- @@ -377,6 +388,136 @@ class NoteDB: #======================================================= + # Clipboard Functions + + # stream format: + # page id + # track index + # number of following notes (N) + # note id + # ... up to N + # page id or -1 to exit + def notesToClipboard( self, stream ): + self.clipboard = [] + self.clipboardArea = [] + i = [-1] + pages = [] + p = self._readstream(stream,i) + while p != -1: + if p not in pages: + page = [ [] for x in range(Config.NUMBER_OF_TRACKS) ] + pageArea = { "limit": [ 99999, 0 ], + "tracks": [ 0 for x in range(Config.NUMBER_OF_TRACKS) ] } + pages.append(p) + self.clipboard.append(page) + self.clipboardArea.append(pageArea) + else: + ind = pages.index(p) + page = self.clipboard[ind] + pageArea = self.clipboardArea[ind] + t = self._readstream(stream,i) + pageArea["tracks"][t] = 1 + N = self._readstream(stream,i) + for j in range(N): + cs = self.noteD[p][t][self._readstream(stream,i)].cs.clone() + if cs.onset < pageArea["limit"][0]: pageArea["limit"][0] = cs.onset + if cs.onset + cs.duration > pageArea["limit"][1]: pageArea["limit"][1] = cs.onset + cs.duration + page[t].append( cs ) + p = self._readstream(stream,i) + + return self.clipboardArea + + def tracksToClipboard( self, pages, tracks ): + self.clipboard = [] + self.clipboardOrigin = [ 0, 0 ] + self.clipboardArea = [] + for p in pages: + page = [ [] for x in range(Config.NUMBER_OF_TRACKS) ] + pageArea = { "limit": [ 0, 99999 ], + "tracks": [ 0 for x in range(Config.NUMBER_OF_TRACKS) ] } + self.clipboard.append(page) + self.clipboardArea.append(pageArea) + for t in tracks: + pageArea["tracks"][t] = 1 + for id in self.noteD[p][t]: + cs = self.noteD[p][t][id].cs.clone() + page[t].append( cs ) + + return self.clipboardArea + + # trackMap = { X: Y, W: Z, ... }; X,W are track indices, Y,Z are clipboard indices + def pasteClipboard( self, pages, offset, trackMap ): + if not len(self.clipboard): return + + deleteStream = [] + updateStream = [] + addStream = [] + + pp = 0 + ppMax = len(self.clipboard) + for p in pages: + ticks = self.pages[p].ticks + area = self.clipboardArea[pp] + area["limit"][0] += offset + area["limit"][1] += offset + for t in trackMap.keys(): + if not area["tracks"][trackMap[t]]: continue + tdeleteStream = [] + tupdateOStream = [] + tupdateDStream = [] + taddStream = [] + # clear area + for n in self.noteS[p][t]: + start = n.cs.onset + end = start + n.cs.duration + if area["limit"][0] <= start < area["limit"][1]: start = area["limit"][1] + if area["limit"][0] < end <= area["limit"][1]: end = area["limit"][0] + if start < area["limit"][0] and end > area["limit"][1]: end = area["limit"][0] + if end <= start: + tdeleteStream.append( n.id ) + elif start != n.cs.onset: + tupdateDStream += [ n.id, end - start ] + tupdateOStream += [ n.id, start ] + elif end != start + n.cs.duration: + tupdateDStream += [ n.id, end - start ] + if len(tdeleteStream): + deleteStream += [ p, t, len(tdeleteStream) ] + tdeleteStream + if len(tupdateOStream): + updateStream += [ p, t, PARAMETER.ONSET, len(tupdateOStream)//2 ] + tupdateOStream + if len(tupdateDStream): + updateStream += [ p, t, PARAMETER.DURATION, len(tupdateDStream)//2 ] + tupdateDStream + # paste notes + for cs in self.clipboard[pp][trackMap[t]]: + newcs = cs.clone() + newcs.onset += offset + if newcs.onset >= ticks: continue + if newcs.onset + newcs.duration > ticks: + newcs.duration = ticks - newcs.onset + newcs.pageId = p + newcs.trackId = t + # TODO update the cs.instrument or any other parameters? + taddStream.append( newcs ) + if len(taddStream): + addStream += [ p, t, len(taddStream) ] + taddStream + + pp += 1 + if pp == ppMax: pp -= ppMax + + if len(deleteStream): + self.deleteNotes( deleteStream + [-1] ) + if len(updateStream): + self.updateNotes( updateStream + [-1] ) + if len(addStream): + return self.addNotes( addStream + [-1] ) + + return None + + def getClipboardArea( self, ind ): + N = len(self.clipboardArea) + while ind >= N: ind -= N + return self.clipboardArea[ind] + + #======================================================= # Listener Functions def addListener( self, listener, parasite = None, page = False, note = False ): @@ -438,6 +579,11 @@ class NoteDB: def getPageIndex( self, page ): return self.tune.index(page) + def getNote( self, page, track, id, listener = None ): + if listener: + return self.parasiteD[page][track][listener][id] + return self.noteD[page][track][id] + def getNotesByPage( self, page, listener = None ): notes = [] if listener: diff --git a/scripts/clean_trailing_whitespace.py b/scripts/clean_trailing_whitespace.py index 13532b2..57e793c 100644 --- a/scripts/clean_trailing_whitespace.py +++ b/scripts/clean_trailing_whitespace.py @@ -15,7 +15,7 @@ r = open(bakName) w = open(fName, "w" ) for line in r: line = line[:-1] - while len(line) and (line[-1] == " " or line[-1] == " "): + while len(line) and (line[-1] == " " or line[-1] == " "): line = line[:-1] w.write(line+"\n") |