diff --git a/.gitignore b/.gitignore index ea04771..f7cfab0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ test.html /master.txt messages.csv /sorted_old.csv -application.*/ \ No newline at end of file +application.*/ +.DS_Store \ No newline at end of file diff --git a/Counter.pde b/Counter.pde new file mode 100644 index 0000000..fdfc3ec --- /dev/null +++ b/Counter.pde @@ -0,0 +1,7 @@ +/** + * Static abstract class to implement static counters + * https://forum.processing.org/two/discussion/20578/static-fields-in-a-class + */ +static abstract class UnknownPersonCounter { + static int count = 0; +} \ No newline at end of file diff --git a/Exceptions.pde b/Exceptions.pde index ee63160..ca159c2 100644 --- a/Exceptions.pde +++ b/Exceptions.pde @@ -3,4 +3,3 @@ public class NotDirectoryException extends Exception { super(errorMessage); } } - diff --git a/FBVis.pde b/FBVis.pde index 63983ee..78e3d3b 100644 --- a/FBVis.pde +++ b/FBVis.pde @@ -1,402 +1,156 @@ -// Main entry point of the program +/** + * FBVis + * Created by Muchen He; see README.md for more details + */ -// TODO: re arrange the persons based on groups -// TODO: filter out specific groups -// TODO: broadcast effect for personal wall postings -// TODO: stattrak send & receive metrics per person -// TODO: make rendering of the people and messages with shaders on a separate graphic layer -import java.util.Map; -import ch.bildspur.postfx.builder.*; -import ch.bildspur.postfx.pass.*; -import ch.bildspur.postfx.*; +/** + * Global variables + */ -// Configuration is the most important so it needs to be set up first -FBVisConfig CONFIG; -PostFX fx; +/* Object for reading/writing config file */ +FBVisConfig g_config; -// Render layers +/* Render layers */ +/* TODO: payload should be on its own layers */ RenderUILayer g_uiLayer; RenderPeopleLayer g_pplLayer; -StatCardHover statcardHover; +/* Person node instances (view) */ +ArrayList g_persons; -// Hash map to hold to the person -IntDict nameToPersonIndexMap; -ArrayList persons; +/* Message payloads (view) */ +ArrayList g_msgPayloads; -ArrayList payloads; -final int PAYLOADS_MAXSIZE = 2048; -PayloadFactory payloadFactory; -MessageManager man; +/* Payload factory instantiates msgPayload views */ +PayloadFactory g_payloadFactory; -PackedSpiral g_layoutGen; +/* Message data and manager */ +MsgManager g_msgMan; -// For display loading bars on splashscreen -Progress progress; +/* Layout generator */ +LayoutGenerator g_layoutGen; -// timing -long currentTimestamp; -long nextTimestamp; -Timeline timeline; -SpeedControl speedControl; +/* Progress bar TODO: Change name ot ProgressBar */ +Progress g_progress; -// Font -PFont font; -PFont monospaceFont; +/* Timing and animation */ +long t_current; /* Current timestamp in ms */ +long t_next; /* Next timestamp in ms */ -// Global togglable flags -Boolean g_toggle_UI = true; +/* Timeline progress bar */ +Timeline g_timeline; -// Mouse dragging control +/* Speed controller */ +SpeedControl g_speedControl; + +/* UI */ +PFont g_uiFont; +PFont g_uiMonospaceFont; +Boolean g_uiVisible; +PImage g_logo; +float halfwidth; +float halfheight; + +/* Mouse dragging control */ +/* TODO: put this to [PER LAYER] control */ Boolean g_mouseLocked = false; float mouseDown_x = 0; float mouseDown_y = 0; - float g_offsetX = 0.0; float g_offsetY = 0.0; -int g_state; -final int STATE_UNINIT = 0; -final int STATE_RUN = 1; -final int STATE_PAUSE = 2; +/* App state */ +enum AppState { + UNINITIALIZED, + RUNNING, + PAUSED +} +AppState g_appState; + +/** + * Initialization functions + */ void settings() { - // Size and fullscreen should go inside here - // But none of the Processing functions are available - size(1920, 1080, P2D); - smooth(2); + size(800, 600, P2D); + halfwidth = width / 2; + halfheight = height / 2; } void setup() { - // There are 3 main stages in the setup function - // [1] Load and read configuration file - // [2] Run regular Processing 3 setup stuff - // [3] Run the initialization routine - - // [1] Configuration - CONFIG = new FBVisConfig(); - - // [2] - g_state = STATE_UNINIT; - nameToPersonIndexMap = new IntDict(); - persons = new ArrayList(); - - payloads = new ArrayList(); - payloadFactory = new PayloadFactory(payloads); - - timeline = new Timeline(50, height - 50, width - 100, 30); - speedControl = new SpeedControl(); - statcardHover = new StatCardHover(); - - if (CONFIG.enableShaders) { - fx = new PostFX(this); - fx.preload(RGBSplitPass.class); - fx.preload(BloomPass.class); - } - frameRate(CONFIG.fps); + /* Configuration */ + g_config = new FBVisConfig(); + g_logo = loadImage("img/logo.png"); + g_logo.resize(0, int(0.3 * height)); - // Geometry and layout - g_layoutGen = new PackedSpiral(70, width/2, height/2); + /* Initialize app */ + g_appState = AppState.UNINITIALIZED; + g_persons = new ArrayList(); + g_msgPayloads = new ArrayList(); + g_payloadFactory = new PayloadFactory(g_msgPayloads); + g_progress = new Progress(); - // [3] + frameRate(g_config.fps); + + /* Run data processing routine */ thread("initialize"); } -// Async initialization function void initialize() { - // Load types - font = createFont("Helvetica", 32); - monospaceFont = createFont("Courier", 32); + initializeUI(); + initializeMsgs(); - // Load and process - progress = new Progress(); - man = new MessageManager(CONFIG.dataRootPath); + /* Once done, we can begin playing */ + g_appState = AppState.PAUSED; +} - // Initialize layers - g_uiLayer = new RenderUILayer(); - g_uiLayer.timeline = timeline; - g_uiLayer.speedControl = speedControl; - g_uiLayer.statCardHover = statcardHover; +void initializeUI() { + /* TODO: remove hardcoded geometry */ + g_timeline = new Timeline(50, height - 50, width - 100, 30); + g_speedControl = new SpeedControl(); + g_layoutGen = new PackedSpiral(70, halfwidth, halfheight); - g_pplLayer = new RenderPeopleLayer(); - g_pplLayer.persons = persons; + /* TODO: use custom/system-independent fonts */ + g_uiFont = createFont("Helvetica", 32); + g_uiMonospaceFont = createFont("Courier", 32); +} - // Set flag to true when done - g_state = STATE_RUN; +void initializeMsgs() { + g_msgMan = new MsgManager(g_config.dataRootPath); + g_msgMan.populate(g_progress); } -int gi = 0; -boolean startFlag = true; -// TODO: reset program -void reset() { - // not implemented +/** + * Drawing functions + */ +void draw() { + drawLoadingScreen(); } +/** + * Draw the loading screen while things are still initializing + */ void drawLoadingScreen() { - // Draws the loading screen (before finished initialization) background(0); + + /* Draw text */ fill(255); noStroke(); - textAlign(LEFT, TOP); - text("FBVis version 0.6.1", 10, 10); - text("github.com/FSXAC/FBVis", 10, 25); textAlign(CENTER, CENTER); - text("Loading Messenger data . . .", width/2, height/2); - - if (progress != null) { - stroke(50); - float start = 0.4 * width; - float end = 0.6 * width; - float y = height / 2 + 20; - line(start, y, end, y); - - stroke(255); - float totalProgress = ( - progress.getLoadingLargeProgress() + progress.getLoadingProgress() + - progress.getSortingProgress() - ) / 3; - float totalWidth = map(totalProgress, 0, 1, 0, 0.2 * width); - line(start, y, start + totalWidth, y); - } -} + text("github.com/FSXAC/FBVis", halfwidth, halfheight + 10); + text("Loading Messenger data . . .", halfwidth, halfheight + 20); -void draw() { - switch (g_state) { - case STATE_UNINIT: - drawLoadingScreen(); - break; - case STATE_RUN: - updateState(); - drawRun(); - break; - case STATE_PAUSE: - drawRun(); - break; - } -} + float y = halfheight + 30; + stroke(150); + line(0.3 * width, y, 0.7 * width, y); -void drawRun() { - // background(0); - fill(0, 100); - noStroke(); - rect(0, 0, width, height); - - // Draw a grid of people - pushMatrix(); - translate(g_offsetX, g_offsetY); - g_pplLayer.render(); - image(g_pplLayer.pg, 0, 0); - - // Draw and update payload - blendMode(SCREEN); - drawPayload(); - blendMode(BLEND); - - if (CONFIG.enableShaders) { - fx.render() - .bloom(0.8, 5, 30) - .rgbSplit(constrain(payloads.size(), 0, 20)) - .compose(); - } - popMatrix(); - - // Draw current date and timeline - if (g_toggle_UI) { - updateTimeline(); - g_uiLayer.timestamp = currentTimestamp; - g_uiLayer.render(); - image(g_uiLayer.pg, 0, 0); - } - - // HACK: we need another robust way to indicate global index - if (gi >= man.organizedMessagesList.size()) { - gi = 0; - g_state = STATE_PAUSE; - } + /* Draw logo */ + tint(255, constrain(frameCount, 0, 255)); + image(g_logo, halfwidth - g_logo.width/2, halfheight - g_logo.height); } -void updateState() { - if (gi == 0) { - resetPersonStats(); - } - - if (CONFIG.enableUniformTime) { - if (startFlag) { - long firstTimeStamp = man.organizedMessagesList.get(gi).timestamp; - if (firstTimeStamp > CONFIG.startTimestamp) { - currentTimestamp = firstTimeStamp; - } else { - currentTimestamp = CONFIG.startTimestamp; - } - - if (CONFIG.enableVerbose) - println("currentTimestamp: " + new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm").format(new java.util.Date(currentTimestamp))); - - startFlag = false; - } - - // Get all the messages for the next time stamp - nextTimestamp = currentTimestamp + (CONFIG.deltaTimestamp * speedControl.getSpeed()); - - long messageTimestamp = man.organizedMessagesList.get(gi % man.organizedMessagesList.size()).timestamp; - - long dt = messageTimestamp - nextTimestamp; - if (dt > CONFIG.deltaAutoSkipTimestamp) { - // Then we know that we need to skip - nextTimestamp = messageTimestamp; - } else if (dt > 0) { - // Don't do anything - } else { - while (messageTimestamp < nextTimestamp) { - int di = gi % man.organizedMessagesList.size(); - MessageData current = man.organizedMessagesList.get(di); - - processCurrentmessageData(current); - gi++; - - messageTimestamp = current.timestamp; - } - } - - currentTimestamp = nextTimestamp; - - } else { - for (int i = 0; i < (CONFIG.numMsgPerFrame * speedControl.getSpeed()); i++) { - int di = gi % man.organizedMessagesList.size(); - MessageData current = man.organizedMessagesList.get(di); - processCurrentmessageData(current); - gi++; - currentTimestamp = current.timestamp; - } - } - - // Update persons - for (PersonNode person : persons) { - person.update(); - } -} - -void updateTimeline() { - timeline.setPercentage(((float) gi % man.organizedMessagesList.size()) / man.organizedMessagesList.size()); - timeline.handleMouseInput(); -} - -void processCurrentmessageData(MessageData current) { - // check if sender and receiver in the persons map - if (!nameToPersonIndexMap.hasKey(current.sender)) { - - // Add to array and get the index and put it in the map - addNewPerson(current.sender); - } - - for (String receiver : current.receivers) { - if (!nameToPersonIndexMap.hasKey(receiver)) { - addNewPerson(receiver); - } - - // For each receiving end, we make a payload - PersonNode senderPerson = persons.get(nameToPersonIndexMap.get(current.sender)); - PersonNode receivePerson = persons.get(nameToPersonIndexMap.get(receiver)); - - if (current.receivers.size() <= 1) { - payloadFactory.makeIndividualPayload(senderPerson, receivePerson, current.contentSizeSqrt); - } else { - payloadFactory.makeGroupPayload(senderPerson, receivePerson, current.contentSizeSqrt); - } - - // For each person, update their stats - senderPerson.incrementMsgSent(); - receivePerson.incrementMsgReceived(); - senderPerson.stats.lastInteractTimestamp = current.timestamp; - receivePerson.stats.lastInteractTimestamp = current.timestamp; - } -} - -void addNewPerson(String name) { - final int index = persons.size(); - - PersonNode new_person; - if (name.equals(CONFIG.masterName)) { - new_person = new PersonMasterNode(name); - } else { - new_person = new PersonNode(name); - } - - final PVector new_position = g_layoutGen.pos(index); - - new_person.setTargetPosition(new_position.x, new_position.y); - persons.add(new_person); - - nameToPersonIndexMap.set(name, index); -} - -// TODO: could be instanciated elsewhere and just cleared -ArrayList toBeRemoved = new ArrayList(); -void drawPayload() { - - toBeRemoved.clear(); - - // Draw and check - for (Payload payload : payloads) { - payload.draw(); - - if (payload.hasArrived()) { - toBeRemoved.add(payload); - } - } - - // Remove from active list - for (Payload payload : toBeRemoved) { - payloads.remove(payload); - } -} - - // Draw by listing all the messages one per frame -void drawListMode(MessageData current) { - float y = (frameCount % 40) * height / 40; - fill(50); - String date = new java.text.SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date(current.timestamp)); - textAlign(LEFT, TOP); - textSize(10); - text(date, 10, y); - text(current.sender, 80, y); - text(current.content, 200, y); -} - -void resetPersonStats() { - for (PersonNode p : persons) { - p.stats.reset(); - } -} - - -// Input handling -void keyPressed() { - if (key == 'l') { - currentTimestamp += CONFIG.deltaSkipTimestamp; - } else if (key == 'h') { - g_toggle_UI = !g_toggle_UI; - } - - // Speed control (test) - else if (key == '=') { - speedControl.incrementSpeed(); - } - else if (key == '-') { - speedControl.decrementSpeed(); - } - - // play/pause - else if (key == ' ') { - if (g_state == STATE_RUN) { - g_state = STATE_PAUSE; - } else if (g_state == STATE_PAUSE) { - g_state = STATE_RUN; - } - } -} // Mouse input handling @@ -408,7 +162,7 @@ void mousePressed() { } void mouseDragged() { - if (g_state < STATE_RUN) { + if (g_appState != AppState.RUNNING || g_appState != AppState.PAUSED) { return; } diff --git a/FBVisConfig.pde b/FBVisConfig.pde index 6ea2678..ea19cce 100644 --- a/FBVisConfig.pde +++ b/FBVisConfig.pde @@ -188,4 +188,13 @@ public class FBVisConfig { output.flush(); output.close(); } + + /** + * Check if an input is inside the ignored list + * @param thread the thread to check + * @return true if message thread 'thread' is in the ignore list + */ + public Boolean checkIfIgnored(String threadName) { + return Arrays.stream(this.ignoreList).anyMatch(threadName::equals); + } } diff --git a/FBVis_old.pde b/FBVis_old.pde new file mode 100644 index 0000000..3395e8c --- /dev/null +++ b/FBVis_old.pde @@ -0,0 +1,443 @@ +// // Main entry point of the program + +// // TODO: re arrange the persons based on groups +// // TODO: filter out specific groups +// // TODO: broadcast effect for personal wall postings +// // TODO: stattrak send & receive metrics per person +// // TODO: make rendering of the people and messages with shaders on a separate graphic layer + +// import java.util.Map; +// import ch.bildspur.postfx.builder.*; +// import ch.bildspur.postfx.pass.*; +// import ch.bildspur.postfx.*; + +// /* Handling configurations of FBVis */ +// FBVisConfig CONFIG; + +// /* PostFX */ +// PostFX fx; + +// // Render layers +// RenderUILayer g_uiLayer; +// RenderPeopleLayer g_pplLayer; + +// /* The Stat card that shows when hovered over people */ +// StatCardHover statcardHover; + +// /* TODO: To be refactored to line up with the MsgManager */ +// // Hash map to hold to the person +// IntDict nameToPersonIndexMap; +// ArrayList persons; + + +// /* Payload */ +// ArrayList payloads; +// final int PAYLOADS_MAXSIZE = 2048; +// PayloadFactory payloadFactory; + +// /* Message data */ +// MessageManager man; + +// /* Spiral generator */ +// PackedSpiral g_layoutGen; + +// /* Porgress bar -- For display loading bars on splashscreen */ +// Progress progress; + +// /* Timing and animation */ +// long currentTimestamp; +// long nextTimestamp; +// Timeline timeline; +// SpeedControl speedControl; + +// /* Font */ +// PFont font; +// PFont monospaceFont; + +// // Global togglable flags +// Boolean g_toggle_UI = true; + +// // Mouse dragging control +// Boolean g_mouseLocked = false; +// float mouseDown_x = 0; +// float mouseDown_y = 0; +// float g_offsetX = 0.0; +// float g_offsetY = 0.0; + +// /* App life-cycle states */ +// int g_state; +// final int STATE_UNINIT = 0; +// final int STATE_RUN = 1; +// final int STATE_PAUSE = 2; + +// void settings() { +// // Size and fullscreen should go inside here +// // But none of the Processing functions are available +// /* TODO: add config for fullscreen */ +// size(1920, 1080, P2D); +// smooth(2); +// } + +// void setup() { +// // There are 3 main stages in the setup function +// // [1] Load and read configuration file +// // [2] Run regular Processing 3 setup stuff +// // [3] Run the initialization routine + +// // [1] Configuration +// CONFIG = new FBVisConfig(); + +// // [2] +// g_state = STATE_UNINIT; +// nameToPersonIndexMap = new IntDict(); +// persons = new ArrayList(); + +// payloads = new ArrayList(); +// payloadFactory = new PayloadFactory(payloads); + +// timeline = new Timeline(50, height - 50, width - 100, 30); +// speedControl = new SpeedControl(); +// statcardHover = new StatCardHover(); + +// // if (CONFIG.enableShaders) { +// // fx = new PostFX(this); +// // fx.preload(RGBSplitPass.class); +// // fx.preload(BloomPass.class); +// // } +// frameRate(CONFIG.fps); + +// // Geometry and layout +// g_layoutGen = new PackedSpiral(70, width/2, height/2); + +// // [3] +// thread("initialize"); +// } + +// // Async initialization function +// void initialize() { +// // Load types +// font = createFont("Helvetica", 32); +// monospaceFont = createFont("Courier", 32); + +// // Load and process +// progress = new Progress(); +// // man = new MessageManager(CONFIG.dataRootPath); +// MsgManager m = new MsgManager(CONFIG.dataRootPath); +// m.populate(progress); + +// /* TODO: FIXME: TEMP: */ +// exit(); + +// // Initialize layers +// g_uiLayer = new RenderUILayer(); +// g_uiLayer.timeline = timeline; +// g_uiLayer.speedControl = speedControl; +// g_uiLayer.statCardHover = statcardHover; + +// g_pplLayer = new RenderPeopleLayer(); +// g_pplLayer.persons = persons; + +// // Set flag to true when done +// g_state = STATE_RUN; +// } + +// int gi = 0; +// boolean startFlag = true; + +// // TODO: reset program +// void reset() { +// // not implemented +// } + +// void drawLoadingScreen() { +// // Draws the loading screen (before finished initialization) +// background(0); +// fill(255); +// noStroke(); +// textAlign(LEFT, TOP); +// text("FBVis version 0.6.1", 10, 10); +// text("github.com/FSXAC/FBVis", 10, 25); +// textAlign(CENTER, CENTER); +// text("Loading Messenger data . . .", width/2, height/2); + +// if (progress != null) { +// stroke(50); +// float start = 0.4 * width; +// float end = 0.6 * width; +// float y = height / 2 + 20; +// line(start, y, end, y); + +// stroke(255); +// float totalProgress = ( +// progress.getLoadingLargeProgress() + progress.getLoadingProgress() + +// progress.getSortingProgress() +// ) / 3; +// float totalWidth = map(totalProgress, 0, 1, 0, 0.2 * width); +// line(start, y, start + totalWidth, y); +// } +// } + +// void draw() { +// switch (g_state) { +// case STATE_UNINIT: +// drawLoadingScreen(); +// break; +// case STATE_RUN: +// updateState(); +// drawRun(); +// break; +// case STATE_PAUSE: +// drawRun(); +// break; +// } +// } + +// void drawRun() { +// background(0); + +// // Draw a grid of people +// pushMatrix(); +// translate(g_offsetX, g_offsetY); +// g_pplLayer.render(); +// image(g_pplLayer.pg, 0, 0); + +// // Draw and update payload +// blendMode(SCREEN); +// drawPayload(); +// blendMode(BLEND); + +// if (CONFIG.enableShaders) { +// fx.render() +// .bloom(0.8, 5, 30) +// .rgbSplit(constrain(payloads.size(), 0, 20)) +// .compose(); +// } +// popMatrix(); + +// // Draw current date and timeline +// if (g_toggle_UI) { +// updateTimeline(); +// g_uiLayer.timestamp = currentTimestamp; +// g_uiLayer.render(); +// image(g_uiLayer.pg, 0, 0); +// } + +// // HACK: we need another robust way to indicate global index +// if (gi >= man.organizedMessagesList.size()) { +// gi = 0; +// g_state = STATE_PAUSE; +// } +// } + +// void updateState() { +// if (gi == 0) { +// resetPersonStats(); +// } + +// if (CONFIG.enableUniformTime) { +// if (startFlag) { +// long firstTimeStamp = man.organizedMessagesList.get(gi).timestamp; +// if (firstTimeStamp > CONFIG.startTimestamp) { +// currentTimestamp = firstTimeStamp; +// } else { +// currentTimestamp = CONFIG.startTimestamp; +// } + +// if (CONFIG.enableVerbose) +// println("currentTimestamp: " + new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm").format(new java.util.Date(currentTimestamp))); + +// startFlag = false; +// } + +// // Get all the messages for the next time stamp +// nextTimestamp = currentTimestamp + (CONFIG.deltaTimestamp * speedControl.getSpeed()); + +// long messageTimestamp = man.organizedMessagesList.get(gi % man.organizedMessagesList.size()).timestamp; + +// long dt = messageTimestamp - nextTimestamp; +// if (dt > CONFIG.deltaAutoSkipTimestamp) { +// // Then we know that we need to skip +// nextTimestamp = messageTimestamp; +// } else if (dt > 0) { +// // Don't do anything +// } else { +// while (messageTimestamp < nextTimestamp) { +// int di = gi % man.organizedMessagesList.size(); +// MessageData current = man.organizedMessagesList.get(di); + +// processCurrentmessageData(current); +// gi++; + +// messageTimestamp = current.timestamp; +// } +// } + +// currentTimestamp = nextTimestamp; + +// } else { +// for (int i = 0; i < (CONFIG.numMsgPerFrame * speedControl.getSpeed()); i++) { +// int di = gi % man.organizedMessagesList.size(); +// MessageData current = man.organizedMessagesList.get(di); +// processCurrentmessageData(current); +// gi++; +// currentTimestamp = current.timestamp; +// } +// } + +// // Update persons +// for (PersonNode person : persons) { +// person.update(); +// } +// } + +// void updateTimeline() { +// timeline.setPercentage(((float) gi % man.organizedMessagesList.size()) / man.organizedMessagesList.size()); +// timeline.handleMouseInput(); +// } + +// void processCurrentmessageData(MessageData current) { +// // check if sender and receiver in the persons map +// if (!nameToPersonIndexMap.hasKey(current.sender)) { + +// // Add to array and get the index and put it in the map +// addNewPerson(current.sender); +// } + +// for (String receiver : current.receivers) { +// if (!nameToPersonIndexMap.hasKey(receiver)) { +// addNewPerson(receiver); +// } + +// // For each receiving end, we make a payload +// PersonNode senderPerson = persons.get(nameToPersonIndexMap.get(current.sender)); +// PersonNode receivePerson = persons.get(nameToPersonIndexMap.get(receiver)); + +// if (current.receivers.size() <= 1) { +// payloadFactory.makeIndividualPayload(senderPerson, receivePerson, current.contentSizeSqrt); +// } else { +// payloadFactory.makeGroupPayload(senderPerson, receivePerson, current.contentSizeSqrt); +// } + +// // For each person, update their stats +// senderPerson.incrementMsgSent(); +// receivePerson.incrementMsgReceived(); +// senderPerson.stats.lastInteractTimestamp = current.timestamp; +// receivePerson.stats.lastInteractTimestamp = current.timestamp; +// } +// } + +// void addNewPerson(String name) { +// final int index = persons.size(); + +// PersonNode new_person; +// if (name.equals(CONFIG.masterName)) { +// new_person = new PersonMasterNode(name); +// } else { +// new_person = new PersonNode(name); +// } + +// final PVector new_position = g_layoutGen.pos(index); + +// new_person.setTargetPosition(new_position.x, new_position.y); +// persons.add(new_person); + +// nameToPersonIndexMap.set(name, index); +// } + +// // TODO: could be instanciated elsewhere and just cleared +// ArrayList toBeRemoved = new ArrayList(); +// void drawPayload() { + +// toBeRemoved.clear(); + +// // Draw and check +// for (Payload payload : payloads) { +// payload.draw(); + +// if (payload.hasArrived()) { +// toBeRemoved.add(payload); +// } +// } + +// // Remove from active list +// for (Payload payload : toBeRemoved) { +// payloads.remove(payload); +// } +// } + +// // Draw by listing all the messages one per frame +// void drawListMode(MessageData current) { +// float y = (frameCount % 40) * height / 40; +// fill(50); +// String date = new java.text.SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date(current.timestamp)); +// textAlign(LEFT, TOP); +// textSize(10); +// text(date, 10, y); +// text(current.sender, 80, y); +// text(current.content, 200, y); +// } + +// void resetPersonStats() { +// for (PersonNode p : persons) { +// p.stats.reset(); +// } +// } + + +// // Input handling +// void keyPressed() { +// if (key == 'l') { +// currentTimestamp += CONFIG.deltaSkipTimestamp; +// } else if (key == 'h') { +// g_toggle_UI = !g_toggle_UI; +// } + +// // Speed control (test) +// else if (key == '=') { +// speedControl.incrementSpeed(); +// } +// else if (key == '-') { +// speedControl.decrementSpeed(); +// } + +// // play/pause +// else if (key == ' ') { +// if (g_state == STATE_RUN) { +// g_state = STATE_PAUSE; +// } else if (g_state == STATE_PAUSE) { +// g_state = STATE_RUN; +// } +// } +// } + + +// // Mouse input handling +// void mousePressed() { +// g_mouseLocked = true; + +// mouseDown_x = mouseX - g_offsetX; +// mouseDown_y = mouseY - g_offsetY; +// } + +// void mouseDragged() { +// if (g_state < STATE_RUN) { +// return; +// } + +// if (g_mouseLocked) { +// g_offsetX = mouseX - mouseDown_x; +// g_offsetY = mouseY - mouseDown_y; +// } +// } + +// void mouseReleased() { +// g_mouseLocked = false; +// } + +// float mouseXSpace() { +// return mouseX - g_offsetX; +// } + +// float mouseYSpace() { +// return mouseY - g_offsetY; +// } diff --git a/Message.pde b/Message.pde index cfd0657..28029c5 100644 --- a/Message.pde +++ b/Message.pde @@ -40,7 +40,7 @@ class MessageManager { // If in the ignore list, then skip boolean ignore = false; - for (String ignoredItem : CONFIG.ignoreList) { + for (String ignoredItem : g_config.ignoreList) { if (ignoredItem.equals(filename)) { ignore = true; break; @@ -62,7 +62,7 @@ class MessageManager { } /* Get message util object and populate with all the json files */ - if (CONFIG.enableVerbose) println("Loading: " + messageDataPath); + if (g_config.enableVerbose) println("Loading: " + messageDataPath); MessageUtil newMessageUtil = new MessageUtil(messageDataPath); /* Populate in reverse order */ @@ -77,11 +77,11 @@ class MessageManager { i++; // Status - progress.setLoadingProgress(i / filenames.size()); + g_progress.setLoadingProgress(i / filenames.size()); } j++; - progress.setLoadingLargeProgress(j / this.rootPaths.size()); + g_progress.setLoadingLargeProgress(j / this.rootPaths.size()); } this.buildMessagesList(); @@ -89,12 +89,12 @@ class MessageManager { // Builds an timely ordered list public void buildMessagesList() { - if (CONFIG.enableVerbose) println("Building ordered messages list, sorting through all messages by time"); + if (g_config.enableVerbose) println("Building ordered messages list, sorting through all messages by time"); for (int i = 0; i < this.messageUtils.size(); i++) { // Status - if (CONFIG.enableVerbose) println("Sorting " + str(i) + "/" + str(messageUtils.size()) + " entries"); - progress.setSortingProgress(i / messageUtils.size()); + if (g_config.enableVerbose) println("Sorting " + str(i) + "/" + str(messageUtils.size()) + " entries"); + g_progress.setSortingProgress(i / messageUtils.size()); MessageUtil mu = this.messageUtils.get(i); for (MessageData md : mu.getMessagesList()) { @@ -225,7 +225,7 @@ class MessageUtil { String name = nameObj.getString("name"); - if (name.equals(CONFIG.defaultName)) { + if (name.equals(g_config.defaultName)) { name += ' ' + str(globalUnknownUserCount); globalUnknownUserCount++; } @@ -285,7 +285,7 @@ class MessageUtil { } } - if (CONFIG.enableVerbose) { + if (g_config.enableVerbose) { println("Finished processsing file"); println("Total of " + str(this.messagesList.size()) + " messages"); } diff --git a/MsgData.pde b/MsgData.pde new file mode 100644 index 0000000..134b89e --- /dev/null +++ b/MsgData.pde @@ -0,0 +1,45 @@ + + +/** + * Container calss for each message + * It contains the timestamp of the message, sender and receiver IDs, + * and the message content itself + * + * TODO: in the future this should support different types of msgs + * -- since add/remove participants in the thread is registered as a type + * of msg (e.g. subscribe, unsubscribe). Therefore it's appropriate to + * create a parent MsgEvent class that can be used + */ +class MsgData { + + /* Msg data members */ + private long timestamp; + private int sender_id; + private ArrayList receiver_ids; + private String content; + + public MsgData(long timestamp, int sender_id, ArrayList receiver_ids, + String content) { + + this.timestamp = timestamp; + this.sender_id = sender_id; + this.receiver_ids = receiver_ids; + this.content = content; + } + + /** + * Returns true if this message is sent in a group (if the number of + * recipients is greater than 1) + */ + public Boolean isGroupMessage() { + return this.receiver_ids.size() > 1; + } + + /** + * Getters + */ + public long getTimestamp() { return this.timestamp; } + public int getSenderId() { return this.sender_id; } + public String getContent() { return this.content; } + public int getContentLength() { return this.content.length(); } +} diff --git a/MsgManager.pde b/MsgManager.pde new file mode 100644 index 0000000..d1c0dd7 --- /dev/null +++ b/MsgManager.pde @@ -0,0 +1,186 @@ +import java.util.Map; +import java.util.HashMap; + +class MsgManager { + + String[] msgRootPaths; + + /* ID to person hash map */ + /* TODO: instead of mapping to string, map to person obj */ + HashMap personMap; + + /* Threads */ + private ArrayList msgThreads; + + /* Metadata */ + private long earliestTimestamp; + private long latestTimestamp; + private int totalMsgs; + + /** + * MsgManager handles all the data processing of the messages + * .json files, as well as holding onto the data containers of msgs + * @param rootPath the rootpath where the downloaded messages are archived + */ + public MsgManager(String rootPath) { + + /* Add messages paths to be searched */ + this.msgRootPaths = new String[3]; + this.msgRootPaths[0] = pathJoin(rootPath, "messages", "inbox"); + this.msgRootPaths[1] = pathJoin(rootPath, "messages", "archived_threads"); + this.msgRootPaths[2] = pathJoin(rootPath, "messages", "filtered_threads"); + + this.personMap = new HashMap(); + + this.msgThreads = new ArrayList(); + } + + /** + * Read all data + * @param progressBar reference to a progressBar object for updaing the + * progress of populating the messages data + */ + public void populate() { this.populate(null); } + public void populate(Progress progressBar) { + + /* A master list of all thread directories that contains .json files */ + StringList threadList = new StringList(); + + /* Find all threads */ + for (String rootPath : this.msgRootPaths) { + try { + + /* Add to master list */ + StringList threads = listFileNames(rootPath); + + /* If there are no threads; move on to next */ + if (threads == null) + continue; + + /* For each thread, add to the master list */ + for (String thread : threads) { + + /* If it's in the ignore list (defined in g_config), ignore */ + if (g_config.checkIfIgnored(thread)) + continue; + + threadList.append(pathJoin(rootPath, thread)); + } + } catch (NotDirectoryException e) { + println("Error while trying to access root paths: " + + rootPath); + continue; + } + } + + /* Instantiate MsgThread for each thread for processing */ + int numProcessableThreads = threadList.size(); + for (int i = 0; i < threadList.size(); i++) { + + /* Create a new MsgThread object; add to list if it's valid */ + MsgThread newMsg = new MsgThread(this, threadList.get(i)); + if (newMsg.isInitialized()) { + this.msgThreads.add(newMsg); + } + + if (progressBar != null) { + /* TODO: handle progress bar */ + println(str(i) + "/" + str(numProcessableThreads)); + } + } + + /* Compute metadata */ + this.populateThreadsMetadata(); + } + + /** + * Computes the threads metadata at initial-time since it only needs to + * be done once; metadata includes earliest timestamp, last timestamp, + * total number of messages, total number of unique participants, etc. + */ + private void populateThreadsMetadata() { + + /* Calculate earliest and latest timestamp */ + Boolean init = true; + for (MsgThread t : this.msgThreads) { + final long t0 = t.getEarliestTimestamp(); + final long t1 = t.getLatestTimestamp(); + + if (init) { + this.earliestTimestamp = t0; + this.latestTimestamp = t1; + } else { + if (t0 < this.earliestTimestamp) { + this.earliestTimestamp = t0; + } + + if (t1 > this.latestTimestamp) { + this.latestTimestamp = t1; + } + } + } + + /* Compute total number of messages */ + this.totalMsgs = 0; + for (MsgThread t : this.msgThreads) { + totalMsgs += t.getThreadSize(); + } + } + + /** + * Get an ID given a name + * @param name the name + * @return the id associated with the name; -1 if is not found + */ + public int getPersonIDFromName(String name) { + for (Map.Entry person : this.personMap.entrySet()) { + if (person.getValue().equals(name)) { + return (int) person.getKey(); + } + } + + return -1; + } + + /** + * Get an ID given a name; allocate a new ID->name mapping if not found + * @param name the name + * @param the id associated with the name + */ + public int getAndSetPersonIDFromName(String name) { + final int id = this.getPersonIDFromName(name); + if (id == -1) { + int new_id = this.personMap.size(); + this.personMap.put(new_id, name); + return new_id; + } else { + return id; + } + } + + /** + * Reset thread pointers; resets the head index of each thread to + * point to the first/oldest message of each thread + */ + public void resetThreadHeads() { + for (MsgThread t : this.msgThreads) { + t.resetHead(); + } + } + + /** + * Returns an arraylist of MsgData from all the threads up to the given + * timestamp -- from their current head index + * @param t the end timestamp in ms + * @return an arraylist of msgs + */ + public ArrayList getMsgsUntil(long t) { + ArrayList msgs = new ArrayList(); + + for (MsgThread th : this.msgThreads) { + msgs.addAll(th.getMsgsUntil(t)); + } + + return msgs; + } +} diff --git a/MsgThread.pde b/MsgThread.pde new file mode 100644 index 0000000..fd31b1a --- /dev/null +++ b/MsgThread.pde @@ -0,0 +1,401 @@ +enum ThreadType { + REGULAR, /* Regular chat (1-1) */ + REGULAR_GROUP /* Regular group chat */ +} + +/* Parses a single thread */ +class MsgThread { + + /* Reference to parent/manager (for people look up) */ + private MsgManager manager; + + /* Message data */ + private String threadRootPath; + private String[] jsonFiles; + private String name; + + /* Thread type */ + private ThreadType threadType; + + private Boolean initialized; + + /* Contains all the message data */ + private ArrayList messagesData; + + /* The index that points to the head of messagesData */ + private int head; + + /* Participants (ids of people) */ + private ArrayList participant_ids; + + /* Regular thread unknown participant (only for REGULAR thread type) */ + private Boolean unknownParticipant = false; + private String unknownParticipantAlias; + + /* Maximum group chat size (TODO: parameterize in config) */ + private final int MAX_PARTICIPANTS = 20; + + /** + * Constructor for the thread parser; takes in a path to where the + * message .json files are located -- Messenger splits larger + * threads into multiple .json files; + * @param manager the parent MsgManager obj -- must be instantiated + * through here + * @param threadPath the root path of the thread where .json files + * are stored + */ + public MsgThread(MsgManager manager, String threadPath) { + + this.initialized = false; + + /* assign parent/manager */ + this.manager = manager; + + /* given the rootPath, add all json file paths to filePaths */ + this.threadRootPath = threadPath; + try { + this.jsonFiles = sortFilenamesNumerically( + listFileNames(threadPath, "json")); + } catch (NotDirectoryException e) { + println("Not a directory exception occured while " + + "attempting to parse " + threadPath); + } + + /* Messages data container */ + messagesData = new ArrayList(); + + /* process all files */ + if (this.jsonFiles != null) + this.initialized = this.processAllJsonFiles(); + + /* reset data pointer */ + this.head = 0; + } + + /** + * Process all the json files -- paths are stored in filePaths + * @return true if process successful + */ + private Boolean processAllJsonFiles() { + + /* process all messages in reverse order of sorting (oldest first) */ + for (int idx = this.jsonFiles.length - 1; idx >= 0; idx--) { + + /* Load JSON file */ + String jsonFilePath = pathJoin(this.threadRootPath, this.jsonFiles[idx]); + final JSONObject jsonData = loadJSONObject(jsonFilePath); + + /* Populate metadata about thread */ + if (idx == this.jsonFiles.length - 1) { + if (!this.processJSONMetadata(jsonData)) { + return false; + } + } + + /* Process messages */ + this.processJsonFile(jsonData); + } + + return true; + } + + /** + * Process and populate thread metadata such as array of parcipant ids + * (only need to run once even for multiple json files) + * @param jsonData the json data of the file + * @return true if successfullly parses metadata; false if cancel + */ + private Boolean processJSONMetadata(JSONObject jsonData) { + + /* Get thread metadata */ + this.name = jsonData.getString("title"); + this.setThreadType(jsonData.getString("thread_type")); + + /* Get thread participants */ + if (!processJSONParticipants(jsonData)) + return false; + + return true; + } + + /** + * Sets the thread type based on the string listed in the json file + * @param type A string denoting what type it is + */ + private void setThreadType(String type) { + if (type.equals("Regular")) { + this.threadType = ThreadType.REGULAR; + } else if (type.equals("RegularGroup")) { + this.threadType = ThreadType.REGULAR_GROUP; + } else { + println("This should not happen"); + assert false; + } + } + + /** + * Process the participants in a thread + * @param jsonData the json data + * @return true if process is successful, false if fails due to invalid + * set of participants + */ + private Boolean processJSONParticipants(JSONObject jsonData) { + /* Get participants in this thread */ + final JSONArray participantsData = jsonData.getJSONArray("participants"); + + /* Stop processing if number of participants is too few + * or if the number of participants is too large + */ + if (participantsData.size() <= 1 || + participantsData.size() > MAX_PARTICIPANTS) { + return false; + } + + /* Instantiate array of participant ids */ + this.participant_ids = new ArrayList(); + + /* Get participants from file */ + for (int i = 0; i < participantsData.size(); i++) { + String name = participantsData.getJSONObject(i).getString("name"); + + /* Check if the name is "default name/no name" */ + if (name.equals(g_config.defaultName)) { + + if (this.threadType == ThreadType.REGULAR) { + + /* If it's a regular thread: use unknown alias */ + name = "Unknown " + str(UnknownPersonCounter.count++); + this.unknownParticipantAlias = name; + this.unknownParticipant = true; + + } else if (this.threadType == ThreadType.REGULAR_GROUP) { + + /* Ignore if unknown person shows up in group chat */ + continue; + } + } + + /* Get ID from manager and populate member array */ + this.participant_ids.add(manager.getAndSetPersonIDFromName(name)); + } + + return true; + } + + /** + * Process a single json file + * @param jsonData the json data of the file + */ + private void processJsonFile(JSONObject jsonData) { + + /* Get message data */ + final JSONArray msgsData = jsonData.getJSONArray("messages"); + + /* Loop in reverse such that it's sorted oldest to newest */ + for (int i = msgsData.size() - 1; i >= 0; i--) { + final JSONObject msgData = msgsData.getJSONObject(i); + + if (msgData.getBoolean("is_unsent")) + continue; + + processMsg(msgData); + } + } + + private void processMsg(JSONObject msgData) { + /** + * Sender id + * NOTE: if the facebook user deleted their profile, + * the sender_name retrievied here would be 'Other User' + * (as of Nov. 2021). + */ + String sender = msgData.getString("sender_name"); + assert sender != null; + + /* Check if the sender name is unknown */ + if (sender.equals("Facebook User") || sender.equals("Other User")) { + if (this.threadType == ThreadType.REGULAR) { + + /* If this is in a regular chat, replace name with alias */ + assert this.unknownParticipant; + sender = this.unknownParticipantAlias; + + } else if (this.threadType == ThreadType.REGULAR_GROUP) { + + /* Ignore this message entry if it's from unknown */ + return; + } + } + + final int sender_id = this.manager.getPersonIDFromName(sender); + + /* At this point, if the sender_id is still -1, then it means + * that sender name is kept in the transcript, but the name doesn't + * match with the participants list (e.g. "Facebook User"), so + * it will not have an ID since IDs are assigned when reading + * the participants list + * + * We can definitely improve the logic to assign IDs to people, + * including doing a pass of all senders and partcipants in a thread + * before we process any messages -- but this may slow down processing + * by a lot. For now we just drop this message + */ + if (sender_id == -1) { + return; + } + + /* Get receivers + * -- which should just be a copy of the participants + * list minus the sender + */ + ArrayList receiver_ids = new ArrayList( + this.participant_ids); + receiver_ids.remove(Integer.valueOf(sender_id)); + + /* Timestamp */ + final long timestamp = msgData.getLong("timestamp_ms"); + + /* Process single message depending on the type */ + switch (msgData.getString("type")) { + case "Generic": + case "Share": + this.messagesData.add( + this.processGenericMsg(sender_id, receiver_ids,timestamp, msgData)); + break; + + case "Call": + this.messagesData.add( + this.processCallMsg(sender_id, receiver_ids, timestamp, msgData)); + break; + + case "Subscribe": + /* TODO */ + break; + case "Unsubscribe": + /* TODO */ + break; + } + } + + private MsgData processGenericMsg(int sender_id, + ArrayList receiver_ids, long timestamp, JSONObject msgData) { + + /* Determine what kind of message it is */ + /* TODO: for now we'll just focus on text msgs (see below) */ + final String content = msgData.getString("content"); + // final JSONArray photos = msgData.getJSONArray("photos"); + // final JSONArray photos = msgData.getJSONArray("videos"); + // final JSONObject sticker = msgData.getJSONObject("sticker"); + + // TODO: ignored for now + //if (content == null) + // return null; + + /* Create new message data container */ + return new MsgData( + timestamp, + sender_id, + receiver_ids, + content + ); + } + + private MsgData processCallMsg(int sender_id, + ArrayList receiver_ids, long timestamp, JSONObject msgData) { + + /* TODO: NOTE: calls should be visualized by a line */ + /* TODO: call has field 'call_duration' in seconds */ + final int callDurationSecs = msgData.getInt("call_duration"); + + /* TODO: a different type is required to represent this type of msg */ + return new MsgData( + timestamp, + sender_id, + receiver_ids, + "TODO: CALL" + ); + } + + private MsgData processSubscribeMsg(int sender_id, + ArrayList receiver_ids, long timestamp, JSONObject MsgData) { + /* Unimplemented */ + + return null; + } + + private MsgData processUnsubscribeMsg(int sender_id, + ArrayList receiver_ids, long timestamp, JSONObject MsgData) { + /* Unimplemented */ + + return null; + } + + /** + * Reset messages data index/pointer + */ + public void resetHead() { + this.head = 0; + } + + /** + * Get HEAD msg + * @return HEAD MsgData + */ + public MsgData getHeadMsgData() { + return this.messagesData.get(this.head); + } + + /** + * Get the timestamp of the HEAD + * @return the timestamp of the MsgData at HEAD + */ + public long getHeadTimestamp() { + return this.getHeadMsgData().getTimestamp(); + } + + /** + * Get a list of messages from head to a specified timestamp + * Then increase the HEAD index up to the time + * @param endTime the end timestamp + */ + public ArrayList getMsgsUntil(long endTime) { + + ArrayList msgs = new ArrayList(); + while (this.getHeadTimestamp() < endTime) { + + /* Add head msg and increment head */ + msgs.add(this.getHeadMsgData()); + this.head++; + } + + return msgs; + } + + /** + * Returns whether this thread has been processed and initialized + */ + public Boolean isInitialized() { + return this.initialized; + } + + /** + * Returns earliest timestamp + */ + public long getEarliestTimestamp() { + return this.messagesData.get(0).getTimestamp(); + } + + /** + * Returns latest timestamp + */ + public long getLatestTimestamp() { + return this.messagesData.get(this.messagesData.size() - 1).getTimestamp(); + } + + /** + * Returns number of messages in this thread + */ + public int getThreadSize() { + return this.messagesData.size(); + } +} diff --git a/PathUtil.pde b/PathUtil.pde index 9b5ad34..643aadf 100644 --- a/PathUtil.pde +++ b/PathUtil.pde @@ -1,14 +1,32 @@ import java.util.Arrays; import java.util.Comparator; +/** + * Joins two paths together + * @param a the first part of the path + * @param b the second part of the path + * @return a + b + */ String pathJoin(String a, String b) { - return a + CONFIG.pathSeparator + b; + return a + File.separator + b; } +/** + * Joins two paths together + * @param a the first part of the path + * @param b the second part of the path + * @param c the third part of the path + * @return a + b + c + */ String pathJoin(String a, String b, String c) { - return pathJoin(a, b) + CONFIG.pathSeparator + c; + return pathJoin(pathJoin(a, b), c); } +/** + * Gets a list of file names that are in a given directory + * @param dir the path to directory + * @return a StringList of files in that directory (un-joined) + */ StringList listFileNames(String dir) throws NotDirectoryException { File file = new File(dir); StringList filenames = new StringList(); @@ -24,7 +42,34 @@ StringList listFileNames(String dir) throws NotDirectoryException { return filenames; } +/** + * Gets a list of file names that are in a given directory, + * only if such file names have a given extension + * @param dir the path to directory + * @param extension the extension to match for + * @return a StringList of files in that directory that have + * the specified extension + */ +StringList listFileNames(String dir, String extention) throws NotDirectoryException { + final StringList allfiles = listFileNames(dir); + StringList filenames = new StringList(); + + for (String f : allfiles) { + if (f.endsWith(extention)) { + filenames.append(f); + } + } + + return filenames; +} +/** + * Extracts an integer from a string given a start and end token/character + * @param s the string to interpret + * @param startToken the starting character in front of the number + * @param endToken the ending character behind the number + * @return an integer extracted from s; returns 0 if one cannot be found + */ int extractNumber(String s, char startToken, char endToken) { int i = 0; try { @@ -39,19 +84,11 @@ int extractNumber(String s, char startToken, char endToken) { return i; } -StringList listFileNames(String dir, String extention) throws NotDirectoryException { - StringList allfiles = listFileNames(dir); - StringList files = new StringList(); - - for (String f : allfiles) { - if (f.endsWith(extention)) { - files.append(f); - } - } - - return files; -} - +/** + * Given a list of files, sort it numerically based on the number + * @param files a StringList of file names + * @return an array of strings where it's sorted based on extracted number + */ String[] sortFilenamesNumerically(StringList files) { String[] sorted = new String[files.size()]; for (String f : files) { diff --git a/Payload.pde b/Payload.pde index 76677ea..5c5c289 100644 --- a/Payload.pde +++ b/Payload.pde @@ -124,7 +124,7 @@ class PayloadSegment extends Payload{ PersonNode targetPerson; float radius = random(3, 8); - float opacity = random(CONFIG.payloadOpacityMin, CONFIG.payloadOpacityMax); + float opacity = random(g_config.payloadOpacityMin, g_config.payloadOpacityMax); float travel_lerp; float travel_y_lerp_mult; @@ -138,17 +138,17 @@ class PayloadSegment extends Payload{ this.prevX = this.x; this.prevY = this.y; - if (CONFIG.payloadSizeBasedOnMessageLength) { + if (g_config.payloadSizeBasedOnMessageLength) { this.radius = size; } - this.travel_lerp = random(CONFIG.payloadSegmentLerpMin, CONFIG.payloadSegmentLerpMax); + this.travel_lerp = random(g_config.payloadSegmentLerpMin, g_config.payloadSegmentLerpMax); this.travel_y_lerp_mult = random(0.5, 0.8); this.targetPerson = target; // TODO: FIXME: - if (source.equals(CONFIG.masterName)) { + if (source.equals(g_config.masterName)) { this.isMasterSending = true; } } @@ -156,7 +156,7 @@ class PayloadSegment extends Payload{ @Override public void draw() { pushMatrix(); - stroke(this.isMasterSending ? CONFIG.payloadSendColor : CONFIG.payloadReceiveColor, this.opacity); + stroke(this.isMasterSending ? g_config.payloadSendColor : g_config.payloadReceiveColor, this.opacity); strokeWeight(this.radius); line(this.x, this.y, this.prevX, this.prevY); popMatrix(); @@ -184,13 +184,13 @@ class PayloadSegment extends Payload{ class PayloadSegment2 extends PayloadSegment { public PayloadSegment2(PersonNode source, PersonNode target, float size) { super(source, target, size); - this.travel_lerp = random(CONFIG.payloadSegmentGroupLerpMin, CONFIG.payloadSegmentGroupLerpMax); + this.travel_lerp = random(g_config.payloadSegmentGroupLerpMin, g_config.payloadSegmentGroupLerpMax); } @Override public void draw() { pushMatrix(); - stroke(CONFIG.payloadGroupColor, this.opacity); + stroke(g_config.payloadGroupColor, this.opacity); strokeWeight(this.radius); line(this.x, this.y, this.prevX, this.prevY); popMatrix(); @@ -208,12 +208,12 @@ class PayloadFactory { } public void makeIndividualPayload(PersonNode sender, PersonNode receiver, float size) { - if (PAYLOADS_MAXSIZE > this.payloads.size()) + //if (PAYLOADS_MAXSIZE > this.payloads.size()) this.payloads.add(new PayloadSegment(sender, receiver, size)); } public void makeGroupPayload(PersonNode sender, PersonNode receiver, float size) { - if (PAYLOADS_MAXSIZE > this.payloads.size()) + //if (PAYLOADS_MAXSIZE > this.payloads.size()) this.payloads.add(new PayloadSegment2(sender, receiver, size)); } } diff --git a/Person.pde b/Person.pde index 2cad31b..3405d04 100644 --- a/Person.pde +++ b/Person.pde @@ -46,8 +46,8 @@ class PersonNode { this.stats = new PersonStat(); // Set name - if (CONFIG.hideRealNames) { - this.name = CONFIG.hideNameReplacement; + if (g_config.hideRealNames) { + this.name = g_config.hideNameReplacement; } else { this.name = name; } @@ -118,8 +118,8 @@ class PersonNode { pg.popMatrix(); // Set hover state for UI (todo: add mutex lock so only one hover is possible and mouse input is consumed) - statcardHover.person = this; - statcardHover.show = true; + //statcardHover.person = this; + //statcardHover.show = true; } protected void drawNode(PGraphics pg) { diff --git a/RenderLayer.pde b/RenderLayer.pde index 01983b9..c8ef7f1 100644 --- a/RenderLayer.pde +++ b/RenderLayer.pde @@ -57,7 +57,7 @@ class RenderUILayer extends RenderLayer { } private void renderTimestamp() { - this.pg.textFont(monospaceFont); + this.pg.textFont(g_uiMonospaceFont); this.pg.textAlign(CENTER, CENTER); String date = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm").format(new java.util.Date(this.timestamp)); this.pg.textSize(20); @@ -85,7 +85,7 @@ class RenderUILayer extends RenderLayer { } private void renderStatCardHover() { - this.pg.textFont(font); + this.pg.textFont(g_uiFont); if (this.statCardHover == null) return; this.statCardHover.draw(this.pg); } diff --git a/Timeline.pde b/Timeline.pde index 13d1ee8..4ca62cf 100644 --- a/Timeline.pde +++ b/Timeline.pde @@ -25,29 +25,29 @@ class Timeline { public void handleMouseInput() { // Check if mouse is inside the box - final float x2 = this.x + this.w; + //final float x2 = this.x + this.w; - this.hovered = mouseX > this.x && mouseX < x2 && mouseY > this.y && mouseY < this.y + this.h; + //this.hovered = mouseX > this.x && mouseX < x2 && mouseY > this.y && mouseY < this.y + this.h; - if (this.hovered) { - if (mousePressed) { - final float new_percentage = map(mouseX, this.x, x2, 0, 1); - this.setPercentage(new_percentage); - - final int new_gi = (int) map(new_percentage, 0, 1, 0, man.organizedMessagesList.size()); - - if (gi < new_gi) { - while (gi < new_gi) { - int di = gi % man.organizedMessagesList.size(); - MessageData current = man.organizedMessagesList.get(di); - processCurrentmessageData(current); - gi++; - } - } else { - gi = new_gi; - currentTimestamp = man.organizedMessagesList.get(gi).timestamp; - } - } - } + //if (this.hovered) { + // if (mousePressed) { + // final float new_percentage = map(mouseX, this.x, x2, 0, 1); + // this.setPercentage(new_percentage); + + // final int new_gi = (int) map(new_percentage, 0, 1, 0, man.organizedMessagesList.size()); + + // if (gi < new_gi) { + // while (gi < new_gi) { + // int di = gi % man.organizedMessagesList.size(); + // MessageData current = man.organizedMessagesList.get(di); + // processCurrentmessageData(current); + // gi++; + // } + // } else { + // gi = new_gi; + // currentTimestamp = man.organizedMessagesList.get(gi).timestamp; + // } + // } + //} } } diff --git a/data/config.ini b/data/config.ini index a205755..6e9ddb5 100644 --- a/data/config.ini +++ b/data/config.ini @@ -1,7 +1,7 @@ [data config] -data_root_path=/Users/muchen/Downloads/messages_toy +data_root_path=/Users/muchen/Downloads/messages master_name=Muchen He -facebook_default_name=Unnamed +facebook_default_name=Facebook User [program config] run_verbose=no diff --git a/data/img/logo.png b/data/img/logo.png new file mode 100644 index 0000000..43b3941 Binary files /dev/null and b/data/img/logo.png differ